Expose sandboxed read_file/write_file helpers to inline code (Python_exec) in unsafe mode #93

Open
opened 2026-07-31 19:26:18 +00:00 by CoreRasurae · 0 comments
Member

Metadata

  • Commit Message: feat(agents): expose sandboxed file helpers to inline code
  • Branch: feature/m1-inline-code-sandboxed-file-access

Background and context

Inline-code tools (§4.5.2) — e.g. a Python_exec-style type: tool agent with
a code body — run in the restricted sandbox (§13.2). Today that sandbox
exposes no filesystem access at all: open() is not in the §13.2.1 built-in set
and §13.2.3 prohibits arbitrary filesystem I/O unconditionally. That is
conformant, but it means inline code cannot read or write file contents
dynamically from within a single code body; the only sanctioned path is static
composition (file_read tool → inline tool → file_write tool), which does not
cover dynamic or multi-file access.

ADR-2035 (docs/adr/ADR-2035-inline-code-sandboxed-file-access.md) decides
to close this gap with sanctioned helper functions rather than raw open():
two callables, read_file/write_file, injected into the inline sandbox in
unsafe mode, reusing the existing file_read/file_write validated cores and
confined to the sandbox root. This issue implements that decision and applies
the specification amendments the ADR mandates.

Gating: implementation of this issue is contingent on ADR-2035 being
accepted
. It should not begin until the ADR is merged.

Current behavior

  • Inline code cannot call open() in any mode; no read_file/write_file
    facility exists.
  • File contents can only reach inline code by being piped in as input_data
    from a separately-wired file_read tool.

Expected behavior

Per ADR-2035, when the host is in unsafe mode the inline sandbox gains exactly
two injected local callables (like json), and nothing else widens:

  • read_file(path, max_chars=None, offset=0) -> str — returns the raw decoded
    file contents, reusing the file_read core (incl. ADR-2033 offset/max_chars
    windowing). Returns content, not the [FILE_READ_SUCCESS] LLM envelope.
  • write_file(path, content, mode="w") -> int — writes via the file_write
    core (§4.5.5 modes), returns characters written.

Both helpers confine access to the sandbox root and preserve the existing
read/write privilege split.

Acceptance criteria

  • In unsafe mode, read_file and write_file are available as injected
    locals inside inline-code bodies; in safe mode they are absent (referencing
    them raises NameError).
  • open, os, io, pathlib, and file descriptors remain unavailable to
    inline code (the §13.2.1 built-in table is unchanged).
  • read_file returns the raw decoded content as str, honoring max_chars
    and offset with the same semantics as the file_read tool.
  • write_file writes via the file_write core, honors w/a/insert
    modes, and returns the number of characters written.
  • Containment: both helpers reject .. traversal, ~ expansion, and any
    path whose resolved real path (symlinks followed) falls outside the sandbox
    root; a violation raises ValueError naming the offending path.
  • write_file performs a write only when the invocation context contains
    _unsafe_mode: true; without it, it raises ValueError. read_file does
    not require _unsafe_mode.
  • docs/index.md is updated per ADR-2035 D-6 (§13.2.3 reworded, §4.5.2 lists
    the helpers, §13.2.1 note, §13.3 cross-reference) in the same change.
  • The §13.2.2 expression sandbox (transform.fn, bridge predicates) is
    unchanged and gains no file access.

Supporting information

  • Decision record: docs/adr/ADR-2035-inline-code-sandboxed-file-access.md
    (Status: proposed → must be accepted before this issue starts).
  • Spec: docs/index.md — §4.5.2 (inline tools / injected facilities), §4.5.1 &
    §4.5.5 (file_read/file_write tools and modes), §13.2.1 (inline built-ins),
    §13.2.3 (prohibited capabilities), §13.3 (filesystem boundaries), §4.5.4
    (safe-mode restrictions).
  • Reuses the ADR-2033 offset/max_chars windowing for read_file.

Subtasks

  • Extract the file_read/file_write validation+I/O cores so they can back
    both the built-in tools and the injected helpers (single validation
    surface).
  • Implement the sandbox-root containment check (realpath resolution,
    symlink-escape rejection, ../~ rejection), raising ValueError.
  • Implement read_file and write_file over those cores; gate write_file
    on _unsafe_mode.
  • Inject the helpers into the inline-code namespace only when the host is in
    unsafe mode; ensure they are absent otherwise.
  • Update docs/index.md per ADR-2035 D-6, in the same commit as the code.
  • Tests (Behave): helper availability (unsafe vs safe/NameError), read with
    offset/max_chars, write modes, _unsafe_mode-absent write rejection, and
    sandbox-escape attempts (.., absolute, ~, symlink-out-of-root).
  • Tests (Robot): integration exercising inline code reading and writing a
    real file within the sandbox, and rejecting an escape attempt.
  • Update CHANGELOG with a user-facing entry.
  • Verify coverage >= 97% via nox -s coverage_report.
  • Run nox (all default sessions), fix any errors.

Definition of Done

This issue is complete when:

  • ADR-2035 is accepted and its §13 amendments are reflected in docs/index.md
    in this change.
  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line matches the Commit Message in
    Metadata exactly.
  • The commit is pushed to the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a PR to master, reviewed, and merged.
## Metadata - **Commit Message:** `feat(agents): expose sandboxed file helpers to inline code` - **Branch:** `feature/m1-inline-code-sandboxed-file-access` ## Background and context Inline-code tools (§4.5.2) — e.g. a `Python_exec`-style `type: tool` agent with a `code` body — run in the restricted sandbox (§13.2). Today that sandbox exposes no filesystem access at all: `open()` is not in the §13.2.1 built-in set and §13.2.3 prohibits arbitrary filesystem I/O unconditionally. That is conformant, but it means inline code cannot read or write file contents dynamically from within a single code body; the only sanctioned path is static composition (`file_read` tool → inline tool → `file_write` tool), which does not cover dynamic or multi-file access. **ADR-2035** (`docs/adr/ADR-2035-inline-code-sandboxed-file-access.md`) decides to close this gap with **sanctioned helper functions** rather than raw `open()`: two callables, `read_file`/`write_file`, injected into the inline sandbox in unsafe mode, reusing the existing `file_read`/`file_write` validated cores and confined to the sandbox root. This issue implements that decision and applies the specification amendments the ADR mandates. > Gating: implementation of this issue is contingent on **ADR-2035 being > accepted**. It should not begin until the ADR is merged. ## Current behavior - Inline code cannot call `open()` in any mode; no `read_file`/`write_file` facility exists. - File contents can only reach inline code by being piped in as `input_data` from a separately-wired `file_read` tool. ## Expected behavior Per ADR-2035, when the host is in unsafe mode the inline sandbox gains exactly two injected local callables (like `json`), and nothing else widens: - `read_file(path, max_chars=None, offset=0) -> str` — returns the raw decoded file contents, reusing the `file_read` core (incl. ADR-2033 offset/max_chars windowing). Returns content, not the `[FILE_READ_SUCCESS]` LLM envelope. - `write_file(path, content, mode="w") -> int` — writes via the `file_write` core (§4.5.5 modes), returns characters written. Both helpers confine access to the sandbox root and preserve the existing read/write privilege split. ## Acceptance criteria - [ ] In unsafe mode, `read_file` and `write_file` are available as injected locals inside inline-code bodies; in safe mode they are absent (referencing them raises `NameError`). - [ ] `open`, `os`, `io`, `pathlib`, and file descriptors remain unavailable to inline code (the §13.2.1 built-in table is unchanged). - [ ] `read_file` returns the raw decoded content as `str`, honoring `max_chars` and `offset` with the same semantics as the `file_read` tool. - [ ] `write_file` writes via the `file_write` core, honors `w`/`a`/`insert` modes, and returns the number of characters written. - [ ] Containment: both helpers reject `..` traversal, `~` expansion, and any path whose resolved real path (symlinks followed) falls outside the sandbox root; a violation raises `ValueError` naming the offending path. - [ ] `write_file` performs a write only when the invocation context contains `_unsafe_mode: true`; without it, it raises `ValueError`. `read_file` does not require `_unsafe_mode`. - [ ] `docs/index.md` is updated per ADR-2035 D-6 (§13.2.3 reworded, §4.5.2 lists the helpers, §13.2.1 note, §13.3 cross-reference) in the same change. - [ ] The §13.2.2 expression sandbox (`transform.fn`, bridge predicates) is unchanged and gains no file access. ## Supporting information - Decision record: `docs/adr/ADR-2035-inline-code-sandboxed-file-access.md` (Status: proposed → must be accepted before this issue starts). - Spec: `docs/index.md` — §4.5.2 (inline tools / injected facilities), §4.5.1 & §4.5.5 (`file_read`/`file_write` tools and modes), §13.2.1 (inline built-ins), §13.2.3 (prohibited capabilities), §13.3 (filesystem boundaries), §4.5.4 (safe-mode restrictions). - Reuses the ADR-2033 `offset`/`max_chars` windowing for `read_file`. ## Subtasks - [ ] Extract the `file_read`/`file_write` validation+I/O cores so they can back both the built-in tools and the injected helpers (single validation surface). - [ ] Implement the sandbox-root containment check (realpath resolution, symlink-escape rejection, `..`/`~` rejection), raising `ValueError`. - [ ] Implement `read_file` and `write_file` over those cores; gate `write_file` on `_unsafe_mode`. - [ ] Inject the helpers into the inline-code namespace only when the host is in unsafe mode; ensure they are absent otherwise. - [ ] Update `docs/index.md` per ADR-2035 D-6, in the same commit as the code. - [ ] Tests (Behave): helper availability (unsafe vs safe/NameError), read with offset/max_chars, write modes, `_unsafe_mode`-absent write rejection, and sandbox-escape attempts (`..`, absolute, `~`, symlink-out-of-root). - [ ] Tests (Robot): integration exercising inline code reading and writing a real file within the sandbox, and rejecting an escape attempt. - [ ] Update CHANGELOG with a user-facing entry. - [ ] Verify coverage >= 97% via `nox -s coverage_report`. - [ ] Run `nox` (all default sessions), fix any errors. ## Definition of Done This issue is complete when: - ADR-2035 is accepted and its §13 amendments are reflected in `docs/index.md` in this change. - All subtasks above are completed and checked off. - A Git commit is created where the first line matches the Commit Message in Metadata exactly. - The commit is pushed to the branch matching the Branch in Metadata exactly. - The commit is submitted as a PR to `master`, reviewed, and merged.
CoreRasurae added this to the v2.1.0 milestone 2026-07-31 19:26:18 +00:00
CoreRasurae added the
State
Unverified
Type
Feature
Priority
Backlog
labels 2026-07-31 19:26:18 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: cleveragents/cleveractors-core#93