Docs: explain sandbox constraints for sidebar extensions (#9104)

* docs: explain sidebar extension sandbox constraints

* docs: clarify sidebar sandbox authorization boundaries

* docs: distinguish development tool paths from distribution

* docs: refresh sidebar sandbox guidance
This commit is contained in:
Austin Wang
2026-08-11 17:03:19 -07:00
committed by GitHub
parent 31fd3b9c77
commit 5ff45e39c9
3 changed files with 175 additions and 14 deletions
+29 -5
View File
@@ -9,10 +9,14 @@ This is a standalone macOS app that embeds a CMUX sidebar ExtensionKit app exten
3. Replace the Manaflow signing team with your own team.
4. Replace the app and extension bundle identifiers with your own reverse-DNS identifiers.
5. Keep the extension point identifier as `com.cmuxterm.app.cmux.sidebar`.
6. Build and launch the containing app once.
7. In CMUX, click the puzzle button next to the sidebar help button, open Sidebar Extensions, and enable the sample.
8. In the same puzzle menu, choose the extension sidebar provider.
9. In the extension sidebar header, choose `CMUX ExtKit Sample Sidebar` if more than one sidebar extension is enabled.
6. Keep App Sandbox enabled for the extension target. An unsandboxed appex can
build, embed, and sign without registering with ExtensionKit.
7. Build and launch the containing app once.
8. In CMUX, click the puzzle button next to the sidebar help button, open Sidebar
Extensions, and enable the sample.
9. In the same puzzle menu, choose the extension sidebar provider.
10. In the extension sidebar header, choose `CMUX ExtKit Sample Sidebar` if more
than one sidebar extension is enabled.
The sample targets macOS 14+, matching CMUX.
@@ -90,9 +94,29 @@ authors do not define `configuration`, bind an extension point in Swift, or touc
The manifest is the permission request CMUX shows to users. Request only the scopes
your sidebar actually needs.
## Running External Tools
CMUX read scopes can supply a workspace path, but they do not grant filesystem
access to that path. A compiled extension that launches `git` or another
external process must also satisfy the macOS App Sandbox, executable resolution,
working-directory, and file-privacy rules. Follow the SDK's
[Running external tools](../../Packages/macOS/CmuxExtensionKit/README.md#running-external-tools)
guide before adding `Process` code.
## Troubleshooting
If the extension does not appear in CMUX, launch the containing app once, then reopen CMUX's Sidebar Extensions browser.
If the extension does not appear in CMUX, confirm that App Sandbox is enabled for
the extension target, launch the containing app once, then reopen CMUX's Sidebar
Extensions browser. You can also check system discovery directly:
```sh
pluginkit -mAvvv
```
The normal CMUX release uses `com.cmuxterm.app.cmux.sidebar`. Tagged development
builds use the point stored in the host's `CMUXSidebarExtensionPointIdentifier`
Info.plist key (currently `<host-bundle-id>.cmux.sidebar`), so do not filter on
the production point when diagnosing a tagged or custom host.
If it appears but cannot be enabled, check signing on both the containing app and the embedded appex.
+136 -9
View File
@@ -2,7 +2,8 @@
`CmuxExtensionKit` is the zero-dependency public SDK for CMUX sidebar extensions.
Version 1 only supports sidebar extensions. The API exposes a stable workspace snapshot and typed action channels:
The current SDK supports sidebar extensions only. Its API exposes a stable
workspace snapshot and typed action channels:
- read the current sidebar snapshot
- create, select, navigate, and close workspaces
@@ -11,7 +12,8 @@ Version 1 only supports sidebar extensions. The API exposes a stable workspace s
The snapshot includes workspace identity, title, detail text, paths, git branch, unread state, listening ports, pull request URLs, and shared surface metadata. It does not expose terminal buffers, shell history, environment variables, secrets, or arbitrary filesystem access.
Host-side lifecycle, discovery, and display belong in `Packages/macOS/CMUXExtensionHostSupport`.
Host-side lifecycle, discovery, and display belong in
`Packages/macOS/CmuxSidebar/Sources/CmuxSidebar/ExtensionHost`.
Internal cmux-owned sidebar provider/render models live in `Packages/macOS/CmuxSidebarProviderKit`.
They are separate from the public extension-author SDK.
@@ -19,24 +21,31 @@ They are separate from the public extension-author SDK.
Sidebar extensions are ExtensionKit app extensions. `CmuxExtensionKit` and the reference projects target macOS 14+, matching CMUX.
Use `Examples/SampleSidebarExtensionApp` as the reference project:
Use [`Examples/SampleSidebarExtensionApp`](../../../Examples/SampleSidebarExtensionApp)
as the reference project:
1. Open `SampleSidebarExtensionApp.xcodeproj`.
2. Change the app and extension bundle identifiers to your own reverse-DNS prefix.
3. Change the signing team from Manaflow to your team.
4. Keep the extension point identifier as `com.cmuxterm.app.cmux.sidebar`.
5. Build and launch the containing app once so macOS registers the embedded extension.
6. In CMUX, open Sidebar Extensions from the puzzle button next to the sidebar help button and enable your extension.
7. Choose the extension sidebar provider from that puzzle menu.
8. If more than one sidebar extension is enabled, choose your extension from the extension sidebar header.
5. Keep App Sandbox enabled for the extension target.
6. Build and launch the containing app once so macOS registers the embedded extension.
7. In CMUX, open Sidebar Extensions from the puzzle button next to the sidebar
help button and enable your extension.
8. Choose the extension sidebar provider from that puzzle menu.
9. If more than one sidebar extension is enabled, choose your extension from the
extension sidebar header.
The extension target declares the extension point manually in its `Info.plist`:
The extension target declares the extension point in its `Info.plist`. The
reference project gives this build setting the production value
`com.cmuxterm.app.cmux.sidebar`; the tagged development helper overrides it to
match the tagged host:
```xml
<key>EXAppExtensionAttributes</key>
<dict>
<key>EXExtensionPointIdentifier</key>
<string>com.cmuxterm.app.cmux.sidebar</string>
<string>$(CMUX_SIDEBAR_EXTENSION_POINT_ID)</string>
</dict>
```
@@ -107,6 +116,124 @@ host-side callbacks are SPI for CMUX's own host implementation.
Creating or splitting a browser surface with a URL requires both the surface
action scope and `openURL`.
## Running External Tools
`CmuxExtensionKit` permissions govern the data and actions CMUX shares with an
extension. They do not relax the macOS App Sandbox or grant filesystem access.
A child process inherits the extension's sandbox, so apply all of the following
when launching `git` or another command-line tool.
### Keep the extension sandboxed
Leave **App Sandbox** enabled on the appex target
(`ENABLE_APP_SANDBOX = YES`). An unsandboxed ExtensionKit appex can compile,
embed, and sign but never register, with no useful error in CMUX. Check
registration independently of CMUX with:
```sh
pluginkit -mAvvv
```
The normal CMUX release uses `com.cmuxterm.app.cmux.sidebar`. Tagged development
builds use the point stored in the host's `CMUXSidebarExtensionPointIdentifier`
Info.plist key (currently `<host-bundle-id>.cmux.sidebar`). If the extension is
absent from the unfiltered output, fix its sandbox, signing, bundle identifier,
and extension point before debugging the CMUX connection.
### Launch the real executable, not an Xcode shim
Do not launch `/usr/bin/git` from an extension. On macOS it is an Xcode
tool-selection shim that resolves the active developer tool through `xcrun`,
and `xcrun` cannot run inside App Sandbox. Other developer-tool shims under
`/usr/bin` have the same limitation.
```text
xcrun: error: cannot be used within an App Sandbox.
```
For local development, real Git paths reported to avoid the Xcode shim include:
- `/opt/homebrew/bin/git`
- `/Library/Developer/CommandLineTools/usr/bin/git`
- `/Applications/Xcode.app/Contents/Developer/usr/bin/git`
Treat host-installed paths as development-only diagnostics, not portable
distribution targets: their availability and sandbox access vary by machine.
Even if one launches in a local development setup, App Sandbox user-selected
file access authorizes data access, not execution of arbitrary host binaries.
For a distributed extension, follow Apple's [sandboxed command-line helper
guidance](https://developer.apple.com/documentation/xcode/embedding-a-helper-tool-in-a-sandboxed-app):
embed and sign the required executable or helper in the extension's own bundle.
The helper should carry the App Sandbox and sandbox-inheritance entitlements
(`com.apple.security.app-sandbox` and `com.apple.security.inherit`). A child tool
still inherits the extension's sandbox and file access.
### Set a readable working directory
Always set `Process.currentDirectoryURL` before launch. ExtensionKit may start
the appex in a directory that its sandbox cannot read. Tools that inspect their
inherited working directory can then fail before doing useful work:
```text
fatal: Unable to read current working directory: Operation not permitted
```
Use an already authorized directory, normally the repository:
```swift
let process = Process()
process.executableURL = gitURL
process.currentDirectoryURL = repositoryURL
process.arguments = ["status", "--short"]
```
If you keep `-C`, still set `currentDirectoryURL` to a readable directory.
Current Git accepts `-C` as a global option before the subcommand
(`git -C <repository> status`), but not as a `status` option; an explicit
`currentDirectoryURL` also protects launches of other tools that do not have a
Git-style override.
### Authorize repository access explicitly
A `workspacePaths` grant tells the extension where a repository is; it does not
authorize the extension process to open it. CMUX also does not broker command
execution or bookmark data: `CmuxSidebarHost` exposes typed workspace, surface,
and URL actions only. The extension owns every `Process` it starts and every
filesystem grant that process needs.
Enable the appropriate **User Selected File** entitlement on the extension
target and have the user choose the repository with a standard Open panel. If
the containing app owns selection instead, transfer URL bookmark data through
an app-owned channel, such as storage shared by an App Group; the current
`CmuxExtensionKit` XPC channel does not carry arbitrary bookmark payloads. Both
targets need the capabilities required by that channel. Resolve the bookmark in
the extension and keep the authorized URL's security-scoped access active for
the entire child-process lifetime. Apple's [macOS App Sandbox file-access
guide](https://developer.apple.com/documentation/security/accessing-files-from-the-macos-app-sandbox)
distinguishes live interprocess URL bookmarks from persistent security-scoped
bookmarks and documents App Group storage.
Privacy controls such as Files and Folders still apply to locations including
Desktop, Documents, and Downloads. [Apple documents embedded
extensions](https://support.apple.com/guide/security/supporting-extensions-secabd3504cd/web)
as sharing their containing app's privacy-control grants, but CMUX is the
activating host, not the app that contains a third-party sidebar. That is
separate from App Sandbox repository access: use explicit selection and
bookmarks, and handle denial without assuming that extension activation will
present a consent prompt.
Full Disk Access is a user-granted System Settings permission intended for
workflows that genuinely need access across the disk. If a broad-disk workflow
still receives a denial during development, System Settings may require
selecting the embedded `.appex` explicitly. Do not make it the normal setup path
for a sidebar that reads selected repositories; prefer explicit selection and
bookmarks.
If you test privacy grants during development, sign the appex with a stable
Apple Development or self-signed identity. An ad-hoc signature's designated
requirement is derived from the changing binary hash, so a rebuild can be
treated as a different program and lose an existing privacy decision.
## Permissions
List every scope and action your extension needs in its manifest. CMUX filters the
+10
View File
@@ -6,6 +6,16 @@ native SwiftUI in the real sidebar, hot-reloads on save, binds to live cmux
state, and can run cmux commands on tap. This guide is the authoring contract
for you or a coding agent.
This guide covers interpreted custom sidebars, which cannot import frameworks
or start child processes. For compiled Swift in an ExtensionKit sidebar, start
with the
[sample app](../Examples/SampleSidebarExtensionApp/README.md) and the
[CmuxExtensionKit authoring guide](../Packages/macOS/CmuxExtensionKit/README.md).
Compiled extensions run inside the macOS App Sandbox; if yours launches `git`
or another external tool, read
[Running external tools](../Packages/macOS/CmuxExtensionKit/README.md#running-external-tools)
before choosing an executable or opening a repository.
It is a beta, on by default. Turn it off in **Settings → Custom Sidebars**
(`customSidebars.beta.enabled`). While off, custom sidebars do not appear.