CH-009: confine robot file I/O to the data directory - #14
Merged
Conversation
Scopes M-004's fix at RobotData's single resolution point (re-root absolute/traversing paths, enforce the documented 200000-byte quota), closing FIO-001..003. Flags FIO-004's raw java.io scope as a blocking open question against the architecture record's explicit sandbox descoping. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GShGLuFwRpnWNY2WL5Vz5
Resolves CH-009's blocking open question: the bridge confines paths reached through getDataFile/getDataDirectory, not raw java.io calls that bypass it, matching the architecture record's existing sandbox scoping (threads/reflection/sockets already scoped out). FileAttack is not ported as FIO-004 evidence; FileOutputStreamAttack is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GShGLuFwRpnWNY2WL5Vz5
…rectory Closes FIO-001..003: - RobotData.getDataFile now matches classic's RobotFileSystemManager.getDataFile: asterisks stripped, ".." rejected with AccessControlException, and a java.io.File merge (not Path#resolve) so an absolute/root-relative name is re-rooted inside the data directory instead of overriding it. - RobotData tracks the classic 200000-byte quota, seeded from existing files at startup; RobocodeFileOutputStream.write charges it and raises classic's own IOException message on refusal, closing the stream. BotPeer's getDataQuotaAvailable() now reports real remaining quota instead of a hardcoded constant. - Fixed a double-resolution bug the conformance run surfaced: RobocodeFileOutputStream(String) was re-running an already-resolved File's path through the resolver a second time, corrupting it. It now opens the given path verbatim, matching classic's ThreadManager.createRobotFileStream. - FileRedirectionConformanceTest (FIO-001/002) and FileQuotaConformanceTest (FIO-003) prove this on both engines under ARCH-003's conformance tier. FIO-004 stays @draft (IDR-007, corrected from the proposal's initial guess): building and running FileOutputStreamAttack on both engines showed classic blocks it via a SecurityManager unconditional on path, even for a getDataFile -resolved (already-confined) target -- a mechanism JDK 24 removed and this bridge cannot reproduce. Neither of classic's own FIO-004 evidence robots can be ported as evidence under this design; the probe was removed rather than kept passing on a claim it can't support. Updates CAP-004 (status: active, provenance: verified), C-005 (enforcement: partial, Checked-by/Residual declared), and P-001's M-004/M-122 bookkeeping to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GShGLuFwRpnWNY2WL5Vz5
Regenerates the constraints index and corrects design.md's provenance (agent cannot self-promote it to verified; clue validate's ADR-035 activation gate is left correctly blocking on CAP-004's four high-cost inferred artifacts pending human review). Deletes the change workspace. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GShGLuFwRpnWNY2WL5Vz5
RobotData.getDataFile checked ".." against the raw filename before stripping asterisks, so a name like ".*." passed the check clean and then collapsed into ".." once stripped -- a real escape out of the robot's data directory (verified: three levels of ".*./" reaches outside the working tree entirely). Swapped the order: strip first, then check the sanitized string. Classic itself has this same ordering bug (AdvancedRobotProxy checks ".." before RobotFileSystemManager.getDataFile strips asterisks), so this is a deliberate deviation from byte-for-byte classic parity, justified by C-005's own safety rationale (untrusted jars, run unmodified) rather than a fidelity requirement -- no legitimate robot depends on encoding a traversal past an asterisk. Corrected IDR-007's and design.md's "byte-for-byte" framing to say so explicitly. Also narrows FIO-001's and C-005's wording: a true drive-letter- absolute name (C:\...) fails closed on both engines rather than being redirected, which is a different, previously-uncalled-out outcome from the root-relative case the new tests actually exercise. And notes design.md's quota-then-write ordering (matches classic; not worth a second code path to reverse). Adds RobotDataResolveTest as a unit-level regression guard for the resolver's check order, extracting RobotData.resolve(File, String) so the logic is testable without the data-directory singleton. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GShGLuFwRpnWNY2WL5Vz5
RobotDataResolveTest now follows the repo's test<CRIT-ID>_<Type><Direction> naming convention instead of a bespoke "Sanity" label, per an advisory from the scoped review of the traversal-fix commit. Cross-references it from criteria.md's FIO-001 evidence note alongside the conformance test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GShGLuFwRpnWNY2WL5Vz5
flemming-n-larsen
marked this pull request as ready for review
September 5, 2026 14:45
Human review (Flemming N. Larsen) re-read CAP-004/README.md, criteria.md, design.md, and C-005 against the shipped RobotData/ RobocodeFileOutputStream code. Found and fixed one carried-over inaccuracy from before the FIO-001 wording narrowed: README.md and RobotData's own Javadoc still said "absolute path" is re-rooted -- only a root-relative name is; a true drive-letter-absolute name fails the write on both engines instead, as design.md and C-005 already correctly stated. Corrected both, then promoted all four documents' provenance from inferred to verified, clearing clue validate's ADR-035 activation gate (now OK: CAP-004 covered/partial as declared, no remaining high-cost inferred blockers). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GShGLuFwRpnWNY2WL5Vz5
flemming-n-larsen
marked this pull request as draft
September 5, 2026 14:51
flemming-n-larsen
marked this pull request as ready for review
September 5, 2026 14:56
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.
Acceptance brief
Plan item: P-001 / M-004; remains wanted. This merge closes
FIO-001,FIO-002,FIO-003, leavesFIO-004@draft, recordsIDR-007, and promotesC-005toenforcement: partial. No unmerged base is authorized or included.Scenario resolution
FIO-001— verifies:FileRedirectionConformanceTestproves a root-relative name is redirected into the data directory and nothing is written at the named path, on both engines;RobotDataResolveTestadds unit coverage of the resolver's strip/traversal-check ordering.FIO-002— verifies:FileRedirectionConformanceTestproves a plain-name write throughgetDataFileappears ingetDataDirectory's listing, on both engines.FIO-003— verifies:FileQuotaConformanceTestproves the documented 200000-byte quota is reported, two chunks under it succeed, and the third is refused with classic's own message, on both engines.FIO-004— stays@draft. Not proposed as closed by this change:IDR-007records that classic's own evidence for it (FileAttack,FileOutputStreamAttack) depends on a JVMSecurityManagerunconditional on path — even agetDataFile-confined target is blocked in classic — a mechanism JDK 24 removed and this bridge cannot reproduce. Neither classic robot could be ported as evidence; the probe built to test this was removed after confirming the finding empirically on both engines.What merge binds
The merge binds the
RobotDataresolver (path re-rooting, quota tracking) and theRobocodeFileOutputStreamwrite path calling into it,CAP-004's promotion tostatus: activeandprovenance: verified,C-005's partial-enforcement declaration (alsoprovenance: verified), andP-001'sM-004/M-119–M-122bookkeeping (M-122dropped as a closeable evidence door). It changes no frozenrobocode.*signature and retires no criterion;FIO-004remains open rather than retired, since its wording still states a true (if currently unreachable) requirement.Human verification (Flemming N. Larsen): re-read
CAP-004's README/criteria/design andC-005against the shipped code and tests, found and fixed one carried-over inaccuracy (README.md andRobotData's Javadoc still said an "absolute path" gets re-rooted; only a root-relative name does — a true drive-letter-absolute name fails the write on both engines instead, which design.md and C-005 already stated correctly), then promoted all four documents'provenancefrominferredtoverified.clue validatenow reportsOKwith no remaining activation-gate findings. A third context-isolated review pass, scoped to that verification commit, confirmed the correction is factually accurate (independently re-derived theFile-merge semantics for both the root-relative and drive-absolute cases) and found no blocking findings.Verification is complete on candidate
2a8b458: tier-1 unit tests (includingRobotDataResolveTest), the full:conformance-test:testsweep (no regression across existing probes) plus the two new conformance tests on both engines,clue validate --coverage --reality-gaps --index-rows(OK), and three context-isolated agentic review passes. The first found one blocking finding — a path-traversal bypass in the resolver's asterisk/..check ordering — repaired in3f09647with a unit regression test; the second, scoped to that repair, found nothing blocking; the third, scoped to the human-verification commit, likewise found nothing blocking. Advisories closed along the way: FIO-001/C-005 wording narrowed to what's actually tested, quota-before-write ordering noted as intentional, test naming aligned to thetest<CRIT-ID>_...convention.🤖 Generated with Claude Code