Skip to content

CH-009: confine robot file I/O to the data directory - #14

Merged
flemming-n-larsen merged 7 commits into
mainfrom
ch-009-m004-file-io-sandboxing
Sep 5, 2026
Merged

CH-009: confine robot file I/O to the data directory#14
flemming-n-larsen merged 7 commits into
mainfrom
ch-009-m004-file-io-sandboxing

Conversation

@flemming-n-larsen

@flemming-n-larsen flemming-n-larsen commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Acceptance brief

Plan item: P-001 / M-004; remains wanted. This merge closes FIO-001, FIO-002, FIO-003, leaves FIO-004 @draft, records IDR-007, and promotes C-005 to enforcement: partial. No unmerged base is authorized or included.

Scenario resolution

  • FIO-001 — verifies: FileRedirectionConformanceTest proves a root-relative name is redirected into the data directory and nothing is written at the named path, on both engines; RobotDataResolveTest adds unit coverage of the resolver's strip/traversal-check ordering.
  • FIO-002 — verifies: FileRedirectionConformanceTest proves a plain-name write through getDataFile appears in getDataDirectory's listing, on both engines.
  • FIO-003 — verifies: FileQuotaConformanceTest proves 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-007 records that classic's own evidence for it (FileAttack, FileOutputStreamAttack) depends on a JVM SecurityManager unconditional on path — even a getDataFile-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 RobotData resolver (path re-rooting, quota tracking) and the RobocodeFileOutputStream write path calling into it, CAP-004's promotion to status: active and provenance: verified, C-005's partial-enforcement declaration (also provenance: verified), and P-001's M-004/M-119M-122 bookkeeping (M-122 dropped as a closeable evidence door). It changes no frozen robocode.* signature and retires no criterion; FIO-004 remains 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 and C-005 against the shipped code and tests, found and fixed one carried-over inaccuracy (README.md and RobotData'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' provenance from inferred to verified. clue validate now reports OK with 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 the File-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 (including RobotDataResolveTest), the full :conformance-test:test sweep (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 in 3f09647 with 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 the test<CRIT-ID>_... convention.

🤖 Generated with Claude Code

flemming-n-larsen and others added 6 commits September 5, 2026 15:39
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
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
flemming-n-larsen marked this pull request as draft September 5, 2026 14:51
@flemming-n-larsen
flemming-n-larsen marked this pull request as ready for review September 5, 2026 14:56
@flemming-n-larsen
flemming-n-larsen merged commit 57f1bb5 into main Sep 5, 2026
6 checks passed
@flemming-n-larsen
flemming-n-larsen deleted the ch-009-m004-file-io-sandboxing branch September 9, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant