Skip to content

Compiler grants engine write access to paths missing from sandbox filesystem.allowWrite (cache-memory silently fails with EROFS) #55424

Description

@lpcox

Summary

The compiler emits two independent write-permission surfaces that are allowed to disagree:

  1. The engine's tool permission list (e.g. Claude --allowed-tools), and
  2. The AWF sandbox policy filesystem.allowWrite.

When a path appears in (1) but not (2), the agent is explicitly told it may write there, attempts the write, and receives EROFS from the sandbox. Nothing fails loudly — the step exits 0 and the data is silently dropped.

This is currently reproducible for cache-memory, which is provisioned by the compiler, granted to the engine, and omitted from allowWrite.

Reported downstream as github/gh-aw-firewall#7696 ("Unclear location for agent temp files"), surfaced by the api-consumption-report workflow in discussion #55364.

Evidence

From .github/workflows/api-consumption-report.lock.yml (firewall pinned to v0.28.6 — see fix step 1, this predates the /tmp fix in v0.28.7 — and --container-runtime cloud-hypervisor), all in the same job:

Line Step Effect
544 Create cache-memory directory Creates /tmp/gh-aw/cache-memory on the runner
546–553 actions/cache/restore Restores prior contents into /tmp/gh-aw/cache-memory
554–558 Setup cache-memory git repository GH_AW_CACHE_DIR=/tmp/gh-aw/cache-memory
1076 Claude --allowed-tools Grants Write(/tmp/gh-aw/cache-memory/*), Edit(...), MultiEdit(...)
1063 Compiled AWF config "filesystem":{"allowWrite":["/tmp/gh-aw/agent","/workspace","/workspace/.awf-home"]}

/tmp/gh-aw/cache-memory is absent from allowWrite.

Why this fails

filesystem.allowWrite is a narrowing policy. Supplying it makes AWF re-publish every other writable export read-only, then add writable overlays only at the listed paths. So the covering /tmp bind becomes ro, with rw overlays at /tmp/gh-aw/agent, /workspace, and /workspace/.awf-home only. Writes anywhere else under /tmp/gh-aw correctly return EROFS.

This is the firewall working as designed — exempting /tmp/gh-aw wholesale would defeat the purpose of the feature and is explicitly ruled out by its design requirements. The gap is on the compiler side.

Observed impact

In discussion #55364, the agent merged a 5-entry trending history but could not persist it to /tmp/gh-aw/cache-memory/trending/api-consumption/history.jsonl. The run reported success; the next run silently restarts from the stale 2-entry cache. Any cache-memory-enabled workflow running under a sandbox with allowWrite is affected, and the failure is invisible unless the agent happens to self-report it.

Proposed fix

1. Bump the pinned firewall version to v0.28.7 (required).
The failing workflow pins "imageTag":"0.28.6". v0.28.6 does not contain the restricted-/tmp fix from github/gh-aw-firewall#7679 (merge 796ce1b9); v0.28.7 (tag ac4466f8) does. That fix is a separate concern from the allowWrite omission below — it repairs agent startup under a narrowed /tmp, whereas this issue is about a running agent hitting a correctly-narrowed path — but both are needed for cache-memory workflows to work end to end under a narrowing policy. Resolving this issue must include bumping the awf/firewall pin from 0.28.6 to 0.28.7.

Status at time of writing: v0.28.7 is tagged in github/gh-aw-firewall but not yet published as a release (latest published is v0.28.6). The bump should land once the release is available.

2. Include compiler-provisioned write targets in allowWrite (immediate).
When cache-memory is enabled, add /tmp/gh-aw/cache-memory to the generated filesystem.allowWrite. The directory is created at line 544, well before awf is invoked at line 1075, so it satisfies the planner's requirement that allowWrite paths already exist on the host. No firewall change is needed.

3. Derive allowWrite from the same source as engine write permissions (durable).
The two lists should not be authored independently. Ideally allowWrite is generated from — or validated against — the set of write-capable tool permissions, so a path can never be granted to the engine without also being writable in the sandbox.

4. Fail closed at compile time (guardrail).
Emit a compiler error (or at minimum a warning) when a Write/Edit/MultiEdit path prefix is not covered by any allowWrite entry. This converts a silent runtime data-loss bug into a build-time failure.

5. Ensure provisioned directories exist before awf starts.
allowWrite requires existing host paths. Any path the compiler adds must be created by an earlier step — the same prerequisite already documented for $GITHUB_WORKSPACE/.awf-home in github/gh-aw-firewall#7670. Worth auditing other feature-provisioned directories under /tmp/gh-aw for the same mismatch.

Related

Note on the downstream report

The linked report claims "the mount table reports rw" while writes return EROFS. That detail is agent-written and I could not corroborate it; a narrowed bind should show ro. The substantive symptom (writes outside the allowWrite set fail) is unambiguous, but the mount-table claim should not be treated as established.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions