Repository navigation
Conversation
A confined command cannot run `gh`: it cannot read `~/.config/gh`, so `gh` fails before it does anything, and an agent in `confine` cannot open a pull request. Now, on macOS, if `gh` is installed, the command finds a shim first on its `PATH` in place of `gh`: `confine` itself, under that name, which sends its arguments, working directory and standard streams over a socket to `confine`, outside the sandbox. `confine` shows a dialog with the command line, and runs the real `gh` with the command's streams only if you allow it: once, or always, until it exits, for that command, such as `gh pr create`. `gh api` is asked about every time, and `gh auth`, `config`, `alias`, `extension`, `secret`, `variable`, `ssh-key` and `gpg-key` are never run. `CONFINE_ASK` names a program to ask with instead of a dialog. `--headless` turns the broker off, for when no one is at the screen. `gh` runs git, which the repository's configuration and hooks can tell to run any program, and the command can change both. So `gh` runs with a clone of `.git` made where the command cannot write, with hooks and the file system monitor off, and only if its configuration differs from what it was when `confine` started in nothing but what `gh` and `git push` write. The clone then replaces `.git`, so what `gh` changed is kept. With the broker, `confine` waits for the command rather than becoming it. `gh`'s token is in the keychain, which the command can read, so the broker keeps the command from running `gh`, but not from reading its token.
The dialog waited 5 minutes for an answer, so an agent no one was watching stopped for as long, and what you allowed always was forgotten when `confine` exited. Now Deny, the default button, counts down from 10, and is pressed when it reaches zero; the command is told it was not allowed. The dialog is an alert in JavaScript for Automation, as AppleScript's `display dialog` can give up but not count down. What you allow always, such as `gh pr create`, is added to `~/.config/confine/allow`, which the command can neither read nor write, and is not asked again. What each tool may be asked to do, and how it is kept from running what the command wrote, moves out of the broker to `tools/`, one file each, so that tools can be added: `tools/gh.rs` has `gh`'s, and `tools/git.rs` the clone of `.git` that `gh`, and any tool that runs git, runs with. "Broker" stays in the code, where it is the usual word, and leaves the man page, which says what you see: `confine` asking to run a tool outside the sandbox. The shim's socket is in `CONFINE_SOCKET`.
penberg
force-pushed
the
confine-gh-broker
branch
from
October 30, 2026 07:24
d69dfbc to
09cabae
Compare
penberg
added a commit
that referenced
this pull request
Nov 1, 2026
A fork was everything in the directory, so a large `node_modules` or `target` made forking slow. Now a fork and its base leave out what `.gitignore` files ignore and git does not track. `--no-ignore`, for `fsfork` and `confine`, forks everything as before. The name is the one `rg` and `fd` use. Measured on APFS with a debug build: | Directory | `--no-ignore` | Default | | --- | --- | --- | | 500 source files, 200,000 ignored in `node_modules` | 4.7–7.1 s | 0.10–0.13 s | | This repository (244 MB `target`, about 6,000 files) | 0.29–0.48 s | 0.09–0.17 s | Asking git adds a fixed cost, so a directory with very few files forks more slowly than before: about 0.15 s for a one-file directory. ## Behaviour changes - **Cold builds:** builds in a fork no longer start warm unless `--no-ignore` is given. - **Ignored files are missing:** this includes files such as `.env`, which a command in the fork may need. ## How it works - **What counts as ignored:** only `.gitignore` files, as in `fsdiff`, not `.git/info/exclude` or a global excludes file. Tracked files stay even if a pattern matches them. - **Subdirectories:** a subdirectory of a repository also goes by the `.gitignore` files above it. A directory that is itself ignored goes by its own only. - **If git can't answer:** if git is missing or fails, for example on a corrupt index, `fsfork` warns on standard error and forks everything. - **Unchanged parts:** directories with nothing ignored in them are still cloned whole, and the fork is still cloned from the base. ## Security `confine` forks outside the sandbox, and the directory may be a fork that a confined command changed, so git must not use its configuration: `core.fsmonitor` alone would run a command. Git runs on a temporary repository with no configuration and reads only `.gitignore` files and the index. There is a test that a configured `core.fsmonitor` does not run. ## Limitations - A directory with something ignored in it is made anew, keeping its permissions and modification time but not its other attributes. - On macOS, `clonefile` rejects a FIFO or socket on its own, though it takes them inside a directory. Next to something ignored, FIFOs are remade, and sockets and devices are left out. - This branches from `main`, and will touch the same lines of `confine.rs` and `confine.1` as #5, so whichever lands second needs a small rebase. ## Testing `cargo test`, `cargo clippy` and `cargo fmt` pass on macOS, and I checked by hand that repositories with a split or sparse index fork correctly. CI runs the same tests on Linux, where the path through `cp` is exercised; speed there is unmeasured.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A confined command cannot run
gh: it cannot read~/.config/gh, soghfails before it does anything, and an agent inconfinecannot opena pull request.
Now, on macOS, if
ghis installed, the command finds a shim first on itsPATHin place ofgh:confineitself, under that name, which sends itsarguments, working directory and standard streams over a socket to
confine, outside the sandbox.confineshows a dialog with the commandline, and runs the real
ghwith the command's streams only if you allowit: once, or always for that command, such as
gh pr create, which iskept in
~/.config/confine/allow. The dialog denies by itself after 10seconds, so an agent no one is watching is not kept waiting.
gh apiis asked about every time, andgh auth,config,alias,extension,secret,variable,ssh-keyandgpg-keyarenever run.
CONFINE_ASKnames a program to ask with instead of a dialog.--headlessturns the broker off, for when no one is at the screen.ghruns git, which the repository's configuration and hooks can tell torun any program, and the command can change both. So
ghruns with aclone of
.gitmade where the command cannot write, with hooks and thefile system monitor off, and only if its configuration differs from what
it was when
confinestarted in nothing but whatghandgit pushwrite. The clone then replaces
.git, so whatghchanged is kept.With the broker,
confinewaits for the command rather than becoming it.gh's token is in the keychain, which the command can read, so thebroker keeps the command from running
gh, but not from reading itstoken.
Tools live in
cmd/confine/tools/, one file each (gh.rs, andgit.rsfor the.gitprotection any tool that runs git needs).Not done yet
CONFINE_ASK.security find-generic-password -s gh:github.com -w), soREADME.mdandconfine.1saying the keychain guards its items is wrong forgh..gitwhileghruns is lost,ghgoes on if the shim is killed,.gitfiles of worktrees are not supported, and the repository must be on the same volume as the home directory.