You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Final lane D step from #28: the full real-Docker and hostile-input gate for the agent isolation boundary (T9 Docker portion). Lanes F and G wait on this before running agents in production.
Main finding: several isolation checks could not fail
The in-container probes asserted forbidden actions with ! command under set -eu. POSIX shells ignore a failing negated command under set -e, so the probe carried on and printed its success marker even when the forbidden action worked. Reproduced on main: with /work/.git writable, the metadata probe created /work/.git/forbidden and still reported metadata-safe. The read-only-isolation and capacity probes had the same defect.
Every negated check now goes through a deny helper, which exits non-zero and names the breach.
What this adds
Gate self-test (test/agent-gate.test.ts): each negative production probe runs in a container missing exactly one protection and must report that breach: writable Git metadata (two probes), a writable worktree in a read-only phase (two probes), an unbounded task filesystem, unbounded scratch, and secret content in the worktree. The previous probes passed 3 of these 3 broken setups.
New probes, run under real profiles:
scratch-capacity: byte and inode ceilings on /tmp, HOME, CODEX_HOME and the output directory, for Codex and Claude; the deferred-output control directory is not writable.
metadata-alias: hard links from .git/config and objects into /work, /tmp and HOME, writes through symlink aliases, truncation, rm -rf and mv of .git, with a digest of the metadata compared before and after, in planning, review and execute.
hostile-repo: repository symlinks pointing at host files arrive as links, never as their targets, and no host secret is reachable.
Fail closed: an oversized repository fails allocation without leaving volumes or containers.
Live vendor probes (opt-in): Codex and Claude read the mounted schema and must return its value, compared exactly, through the adapter's documented channel (Codex output file, Claude stdout envelope).
CI: the Agent isolation workflow runs the new suite; the main CI run excludes it with the other Docker suites.
Docs:docs/implementation/agent-isolation.md maps each T9 requirement to its tests and states the handoff contract for lanes F and G.
Validation
Full suite with real Docker, serial: 556/556 passed across 26 files, no Docker resources left afterwards. An earlier full run had 2 timing-sensitive supervisor failures (close after the monotonic deadline; READY delimiting). Both passed in isolation twice and in the full rerun; neither covers code changed here.
npm run typecheck passed.
The live vendor probes did not run: they need credentials (CODEBOOST_RUN_AUTH_PROBES=1, CODEBOOST_CODEX_AUTH_FILE, CLAUDE_CODE_OAUTH_TOKEN).
Not in this PR
T9 is complete only after lane F runs every suite in required CI (F6).
- Probe scripts asserted forbidden actions with `! command` under
`set -e`, which never fails, so the metadata, read-only isolation and
capacity probes reported success even when isolation broke. Replace
every negated check with a `deny` helper that exits and names the
breach.
- Add agent-gate breach self-tests: each negative production probe runs
in a container missing one protection and must report that breach.
- Add probes and real-profile tests for scratch byte and inode ceilings
(both vendors), metadata link/alias/truncation/replacement attempts
with an unchanged digest, and hostile repository symlinks; an oversized
repository fails closed without leaving storage.
- Live vendor probes read the mounted schema and return its value through
each adapter's documented channel.
- Run the gate in the Agent isolation workflow, keep it out of the
parallel main CI run, and document the T9 matrix and F/G handoff.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- scratch-capacity required only that fills fail, so an unwritable area
or a Codex container missing CODEX_HOME passed vacuously. Each fill now
must write before hitting its limit, and a Codex container must have
its Codex scratch areas.
- hostile-repo searched with grep -R, which follows symlinks, so a
hostile link to / or a loop could walk the whole container. Search
without following links and check the link targets separately.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on the two moderate issues in Copilot's review of 36cb59d (no inline findings), both fixed in a32e6a9:
Scratch validation:scratch-capacity accepted any failed fill as proof of a limit, so it could pass vacuously. A Codex container without CODEX_HOME skipped the Codex and output-directory checks entirely. Each fill must now write data before it stops (test -s), the file loop must create at least one file, and a container with CODEBOOST_VENDOR=codex must have CODEX_HOME. A new gate case runs the probe in a Codex container without Codex scratch: the previous probe reported scratch-bounded, and it now fails.
Symlink-following traversal:hostile-repo searched with grep -R, which follows symlinks, so a hostile link to / or a loop could make it walk the whole container. It now uses grep -r, which does not follow links inside directories, and checks the link targets separately. The hostile-repository test now includes a link to / and a self-loop, and must finish within 30 s.
Full suite with real Docker: 557/557, no resources left afterwards.
- The container-level Codex live probe now reads the mounted schema
instead of echoing a fixed marker, and the Claude container probe and
both live adapter probes compare the trimmed value exactly, with no
allowance for wrapping quotes or backticks.
- Add gate breach cases for an unbounded HOME and for a repository link
that resolves inside the container, so the hostile-repository link
target checks can fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on Copilot's review of a32e6a9, which reported "four unresolved moderate findings" in isolation-gate coverage and exact vendor-response validation but did not list them (no inline comments or details). I checked both areas and fixed four concrete gaps in 9bd4939:
Codex live container probe did not read the schema. It asked the agent to echo a fixed marker. It now asks for the mounted schema's probe value. Raw stdout can carry more than the final message, so the value must appear as a complete line; the adapter probe checks the exact output-file channel.
Live checks were not exact. The Claude container probe and both live adapter probes allowed one wrapping pair of quotes or backticks. They now require the trimmed value exactly.
Hostile-repository link-target checks had no breach case. A new gate case commits a repository link that resolves inside the container; the probe must report isolation breach: cat /work/escape.
An unbounded HOME had no breach case. A new gate case runs the scratch probe with an unbounded HOME; it must report the dd breach for HOME.
Full suite with real Docker: 559/559, no resources left afterwards. The live vendor probes need credentials and were not run.
If these are not the four findings the review meant, please list them and I will address them.
- Run scratch breach cases in a Codex environment with an unbounded
CODEX_HOME and an unbounded output directory, so removing or breaking
the Codex-only scratch checks fails the gate.
- Run the phase write-denial breach case in planning, questions and
review, and the metadata alias breach case in planning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on the summary of Copilot's review of 9bd4939 (in 5504b32):
Gate tests for read-only phases: the phase write-denial breach case now runs in planning, questions and review, and the metadata alias breach case also runs in planning, not only execute.
Hostile-link coverage: no change. Links in a checkout can only resolve inside the container. The gate already fails the hostile-repository probe for both failure modes: secret content reaching /work, and a repository link that resolves to a readable target. The real-profile test adds a link to / and a self-loop, and must finish within 30 s.
Full suite with real Docker: 564/564, no resources left afterwards.
deny suppressed only stderr, so a forbidden read that unexpectedly
succeeded (such as cat through a repository link) copied its data into
the invocation output before the breach was reported. Discard both
streams of the attempted command; the gate now requires empty stdout for
a resolvable repository link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🟡 Changes recommended
Unresolved isolation-test gaps include an absolute symlink credential escape and incomplete control-directory, inode, metadata-removal, fixture, and exact-output coverage.
Get a fresh assessment by requesting another Copilot review.
The seeder copies repository symlinks as links, so an absolute link such
as root-link -> / let a path-restricted agent tool read container files
outside the checkout, including process environments that hold vendor
credentials. prepareTaskFilesystems now refuses, before creating
storage, any link with an absolute target or one that leaves the
checkout lexically or through a chain of links (checked with the POSIX
native realpath; the JavaScript realpath cancels .. before following
links and misses chained escapes). Links inside the checkout, loops and
not-yet-existing targets still work.
The hostile-repo probe now checks every link in /work, and the gate adds
breach cases for absolute and escaping links, a writable control
directory, and inode-only limit removal for task and scratch areas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on the summary of Copilot's review of 49ef627 (in 36ed738):
Control-directory coverage: a new gate case makes the deferred-output control directory writable; the scratch probe must report touch /run/codeboost-control/forged.
Inode coverage: new gate cases keep the byte limit and remove only the inode limit, for /tmp and for the task filesystem; both probes must fail.
Metadata removal: covered already. With writable metadata, rm -rf /work/.git removes contents but fails on the mountpoint, and the probe then fails on its before/after metadata digest.
Exact output: probes now discard all output of any forbidden command they try (previous round), and the hostile-repository gate cases require empty stdout.
Full suite with real Docker: 572/572, no resources left afterwards.
- realpathSync.native reports ELOOP for an in-checkout cycle such as
a -> b, b -> a, and seeding rethrew it, refusing a repository whose
links all stay inside. A cycle never resolves, so treat it like a
missing target; direct escapes are still refused lexically.
- The hostile-repo probe resolves each link's target from the link's
directory rather than canonicalizing the link path, and treats an
unresolvable cycle as contained.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The metadata-alias probe exits at its first breach, so the gate only
proved detection of the first succeeding operation. For each documented
operation (hard links, alias writes, config overwrite and truncation,
removal and both moves), the gate now checks the production script
denies it and that its deny line reports it when the operation succeeds
against plain writable metadata.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on the summary of Copilot's review of f143be5 (no findings): "the metadata-alias probe stops at the first breach, leaving individual documented operations insufficiently covered."
Addressed in 816628e. For each of the nine documented operations, a new gate case does two things. It checks that the production metadata-alias script contains that exact deny line. It then runs that line alone, with the production deny helper, against plain writable Git metadata where the operation succeeds, and requires the matching isolation breach report. The nine operations are: hard links of .git/config and an object, appends through both aliases, overwriting and truncating .git/config, rm -rf, and mv to /work and to /tmp. Dropping or weakening any one of them now fails the gate.
Full suite with real Docker: 581/581, no resources left afterwards.
Add Codex tests for inode limits in vendor-specific directories
test/agent-gate.test.ts:129
These Codex cases make CODEX_HOME or /run/codeboost-output unbounded by bytes only. The inode-missing cases above run without CODEBOOST_VENDOR=codex, so the vendor-specific inode loops could be removed while the byte cases still fail at dd and this gate would remain green. Add Codex cases with a large byte limit and an overlarge inode limit for each vendor-specific area, passing codexEnvironment to runBroken.
- The link scan skipped .git, but the seeder copies it and it is mounted
at /work/.git, where the read-only mount stops writes but not reads
through a link. Git never needs links in its own metadata, so any
link there is now refused before allocation.
- Check the deadline and the entry limit before each directory entry is
queued, as the clone object audit does, so one huge directory cannot
defer either bound.
- Add Codex gate cases that remove only the inode limit of CODEX_HOME or
the output directory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on the "previously missed" item in Copilot's review of 816628e (in 48347d6):
Codex inode limits: two new gate cases run scratch-capacity in a Codex environment. Each keeps every byte limit and removes only the inode limit of one Codex area: CODEX_HOME or /run/codeboost-output. The probe must fail in both, so removing the Codex file-count loops now fails the gate.
Full suite with real Docker: 584/584, no resources left afterwards.
Link containment treated a link whose target was missing on the host as
harmless, so a chain such as deep/er/top -> ../.. plus
chained -> deep/er/top/../run/codeboost-auth/codex/auth.json passed:
the host lacks that path, but in the container /work/.. is / and the
credential mount exists. Replace the host realpath check with a resolver
that follows each existing link component by component from the link's
directory, applies missing components textually, and requires the path
to stay inside the checkout after every step. More than 40 hops is a
cycle, which cannot resolve and is contained.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live probes trimmed responses, so surrounding whitespace passed. They
now accept the exact value, optionally followed by the single trailing
newline a CLI adds; the Codex container probe requires the value as an
exact, untrimmed output line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow-up on the summary of Copilot's review of 894468d (no findings): "vendor probe checks trim responses and may accept forbidden surrounding formatting."
Addressed in dba3ecc. The live adapter probes and the Claude container probe no longer trim. They accept the exact schema value, optionally followed by the single trailing newline a CLI adds. The Codex container probe requires the value as an exact, untrimmed line of output. These probes need credentials and did not run here; typecheck passes.
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
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.
Final lane D step from #28: the full real-Docker and hostile-input gate for the agent isolation boundary (T9 Docker portion). Lanes F and G wait on this before running agents in production.
Main finding: several isolation checks could not fail
The in-container probes asserted forbidden actions with
! commandunderset -eu. POSIX shells ignore a failing negated command underset -e, so the probe carried on and printed its success marker even when the forbidden action worked. Reproduced onmain: with/work/.gitwritable, themetadataprobe created/work/.git/forbiddenand still reportedmetadata-safe. Theread-only-isolationandcapacityprobes had the same defect.Every negated check now goes through a
denyhelper, which exits non-zero and names the breach.What this adds
test/agent-gate.test.ts): each negative production probe runs in a container missing exactly one protection and must report that breach: writable Git metadata (two probes), a writable worktree in a read-only phase (two probes), an unbounded task filesystem, unbounded scratch, and secret content in the worktree. The previous probes passed 3 of these 3 broken setups.scratch-capacity: byte and inode ceilings on/tmp,HOME,CODEX_HOMEand the output directory, for Codex and Claude; the deferred-output control directory is not writable.metadata-alias: hard links from.git/configand objects into/work,/tmpandHOME, writes through symlink aliases, truncation,rm -rfandmvof.git, with a digest of the metadata compared before and after, in planning, review and execute.hostile-repo: repository symlinks pointing at host files arrive as links, never as their targets, and no host secret is reachable.docs/implementation/agent-isolation.mdmaps each T9 requirement to its tests and states the handoff contract for lanes F and G.Validation
npm run typecheckpassed.CODEBOOST_RUN_AUTH_PROBES=1,CODEBOOST_CODEX_AUTH_FILE,CLAUDE_CODE_OAUTH_TOKEN).Not in this PR
🤖 Generated with Claude Code