Skip to content

fix(sandbox): give sandboxed prek its own home - #1792

Merged
potiuk merged 10 commits into
apache:mainfrom
potiuk:fix/sandbox-prek-home
Oct 24, 2026
Merged

potiuk merged 10 commits into
apache:mainfrom
potiuk:fix/sandbox-prek-home

Conversation

@potiuk

@potiuk potiuk commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

A commit made in a terminal runs prek's git hooks outside the sandbox, from environments under ~/.cache/prek, which the ~/.cache/ grant made sandbox-writable. A sandboxed command could plant code there that ran unsandboxed on the next terminal commit.

  • Settings: "env": {"PREK_HOME": "~/.cache/prek-sandbox"} gives sandboxed prek its own home (only Claude Code's commands see it, never a terminal), and Edit(~/.cache/prek/**) keeps sandboxed commands out of the terminal's home. sandbox-lint requires both. This protects terminal commits in every repository, including ones with their own core.hooksPath.
  • Dispatcher: the env also reaches commands Claude Code runs outside the sandbox. The whole-user git-hook dispatcher gives a hook it runs with no SANDBOX_RUNTIME a host-only PREK_HOME (~/.local/share/prek-host) and XDG_CACHE_HOME (for the pip, go, npm and uv caches hooks use). Verify now flags a repository-level core.hooksPath that skips the dispatcher, and warns for per-project scope.
  • Session start: a repository-level core.hooksPath pointing at the repository's own .git/hooks (some prek install runs set one) skips the whole-user dispatcher; magpie-setup's session-start hook now removes it, since the dispatcher chains there anyway, and reports any other value.
  • Adds the first dispatcher tests.

🤖 Generated with Claude Code

@potiuk
potiuk enabled auto-merge (squash) October 24, 2026 22:11
@potiuk
potiuk force-pushed the fix/sandbox-prek-home branch from 3259ae4 to 490034f Compare October 24, 2026 22:26
@github-actions github-actions Bot added family:setup setup-* skills family:tools tools/* family:docs Docs, MISSION.md, READMEs capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) substrate:sandbox Tool substrate: agent isolation, egress control, settings audit labels Oct 24, 2026
@potiuk
potiuk disabled auto-merge October 24, 2026 22:29
@potiuk potiuk changed the title fix(sandbox): give sandboxed prek its own home fix(sandbox): give host-run git hooks their own prek home Oct 24, 2026
@potiuk potiuk changed the title fix(sandbox): give host-run git hooks their own prek home fix(sandbox): give sandboxed prek its own home Oct 24, 2026
@potiuk
potiuk force-pushed the fix/sandbox-prek-home branch from 9e1d715 to 17b554e Compare October 24, 2026 22:55
@github-actions github-actions Bot added the substrate:setup Tool substrate: adopter-side setup state resolved at runtime (lock parsing, floors, fingerprints) label Oct 24, 2026
@potiuk
potiuk enabled auto-merge (squash) October 24, 2026 22:56
A commit made in a terminal runs prek's git hooks outside the sandbox
from their environments under ~/.cache/prek, which the ~/.cache grant made
sandbox-writable: a sandboxed command could plant code there that ran
unsandboxed on the next terminal commit.

Nothing excluded from the sandbox runs prek, so the settings env (seen only
by Claude Code's own commands, never a terminal) does the split:
PREK_HOME=~/.cache/prek-sandbox for sandboxed prek, and
Edit(~/.cache/prek/**) keeps sandboxed commands out of the terminal's home.
sandbox-lint requires both; verify check 19d, install Step U, the update
drift item and the docs cover the switch.

Generated-by: Claude (Opus 5.5)
…andbox

Review follow-up. Claude Code's env also reaches a command it runs outside
the sandbox (an approved bypass, often a signed commit), whose hooks would
then run the environments sandboxed commands wrote under
~/.cache/prek-sandbox. The whole-user git-hook dispatcher now drops a
PREK_HOME pointing there when SANDBOX_RUNTIME is absent, before chaining to
the repository's own hook, so prek falls back to the host-only
~/.cache/prek. Sandboxed code that unsets SANDBOX_RUNTIME only makes its own
prek fail to write there. Per-project scope has no dispatcher, so verify
check 19d warns about it and the docs say not to bypass the sandbox for a
commit there. Adds the first dispatcher tests.

Generated-by: Claude (Opus 5.5)
…s env

Replaces the settings-env approach. env.PREK_HOME also reached commands
Claude Code runs outside the sandbox, which then needed stripping again.
Instead the whole-user git-hook dispatcher gives a hook it runs outside the
sandbox (no SANDBOX_RUNTIME: a terminal commit or a bypassed one)
PREK_HOME=~/.local/share/prek-host, which no sandbox grant covers, while
sandboxed hooks keep the default ~/.cache/prek. An operator's own PREK_HOME
outside ~/.cache is kept. The settings env, the Edit(~/.cache/prek/**) deny
and the sandbox-lint rule are gone; verify check 19d now checks the
dispatcher copy and warns for per-project scope.

Generated-by: Claude (Opus 5.5)
Review follow-up. The tools a hook calls keep caches of their own (pip, go,
npm, and uv without a configured cache-dir follow XDG_CACHE_HOME, ~/.cache
by default on Linux), and installing from a cache the sandbox wrote is the
same escape. Outside the sandbox the dispatcher now also points
XDG_CACHE_HOME at ~/.local/share/prek-host/cache, with the same rule as
PREK_HOME: an operator's own value outside ~/.cache is kept.

Generated-by: Claude (Opus 5.5)
…passed hooks

Reverts the dispatcher-only switch. Many repositories set their own
core.hooksPath (often left by `prek install`), which skips the whole-user
dispatcher, so a terminal commit there ran prek from the sandbox-writable
~/.cache/prek. The settings env protects terminal commits in every
repository: sandboxed prek uses PREK_HOME=~/.cache/prek-sandbox, and
Edit(~/.cache/prek/**) keeps sandboxed commands out of the terminal's home
(sandbox-lint requires both again). The dispatcher keeps covering hooks
Claude Code runs outside the sandbox (host-only PREK_HOME and
XDG_CACHE_HOME), and verify now flags a repository-level core.hooksPath
that skips it.

Generated-by: Claude (Opus 5.5)
Some prek install runs set a repository-level core.hooksPath to the
repository's own .git/hooks, which overrides the global one and skips the
whole-user dispatcher, and with it the host-only prek home for hooks run
outside the sandbox. The session-start hook now removes that value when the
dispatcher is installed (it chains to .git/hooks anyway) and tells the user;
any other value is reported. It reads and writes only the global config and
the repository's config file through `git config --global` / `--file`, so
no repository hooks or fsmonitor run, refuses a symlinked .git or config,
and leaves linked worktrees to their main checkout.

Generated-by: Claude (Opus 5.5)
Review follow-up. .git sits in the sandbox-writable project, so handing the
repository config to `git config --file` left a window in which .git could
be swapped for a symlink and git would write a file named config wherever
it pointed. The hooks-path cleanup now opens .git and config with O_NOFOLLOW
through one descriptor, parses the [core] hooksPath line itself, and renames
a fresh temporary file inside that directory; a config with includes or
anything unusual is only reported. Values that reach the message (and the
model's context) are quoted, stripped of control characters and truncated.

Generated-by: Claude (Opus 5.5)
@potiuk
potiuk force-pushed the fix/sandbox-prek-home branch from df9a59a to 9f478f1 Compare October 24, 2026 23:01
…anup

Review follow-up. A key after a section header on the same line, or a
header the parser does not understand, now means "only report"; a
[core "x"] subsection is no longer taken for [core]; a config that does not
decode is reported instead of skipped; and the rewritten text must read
back plain with no hooksPath left, or nothing is written.

Generated-by: Claude (Opus 5.5)
Comment thread plugins/magpie-setup/skills/setup/setup_preflight/isolated.py Fixed
CodeQL: overly permissive file permissions. The temporary file was created
0644; it is now created 0600 and given the original config's mode before
the rename, whatever the umask.

Generated-by: Claude (Opus 5.5)
@potiuk
potiuk merged commit d260085 into apache:main Oct 24, 2026
15 checks passed
potiuk added a commit that referenced this pull request Oct 25, 2026
Ships the fixes merged since 0.9.0.dev202610241347 to marketplace installs:
the container-gateway hook hardening (#1784, #1789, #1790, #1793, #1804),
host-only uv state (#1785), session-start cleanup of the stale uv grant
(#1788), the trusted session-start interpreter (#1791), the sandboxed prek
home (#1792) and per-machine gateway hook registration (#1797).

Generated-by: Claude (Opus 5.5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) family:docs Docs, MISSION.md, READMEs family:setup setup-* skills family:tools tools/* substrate:sandbox Tool substrate: agent isolation, egress control, settings audit substrate:setup Tool substrate: adopter-side setup state resolved at runtime (lock parsing, floors, fingerprints)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants