Conversation
Resolve only a validated active Apple developer toolchain and preserve global Git config, include, and excludes semantics through exact read-only sandbox grants. Explicit denies continue to override runtime paths. Generated-by: Codex Signed-off-by: Dante <duanjl.china@gmail.com>
|
Thanks for digging into the Apple Git failure. The direction is reasonable, but I do not think this is ready to merge yet. The current evidence proves resolver behavior and policy construction; it does not yet prove the end-to-end contract on the environments this change is intended to fix. The main gaps I would like addressed:
The CI results are useful, but they do not substitute for the missing end-to-end macOS evidence. I would be happy to re-review once the real Xcode path and the deny/authorization composition cases are demonstrated. |
|
Addressed the authorization and scope concerns in
Validation completed: lint and format; affected runtime build; 87 focused resolver/policy/Bash-wiring tests; both knip checks; and resolver validation against this host's selected CLT plus its physical |
Generated-by: Codex Signed-off-by: Dante <duanjl.china@gmail.com>
2662bf4 to
7894f8b
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
Thanks — the direction looks right and the resolver is well structured. I focused on what the "bounded" bound actually is, the platform gating, and the security claim the Summary makes.
What is bounded
The bounds are the two synchronous discovery subprocesses in packages/runtime/src/sandbox/macos-command-paths.ts: xcode-select -p (1 s — lines 27, 97–103) and codesign --verify (1 s — lines 27–28, 106–112). spawnSync with timeout kills and reaps the child, and stdio: 'ignore' on the codesign probe means there's no pipe or fd to leak, so cleanup on timeout is fine. There are no retries, which I think is correct — retrying a toolchain probe just adds latency. The numbers look reasonable; my concerns below are about coverage and cost, not the values.
codesign --verify --strict does not establish an Apple signature
macos-command-paths.ts:107. --verify only checks that whatever signature is present is internally valid; it does not pin the signer. Verified locally on macOS:
# /bin/ls copied and ad-hoc signed with `codesign -s -`
$ codesign --verify --strict ./libxcrun.dylib ; echo $?
0
$ codesign --verify --strict -R="anchor apple" ./libxcrun.dylib ; echo $?
3
$ codesign --verify --strict -R="anchor apple" \
/Library/Developer/CommandLineTools/usr/lib/libxcrun.dylib ; echo $?
0
The real libxcrun.dylib carries Authority=Apple Code Signing / TeamIdentifier=59GAB85EFG; an ad-hoc one carries Signature=adhoc and passes the current check. So the Summary's "require a valid Apple signature" and the code comment's "code-sign validated" both promise more than the check delivers.
Why I think this is the load-bearing detail rather than cosmetic: the grant is file-read* + file-test-existence + file-map-executable (macos-seatbelt.ts, buildRuntimeRootsPolicy). The signature check is the only thing that makes it acceptable to hand a sandboxed child that grant for a directory derived from an environment variable. The remaining structural guard is a filename heuristic — basename(developerRoot) === 'CommandLineTools' (line 66) or …/Xcode.app/Contents/Developer (line 68) — and $HOME is the only location excluded (lines 57–58). DEVELOPER_DIR is inherited from the host's shell environment, so anything able to set it (shell rc, a plugin-supplied shellEnvironment, user typo) plus write access to a world-writable path can name a directory that satisfies every remaining check once ad-hoc signed.
Adding -R="anchor apple" to the codesign argv closes this at no measurable cost (verify is ~14 ms). I have no strong preference between codesign -R and spctl --assess; the important part is that the signer is pinned.
Related: the test at macos-command-paths.test.ts:150 is named "rejects a structurally plausible toolchain whose libxcrun is not Apple-signed", but the fixture writes the literal text not signed, so it only proves that a non-Mach-O file is rejected. Ad-hoc signing needs no certificate and no keychain, so it is CI-friendly — making the fixture an actually-ad-hoc-signed dylib would let the test name be true.
Nits
-
Discovery runs — and blocks the event loop — even when no sandbox will be applied. In
builtin-tools.tssandboxCommand,macosPathsis computed after thecommandSandboxUnavailableearly return. For an unrestricted/full-access profile under the defaultautopreference,shouldSandboxis false, soselectInitialreturnssandboxType: 'none'andcanEnforcethen returnstrue(sandbox-manager.ts), meaning we do reach the resolver, spawn up to two subprocesses synchronously, andtransformreturnsnoneand discardsexecutableRootsentirely. Memoizing the canonical root for the process lifetime (the PR already accepts the post-validation TOCTOU race, so caching doesn't weaken that) or gating onprofileRequiresSandboxwould remove the waste. Measured ~14 ms forcodesignplus spawn overhead, ×2 sequential spawns, uncached, per Bash invocation. -
The timeout bounds aren't testable and aren't surfaced.
XCODE_SELECT_TIMEOUT_MS/CODESIGN_TIMEOUT_MSandreadSelectedDeveloperDirectory/validateAppleBinaryare module-private; onlyselectDeveloperDirandvalidateAppleBinaryare injectable through options. So the "bounded" half of the title has no coverage in the focused suite — an injectable spawner (or exported timeout) would fix that. Separately, a timeout and "no toolchain found" both silently return[]with no diagnostic, so a user who hits the 1 s limit sees a bare Git failure with nothing pointing at the cause. -
The
buildRuntimeRootsPolicychange reaches beyond the Apple toolchain. The diff also wraps theRUNTIME_READABLE_ROOT_*clauses indeniedRootRequirements. Those roots come from the filesystem worker's Mach-O dependency walk (filesystem-worker/macos-executable-dependencies.ts,filesystem-worker/launch-spec.ts), not from the toolchain. The tightening is right in spirit — explicit denies should win — and I could not find an existing test asserting the old deny-exempt behavior. But there's no new test for this half, and a profile that denies a directory holding the worker's dylibs now losesfile-read*there and can fail to launch the worker. Worth a line in the Summary (it's a separate behavior change from the Git fix) and a focused case inmacos-seatbelt.test.ts. -
Smoke assertion compares canonical output to a non-canonical path. In the new "starts Apple Git…" smoke test,
assert.deepEqual(runtimePaths.executableRoots, [join(selectedDeveloperDirectory, 'usr', 'lib')]), while the resolver returnsrealpathSync(...). It holds here (xcode-select -p→/Library/Developer/CommandLineTools, already canonical — I checked), butrealpathSyncwould match the resolver's contract exactly. AlsocanRunAppleCltgates on the path ending in/CommandLineTools, so Xcode-only machines get no coverage of this PR's path at all. -
Sandbox README conventions.
packages/runtime/src/sandbox/README.mdenumerates every module under "Ownership" and every test file under "Verification". The newmacos-command-paths.tsandmacos-command-paths.test.tsappear in neither, and the new Apple-toolchain allowance is a user-visible "Current behavior" change that deserves a bullet.
Verified OK
- Platform gating —
macosPathsis only computed underplatform === 'darwin'; the Linux and Windows path contexts are untouched, and the new module is not imported into platform-neutral code. macosRuntimeExecutableRootspreserved — the new roots are appended rather than replacing them, so the pre-existing-DEXECUTABLE_ROOT_nassertion inbuiltin-tools.test.tsstill holds and an empty resolver result no longer drops the runtime roots.- Read-only profiles get no grant (
macos-command-paths.ts:85), and the test correctly assertsvalidateAppleBinaryis not even invoked. - Canonicalization —
realpathSyncbefore the layout checks makes the "alias replaced after resolution" test meaningful, and both/and$HOMEare excluded up front. - No env-precedence regression at the
background_commandcall site — the Seatbelt backend returnscommand.envverbatim andoptions.shellEnvironmentis still applied last by the caller, so threading it intosandboxCommandis additive. (I checked the 6-argumentrunSeatbeltCommandcall in the smoke test against the extended helper signature; the argument order is correct.) - ASF headers present on both new files, and every import is used.
Comment-only review, no approval or change request.
Generated-by: Codex Signed-off-by: Dante <duanjl.china@gmail.com>
|
Fixed the signer check in d7ceeb1: I also gated discovery on actual sandbox selection, extended the deny-composition case to runtime-readable roots, documented the filesystem-worker dependency impact, and made the Apple Git smoke canonicalize its expected paths and accept both selected CLT and Xcode layouts. The sandbox README now lists the resolver/tests and the allowance contract. I kept validation adjacent to each sandbox launch instead of caching it for the process lifetime. The one-second subprocess limits and fail-closed behavior are unchanged and now documented. I have left timeout-specific diagnostics and a new subprocess injection interface out of this correction; the focused suite does not claim timeout fault-injection coverage. Validation: 101 focused tests passed, including the real ad-hoc regression; runtime build, lint, format, and both knip checks passed. Root build/typecheck still fail in CI then caught the generated Windows skip inventory missing the new macOS-only suite (failed job). I regenerated it in de5757c; Merged current main in AI-generated with Codex. Follow-up in |
Generated-by: Codex Signed-off-by: Dante <duanjl.china@gmail.com>
Generated-by: Codex
Generated-by: Codex Signed-off-by: Dante <duanjl.china@gmail.com>
Generated-by: Codex Signed-off-by: Dante <duanjl.china@gmail.com>
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed this exact head. The 9-file change bounds Apple toolchain discovery and pins the libxcrun.dylib signer with codesign -R=anchor apple (macos-command-paths.ts:57-99,120-140), gates resolution on an active Darwin sandbox (builtin-tools.ts:839-865), and preserves explicit-deny precedence for runtime/executable roots (macos-seatbelt.ts:492-511). I found no substantiated P0–P3 issue in the inspected paths. Node 24 npm ci, build:test, 90 focused tests, and current-head CI test/package/Windows checks pass. The PR currently conflicts with main in docs/windows-test-inventory.md, so it cannot merge as-is. I could not run real macOS codesign, Seatbelt, or Apple Git startup on this Linux host; please validate both CLT/Xcode configurations on a Mac after resolving the conflict. Directory replacement after path validation is a documented residual TOCTOU limitation.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
Generated-by: Codex
Generated-by: Codex
|
I merged the current On this macOS host, the compiled resolver accepts the actual selected Command Line Tools path and the installed Xcode path, returning their narrow runtime roots; AI-generated with Codex. |
|
Thanks for the update! Some checks are falling, could we take a quick look? |
Generated-by: Codex
|
Thanks for flagging the checks. I traced both failures on The The Windows sandbox job has a separate failure: the broker launches a child, but Node cannot read the repository |
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed exact head 24fd9fc1112e1b5caa67bdd0b7acce810bfcdffb. This PR still changes nine files: it resolves only canonical Apple CLT/Xcode library roots and verifies the libxcrun.dylib Apple signer with bounded probes (packages/runtime/src/sandbox/macos-command-paths.ts:58-99,120-140), adds those roots only for an active Darwin command sandbox (packages/runtime/src/builtin-tools.ts:834-867), and keeps explicit path denies effective for runtime/executable roots (packages/runtime/src/sandbox/macos-seatbelt.ts:491-513). The commits since the previous review sync current main and refresh the Windows test inventory; they do not change those Apple-path or Seatbelt implementations. I found no substantiated P0–P3 issue in the inspected PR paths.
Node 24 clean install, build:test, 90 focused tests, diff check, and a merge-tree against current main pass. Current-head test, package, and windows_recovery checks pass, and the PR is mergeable. windows_sandbox_w0_protocol is red: its worker cannot read the root package.json inside the Windows AppContainer; the same error also appears on main's run 36226849517, so I cannot attribute it to these macOS-only changes. The Windows gate still needs resolution before merge. I could not run macOS codesign, Seatbelt, or real CLT/Xcode Git startup on this Linux host; those platform paths still need Mac validation. Directory replacement after validation remains a documented TOCTOU limitation.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed at this head by three independent AI reviewers (different model families), with a focus on what the sandbox now allows. The mechanism is sound; all three found the same containment gap (inline). Because this is sandbox policy and the fix is one check, we suggest addressing it before merging.
What checks out: the resolver only runs for an active Darwin command sandbox and adds nothing for read-only profiles; DEVELOPER_DIR must be absolute, canonical, not /, not inside $HOME, and shaped like CommandLineTools or <…>/Contents/Developer; libxcrun.dylib must be a regular file inside the library root after canonicalisation and pass codesign --verify --strict -R=anchor apple (the ad-hoc-signed and non-Mach-O tests show this check is load-bearing); both discovery subprocesses are bounded to one second and fail closed; explicit path denies now also subtract from runtime/executable grants. Runtime tests pass (3553 in one reviewer's full run; 90/90 focused).
On reachability: the gap is not directly reachable by the model or plugins today. Sandboxed commands cannot change the Host environment, plugin shells cannot pass environment, and plugin env keys are restricted to MAKA_PLUGIN_*, so DEVELOPER_DIR comes only from the user's Host process. It becomes more serious if a new env source is ever added.
Other notes:
- Git still fails under read-only (Explore) profiles; the positive smoke uses an empty
HOME, which hides the~/.gitconfigfailure reported in #5260. - Every sandboxed command now runs two blocking discovery subprocesses (up to 1 s each), uncached, and a timeout produces no diagnostic.
- No test has yet shown Apple Git actually starting inside Seatbelt; the Git-config Bash test also passes on the old code.
- The remaining TOCTOU window between validation and child start is documented as a known limit.
windows_sandbox_w0_protocol is red on this head, as on main.
Automated review notice: This review was posted by an automated review agent operated by Astro-Han. It combines independent reviews from several AI models. It is not an independent human review and does not replace one.
| } | ||
|
|
||
| const contentsRoot = dirname(developerRoot); | ||
| const sharedFrameworks = join(contentsRoot, 'SharedFrameworks'); |
There was a problem hiding this comment.
P3 (sandbox boundary; please fix before merge) — SharedFrameworks is checked with statSync (follows symlinks) and returned via realpathSync without the containment check applied to libxcrun.dylib. If Contents/SharedFrameworks is a symlink, its target outside the toolchain is returned and macos-seatbelt.ts:503-509 grants it file-read*, file-test-existence and file-map-executable; with the signer stubbed, a symlink to / yielded an executable root of /. The signature check covers only libxcrun.dylib, not this directory. Suggest requiring isPathWithin(realpathSync(sharedFrameworks), contentsRoot) (and the same for the library root), plus a symlink-escape regression test.
There was a problem hiding this comment.
Fixed in ab11ad1ba (now on PR head 9f8d3d71c). Both returned roots are canonicalized and checked against their containing developer directory or Xcode Contents directory. A SharedFrameworks symlink to / and a CLT usr/lib symlink outside the selected developer directory now return no grant. The two new regressions pass; the focused resolver/Seatbelt policy suite passed 31/31, and the full build, lint, format, and Windows inventory checks passed after merging current main. The existing canonical-directory replacement race remains the limitation documented in the PR.
me2seeks
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent operated by me2seeks make. It is not an independent human review and does not replace one.
Summary
Sandboxes macOS Apple Git by canonicalizing DEVELOPER_DIR/xcode-select, codesign-verifying libxcrun.dylib (Apple anchor), granting only toolchain library roots, making explicit denies carve into executable/runtime-readable roots, and dropping host-side Git config discovery. The issue (Seatbelt blocking libxcrun → Git can't start, #5260) is real, and deny-propagation plus read-only exclusion are correct hardening.
Findings
- [P2]
packages/runtime/src/builtin-tools.ts:838-846+sandbox/macos-command-paths.ts:132-153— discovery runs on the operation hot path: every sandboxed macOS Bash call performs up to twospawnSyncsubprocesses (xcode-select,codesign) with no caching, blocking the runtime event loop (tens-to-hundreds of ms typical, up to the 1s timeouts each). The repo's own invariant says spawnSync "must never run on the operation hot path" (default-sandbox-manager.ts:107, cached hot path at :150); Windows/Linux probes are composition-time and cached. Per-call revalidation doesn't even close the documented TOCTOU (policy construction and child spawn remain separate), so a(developerDir, signature)-keyed cache keeps the same security posture without per-command stalls. - [P3]
macos-command-paths.ts:55-102— onlylibxcrun.dylibis signature-checked; a copied (still validly Apple-signed) dylib in an attacker-writable fakeCommandLineToolslayout passes, and the wholeusr/lib(+SharedFrameworks) tree then receivesfile-map-executable. Reachable only via session/host env control ofDEVELOPER_DIR, but worth a documented assumption since workspace-adjacent env config would cross it. - [P2]
windows_sandbox_w0_protocolfails on head (run 36263504448). The change is darwin-scoped but touches sharedbuiltin-tools.ts; confirm unrelated/flake before merge.
Verdict
needs-changes — move toolchain verification off the per-command hot path (or cache it) and triage the red Windows sandbox lane.
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed exact head 9f8d3d71cc5d39e2713a2763e8a2c24d99b98031, including the new Apple toolchain containment change and its two regression tests. I found no substantiated P0–P3 issue in the PR delta. macos-command-paths.ts:77-98 now requires both the canonical library and SharedFrameworks directories to remain inside the selected developer/Xcode bundle; the new tests exercise escapes through each symlink. The previous Apple Git permission and explicit-deny paths remain unchanged by this follow-up.
Node 24 builds of core/storage/runtime and 30 focused macOS-path/Seatbelt tests pass. Hosted test, package, and windows_recovery pass; diff-check and a merge-tree against current main are clean. windows_sandbox_w0_protocol is still failing, so this is not a green-check or merge-readiness claim. I have not run a real macOS CLT/Xcode/Seatbelt startup or a Windows sandbox run locally.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
Generated-by: Codex (gpt-6.1-sol)
Use bounded asynchronous probes while retaining per-launch canonicalization and Apple-signature checks. Document the trusted host toolchain assumption. Generated-by: Codex
|
Addressed the blocking probe concern in the review in The sandbox README also states the trust assumption: Validation: 48 resolver, Seatbelt-policy, and shell tests pass, including the real ad-hoc signer rejection; the builtin Bash tests also pass. Repository lint, format, and both Desktop/UI knip checks pass. Root build/typecheck fail in the DeepSeek web-search codec against the installed OpenResponses API, with subsequent typecheck errors from unbuilt downstream declarations; the changed runtime files compile to updated test output without reported errors. The existing Seatbelt smoke still has six positive/startup failures because this host rejects AI-generated with Codex. |
hqhq1025
left a comment
There was a problem hiding this comment.
Reviewed 22af4d5c4ca9408108b7868e559bcff5360f56dd against current main c838e1fa779a48832f08134547576a0471033187. No substantiated P0–P3 finding in the code and tests checked.
This PR adds narrowly scoped Apple toolchain library grants for writable sandboxed macOS commands, without discovering Git configuration grants. The latest increment changes toolchain selection and signature verification to asynchronous execFile probes, each configured with a one-second timeout and a 64 KiB output limit (packages/runtime/src/sandbox/macos-command-paths.ts:120, :146). Failed probes return no additional roots. Both managed shell launch and executor Bash await the transformed command (packages/runtime/src/shell-tools.ts:293, packages/runtime/src/builtin-tools.ts:230, :690), so policy construction does not consume an unresolved verification result.
I independently checked canonical CLT/Xcode library containment and symlink rejection (packages/runtime/src/sandbox/macos-command-paths.ts:77), the read-only early return (:108), and explicit denied-root requirements on executable/runtime-readable grants (packages/runtime/src/sandbox/macos-seatbelt.ts:491). No storage migration or protocol epoch change is introduced. The diff and a synthetic merge with current main are clean.
The trust assumption is material: DEVELOPER_DIR comes from Host-side configuration, and verifying libxcrun.dylib does not authenticate every file in the granted library directories. The selected installation must be trusted. Canonicalization does not pin those directories against replacement before launch; the updated sandbox documentation states this residual limitation (packages/runtime/src/sandbox/README.md:59).
Validation on Linux with Node 24.18.1: clean npm ci, core/storage/mcp/runtime builds, command-path/Seatbelt/Bash tests 96/96, adjacent shell-tools tests 17/17, and git diff --check pass. The current-head hosted test, package, and windows_recovery checks pass. windows_sandbox_w0_protocol remains red: run 36775701766 shows the Windows filesystem worker cannot read the repository package configuration (ERR_INVALID_PACKAGE_CONFIG, operation not permitted). I did not reproduce this Windows failure or establish its root cause, and do not treat the passing checks as merge readiness while it remains unresolved.
I did not execute actual macOS Apple Git, native signature verification or Seatbelt enforcement, nor validate the filesystem replacement race. Those platform-specific behaviors remain outside the dynamically verified scope. Existing reviews are attached to older commits; this conclusion is limited to the head above.
Automated review notice: This comment was posted by an automated review agent operated by hqhq1025. It is not an independent human review and does not replace one.
Resolve the generated inventory counts from the merged source, preserving the Apple Git platform-contract entry and current upstream declarations. Generated-by: Codex
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Review of exact head 5b15444d. The previous review head was 22af4d5c. The only commit since then is a merge from main (5b15444d, main at 7c90bac2). The PR's own diff against its merge-base is byte-identical to the one at 22af4d5c. The only exception is docs/windows-test-inventory.md, which shifted counts by one (52->53 portable-candidate, 120->121 total) because of the new main baseline. None of the merged main commits touch builtin-tools.ts, shell-tools.ts, or sandbox/.
Earlier findings: none of them is open as a code defect at this head.
- Astro-Han's signer finding (
codesign --verifyaccepted ad-hoc signatures) is fixed:-R=anchor appleis atmacos-command-paths.ts:137. - Astro-Han's
SharedFrameworksandusr/libsymlink-escape finding is fixed with containment checks at:78,:80and:97. - me2seeks's P2 (blocking
spawnSyncon the hot path) is fixed: asyncexecFileis at:146-159, and both Bash paths now await it (builtin-tools.ts:229-230,690,shell-tools.ts:293). Each command still runs the probes uncached, which the author chose on purpose. - me2seeks's P2 on CI:
windows_sandbox_w0_protocolis still red, but it fails with the sameERR_INVALID_PACKAGE_CONFIG ... operation not permittedas main's scheduled run 37106919146. It is pre-existing, not from this PR. - The trust assumption and the TOCTOU limit are documented (
sandbox/README.md:56-59). - Still not delivered: any end-to-end proof that Apple Git starts under Seatbelt (likun666661 #1, Astro-Han). See P2 below.
New findings
- P2, unverified core flow; the grant set looks incomplete for Apple Git itself. See the inline comment on
macos-command-paths.ts:89. - P3: the timeout is bounded only if the child honours SIGTERM, and probes ignore command abort. Inline at
:151. - P3:
DEVELOPER_DIRset to anXcode.appbundle root fails closed. Inline at:91. - P3, carried over: a probe timeout and "no toolchain" both return
[]silently. A user who hits the 1s limit sees a bare Git failure with nothing in the logs.
Verified OK: Non-macOS paths are unaffected: macosPaths is undefined unless platform === 'darwin' (builtin-tools.ts:841-846), and Linux and Windows path contexts are unchanged. Read-only and unsandboxed profiles do no discovery (macos-command-paths.ts:108, builtin-tools.ts:842). The resolver cannot throw: every fs call is in try/catch and the runner always resolves. So the already-prepared onCompletion (builtin-tools.ts:837) is not orphaned by the new await. On timeout or a maxBuffer overrun, execFile kills the child and returns status: null, which fails closed. Explicit denies now also apply to runtime and executable roots (macos-seatbelt.ts:494-509).
Validation (Linux, Node 22.23.2): clean npm ci, then core/storage/mcp/runtime builds pass. The resolver, Seatbelt policy, Seatbelt smoke and shell-tools tests pass 47/47; the smoke and macOS-only cases are skipped because this is not Darwin. builtin-tools.test.js has 63/66 passing; the 3 failures are only RipgrepUnavailableError (no rg on this host) and are unrelated. git diff --check is clean. Hosted test, package and windows_recovery pass. The PR is mergeable, but merge is BLOCKED because review is required. CI has no macOS runner, so none of the Darwin-only tests run in CI. I did not run real codesign, Seatbelt, or CLT/Xcode Git.
| return []; | ||
| } | ||
|
|
||
| if (basename(developerRoot) === 'CommandLineTools') return [libraryRoot]; |
There was a problem hiding this comment.
P2 (medium confidence, unverified on macOS). The only new grant is file-read*/file-map-executable on <developer>/usr/lib (plus SharedFrameworks for Xcode). That is enough for the /usr/bin/git shim to load libxcrun.dylib. But xcrun then execs <developer>/usr/bin/git. Under workspace-write, Seatbelt reads are limited to readable roots (macos-seatbelt.ts:463-480), plus system defaults (/usr, /System, /bin, /etc, ...; :41-47, :51-61). Nothing covers <developer>/usr/bin, usr/libexec/git-core (helpers), or usr/share/git-core. Apple Git's system config (.../usr/share/git-core/gitconfig, which sets credential.helper=osxkeychain) and the git init templates live there. Git treats an unreadable config file as fatal (unknown error occurred while reading the configuration files), so git status could still fail one step after this fix. CI has no macOS runner, and every reported local smoke run hit sandbox_apply exit 71. So starts Apple Git with the selected Apple toolchain (macos-seatbelt-smoke.test.ts:160) has never passed anywhere. Please run that smoke on a real Mac, not inside another sandbox, for both CLT and Xcode. If it fails, add read-only (not file-map-executable) roots for usr/bin, usr/libexec/git-core and usr/share/git-core. If it passes, please post the output so the PR's core claim is backed by evidence.
There was a problem hiding this comment.
The core Seatbelt acceptance claim is still unverified. In 5e9975234 the existing smoke test now honors DEVELOPER_DIR, so it can select CLT and Xcode separately. I reran node --test --test-name-pattern='starts Apple Git' packages/runtime/dist/__tests__/macos-seatbelt-smoke.test.js with /Library/Developer/CommandLineTools and /Applications/Xcode.app/Contents/Developer. Both reached the sandboxed Git invocation and failed with sandbox-exec: sandbox_apply: Operation not permitted (exit 71). That is the outer sandbox rejecting Seatbelt application; it supplies no result about Git's binary/helpers/system config inside this policy.
I have not added the proposed read-only roots or marked this finding resolved. Both toolchains still need this smoke run on a Mac process permitted to apply Seatbelt; its result should determine the smallest additional read grants. The timeout/cancellation and bundle-selection fixes are delivered separately above.
Automated follow-up from Codex.
| execFile( | ||
| executable, | ||
| [...args], | ||
| { encoding: 'utf8', timeout: options.timeout, maxBuffer: 64 * 1024 }, |
There was a problem hiding this comment.
P3. execFile's timeout sends killSignal (default SIGTERM), and the callback only fires on close. So the 1s bound holds only if the child exits on SIGTERM. Pass killSignal: 'SIGKILL' to make the bound unconditional. Two smaller points: the probes don't receive the tool's ctx.abortSignal, so cancelling a command still waits up to about 2s. The runner's stdio option is accepted by the type (:41) but ignored here, so codesign's stderr is buffered into maxBuffer rather than discarded. That is harmless, but the contract is misleading.
There was a problem hiding this comment.
Fixed in 5e9975234. Discovery now uses spawn with killSignal: 'SIGKILL' for both one-second timeouts and receives the command's ctx.abortSignal. The declared stdio is now actually applied: codesign output and both probes' stderr are discarded, while xcode-select stdout remains capped at 64 KiB. The existing probe test now checks SIGKILL and abort-signal propagation. The 65 focused resolver/Seatbelt/manager tests, two macOS Bash wiring tests, root build/typecheck/lint/format and desktop/UI knip passed.
Automated follow-up from Codex.
|
|
||
| if (basename(developerRoot) === 'CommandLineTools') return [libraryRoot]; | ||
|
|
||
| if (basename(developerRoot) !== 'Developer' || basename(dirname(developerRoot)) !== 'Contents') { |
There was a problem hiding this comment.
P3. A DEVELOPER_DIR that points at the bundle root (/Applications/Xcode.app, which xcode-select -s also accepts) fails at :77 because usr/lib is missing, so no roots are granted. It fails closed, so this is not a regression, but Git stays broken for that setup and nothing tells the user why. Consider normalising *.app to *.app/Contents/Developer before the layout checks, or at least logging the rejected path.
There was a problem hiding this comment.
Fixed in 5e9975234. A canonical *.app selection is normalized to Contents/Developer before the existing layout and Apple-signature checks. The normalized directory must remain inside the canonical bundle, so a symlink escape still fails closed. The existing Xcode layout test now exercises both the developer-directory and bundle-root forms and returns the same bounded library/SharedFrameworks roots.
Automated follow-up from Codex.
Generated-by: Codex
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incremental re-review of exact head 5e997523. The previous reviewed head was 5b15444d. The merge-base is unchanged (7c90bac2), and the increment is one commit, 5e997523 ("bound Apple toolchain probes and accept Xcode bundles"): +72/-27 across macos-command-paths.ts, builtin-tools.ts and two test files.
Status of the previous findings
- P2, Apple Git under Seatbelt never shown to start: still open. No real-Mac evidence has been provided, and the grants are unchanged. The only grants are still
<developer>/usr/lib, plusSharedFrameworksfor Xcode (macos-command-paths.ts:100,:109). There is still nothing for<developer>/usr/bin,usr/libexec/git-coreorusr/share/git-core. The author says so directly in the open thread. The smoke test now honoursDEVELOPER_DIR(macos-seatbelt-smoke.test.ts:42-43), which is useful. But both the CLT and the Xcode reruns again failed atsandbox-exec: sandbox_apply: Operation not permitted(exit 71), because they ran inside another sandbox. Sostarts Apple Git with the selected Apple toolchain(macos-seatbelt-smoke.test.ts:161) has still never passed anywhere, and the PR's core claim is still unverified. This is the remaining blocker: a run on a Mac outside any outer sandbox, for both toolchains, then either the passing output or the added read-only roots. - P3, SIGTERM-only timeout, abort ignored,
stdioignored: fixed. The runner now usesspawnwithkillSignal: 'SIGKILL'(:137,:152). It passes the declaredstdiothrough, and it receivesctx.abortSignal(builtin-tools.ts:844->macos-command-paths.ts:64-67). I ran the new runner body under Node 22.23.2. A child that traps SIGTERM was killed at about 1.0s and returnedstatus: null. An abort at 200ms returnedstatus: nullat about 200ms. A stdout overflow (more than 64 KiB) was killed and returnednull. A normalechoreturned status 0 with its stdout. Every failure path resolvesnull, so they all fail closed. - P3,
DEVELOPER_DIRpointing at anXcode.appbundle root: fixed. A*.apprealpath is normalised toContents/Developerand must stay inside the canonical bundle (:76-80). The existing layout, signature andContents/Developerchecks still run after it. The test now covers both forms. - P3, carried over: a probe timeout and "no toolchain" both still return
[]silently. There is still no log line when discovery fails closed, so a user whose probe hit the 1s limit or was rejected sees only a bare Git failure. This was not addressed in this round and is not blocking.
New findings in the increment: none. Minor note: the new abort test only checks that the signal is forwarded to an injected runner, not that the real runner aborts. My direct check above covers that.
Validation (Linux, Node 22.23.2): at 5e997523, clean npm ci, then core/storage/mcp/runtime builds pass. macos-command-paths, macos-seatbelt, macos-seatbelt-smoke and shell-tools tests pass 47/47; the Darwin-only cases are no-ops on this host. CI: test, package and windows_recovery pass. windows_sandbox_w0_protocol fails with the same ERR_INVALID_PACKAGE_CONFIG ... operation not permitted that main already has, so it is pre-existing and not caused by this PR. The PR is mergeable; merge is BLOCKED because review is required. CI still has no macOS runner.
Generated-by: Codex
|
Addressed the carried-over silent-discovery P3 in Root build, lint, format, typecheck, desktop/UI knip and 44 focused resolver/Seatbelt/manager tests passed. The P2 real CLT/Xcode Seatbelt acceptance remains open, as documented in the existing thread. Automated follow-up from Codex. |
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incremental re-review of exact head 57229ba1. The previous reviewed head was 5e997523. The merge-base is unchanged (7c90bac2), so the increment is exactly one commit, 57229ba1 ("explain failed Apple toolchain discovery"): +76/-12 across macos-command-paths.ts and its test.
Status of the previous findings
- P2, Apple Git under Seatbelt never shown to start: still open, and still the blocker. The grants are unchanged:
<developer>/usr/lib(macos-command-paths.ts:108, returned at:124), plusSharedFrameworksfor Xcode (:131,:135). There is still nothing for<developer>/usr/bin,usr/libexec/git-coreorusr/share/git-core. The author's latest comment (2026-10-04 01:37) says so directly: "Discovery still fails closed, with unchanged grants … The P2 real CLT/Xcode Seatbelt acceptance remains open." No new smoke output has been posted. The only reruns are still the twosandbox_apply: Operation not permitted(exit 71) runs from inside an outer sandbox, sostarts Apple Git with the selected Apple toolchain(macos-seatbelt-smoke.test.ts:161) has still never passed anywhere. Needed: a smoke run on a Mac outside any outer sandbox, for both CLT and Xcode, and then either the passing output or the read-only roots that run shows are missing. - P3, carried over: silent
[]when discovery fails: fixed. Every fail-closed return after the abort check now goes throughfail(reason)(:66-73), which defaults toconsole.warn('[sandbox:macos] Apple toolchain discovery failed closed: <reason>. No additional executable roots granted.'). A probe timeout ("xcode-select probe interrupted or timed out") is now told apart from a non-zero exit ("xcode-select probe failed") (:167-175). Each validation step has its own reason. No reason string includes a path, and cancellation stays silent (:65,:67). The new test covers the timeout, no-directory and pre-aborted cases (macos-command-paths.test.ts:234-264).
New findings in the increment
- P3: the warning is not deduplicated.
resolveMacosCommandPathsruns on every sandboxed shell command on darwin (builtin-tools.ts:844), and nothing caches the result. On a Mac without Command Line Tools, every sandboxed command, includingls, will log "xcode-select probe failed". Suggest warning once per reason per process, or caching the negative result briefly. - P3, test gap: the signature-failure reason (
:121) and the layout and containment reasons are not covered by tests.
CI and merge state: test, package and windows_recovery pass. windows_sandbox_w0_protocol fails with the same ERR_INVALID_PACKAGE_CONFIG that main already has, so it is pre-existing and not caused by this PR. The PR is MERGEABLE; merge is BLOCKED because review is required. CI still has no macOS runner.
Verdict: this round's change is good. The PR stays blocked only on the P2 real-Mac evidence or grant widening.
Generated-by: Codex
|
Addressed the two P3 findings in review 5404054274 at Root build, typecheck, lint, format, desktop/UI knip and all 33 focused resolver/Seatbelt tests passed. The P2 remains open: real Apple Git startup with both CLT and Xcode outside an outer sandbox has not been verified, so executable/read grants are unchanged. Automated follow-up from Codex. |
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Incremental re-review of exact head 213c4ad7. The previous reviewed head was 57229ba1. The merge-base is unchanged (7c90bac2), so the increment is exactly one commit, 213c4ad7 ("deduplicate Apple toolchain diagnostics"): +104/-1 across macos-command-paths.ts and its test.
Status of the previous findings
- P2, Apple Git under Seatbelt never shown to start: still open, and still the only blocker. The grants are unchanged: CLT gets
<developer>/usr/libonly (macos-command-paths.ts:138), and Xcode getsusr/libplusSharedFrameworks(:149). There is still nothing for<developer>/usr/bin,usr/libexec/git-coreorusr/share/git-core. The author's comment (2026-10-04 04:35) confirms this: "real Apple Git startup with both CLT and Xcode outside an outer sandbox has not been verified, so executable/read grants are unchanged." No smoke output has been posted.starts Apple Git with the selected Apple toolchain(macos-seatbelt-smoke.test.ts:161) has still never passed anywhere. What would close this: a run on a Mac outside any outer sandbox, for both CLT and Xcode, and then either the passing output or the minimal read-only roots that run shows are missing. Those roots should stay inside the already Apple-signed, containment-checked developer root. - P3, warning not deduplicated: fixed. The default sink goes through
reportDiscoveryFailure(:30-42). It warns at most once per reason per process, with a bounded set of 16 entries. Reasons are a small fixed set with no paths, so the bound never churns. Discovery andcodesignverification still run for every command, and no grant is cached, so a changed toolchain is never served from a stale result. InjectedonDiscoveryFailuresinks still receive every attempt. - P3, test gap for the signature, layout and containment reasons: fixed. A new test covers the signature failure, an unsupported layout and an escaping
libxcrunsymlink. A second test covers repeated warnings, different reasons and per-attempt injected sinks.
New findings: none at P0-P3. One optional nit: the dedup set is module-global and has no reset hook for tests, so the new dedup test depends on no earlier test in the file having emitted "xcode-select probe failed" through the default sink. It passes today. A small resetMacosDiscoveryDiagnosticsForTest() export, or a unique reason in that test, would remove the ordering dependency.
CI and merge state: test, package and windows_recovery pass. windows_sandbox_w0_protocol fails, as it does on main, so the failure is pre-existing and not caused by this PR. The PR is MERGEABLE; merge is BLOCKED because review is required. CI still has no macOS runner. I reviewed the code only this round; the change is diagnostics-only.
Verdict: this round's change is good and both P3s are closed. The PR stays blocked only on the P2: real-Mac CLT and Xcode evidence, or a justified, contained widening of the grants.
Summary
DEVELOPER_DIRorxcode-selectresult throughrealpath, require a valid Apple signature and CLT/Xcode layout, and grant only its runtime library directories for writable command profiles$HOME, arbitrary PATH prefixes, and network access entirely under the child process's existing permission profile; the host no longer invokes Git to discover or authorize config filesusr/libandSharedFrameworksroots to stay inside the selected developer directory and Xcode bundle, respectively, before granting them to Seatbelt; symlink escapes fail closedThe selector path is canonicalized and
libxcrun.dylibis signature-checked immediately before Seatbelt policy construction. The canonical path is not fd-pinned, so replacement of the canonical toolchain directory between validation and process startup remains a residual limitation; this change does not claim to eliminate that race.This builds on hqhq1025's investigation in #4279 and the security/compatibility constraints identified in that review.
Refs #5260
Verification
Validated locally on
9f8d3d71cafter mergingmainatc64680be5:npm run buildnpm run lintandnpm run format:checknpm run windows:inventory(3 tests; 119 declarations)The Windows sandbox W0 job on the prior head failed when Node could not read
package.jsoninside AppContainer; the scheduledmainjob failed with the same package-config error. I reported both logs to the Windows maintainer in this comment. The updated head awaits remote checks.The managed host blocks nested Seatbelt at
sandbox_apply(exit 71), including the repository's pre-existing positive smoke cases, so no end-to-end Xcode/CLT Seatbelt pass is claimed.AI use
Select exactly one:
Tool(s) and scope: Codex implemented the sandbox path resolver, policy changes, and regression tests.
Checklist
Does this PR entail a change in behavior?