Skip to content

fix(web): make ~/downloads writable from inside a session - #320

Merged
defangdevs merged 1 commit into
masterfrom
fix/316-downloads-writable
Aug 21, 2026
Merged

fix(web): make ~/downloads writable from inside a session#320
defangdevs merged 1 commit into
masterfrom
fix/316-downloads-writable

Conversation

@defangdevs

Copy link
Copy Markdown
Owner

Fixes #316.

~/downloads is a symlink to /var/lib/agent-box-downloads/<user>, so a write through it resolves outside /home and ProtectSystem=strict denies it. The agent unit's ReadWritePaths named the sites dir and not the downloads dir, so the hand-off the canonical guide documents has never worked from inside a session:

$ touch /var/lib/agent-box-downloads/agent/probe
touch: cannot touch '...': Read-only file system
$ systemctl cat agent-box-agent.service | grep -E 'ProtectSystem|ReadWritePaths'
ProtectSystem=strict
ReadWritePaths=/home/agent
ReadWritePaths=/var/lib/agent-box-sites/agent

Found while trying to publish a screenshot for #317 from a session.

The fix

One entry, under the same web.enable guard as its neighbour — the tmpfiles rule that creates the dir is gated too, and an unconditional entry fails the namespace setup with 226/NAMESPACE on a default web-less box (the failure mode memory-protection.nix exists to catch).

Why the existing test did not catch it

web-surface.nix already exercised the drop — but as sudo -u agent from the driver's root shell, which skips ProtectSystem entirely, so it passed against a read-only directory. The ~/sites subtest right below it had already learned this lesson and used nsenter; the downloads one had not.

Both documented flows now write through nsenter -t <MainPID> -m -- runuser -u agent, with the namespace handle hoisted above the subtests so neither can drift back, and each asserts its own directory is in the unit's ReadWritePaths.

Checks run

  • nix run .#assemble + module-generated-up-to-date — ok
  • nix run .#update-golden + golden-snapshot — ok. The golden diff is exactly two lines, ReadWritePaths=/var/lib/agent-box-downloads/{agent,robot}, and only under tests/golden/web/ — the ungated (web-off) unit is untouched, which is the 226/NAMESPACE guard holding.
  • module-single-file, multi-user — ok
  • nix eval .#checks.x86_64-linux.{web-surface,memory-protection}.drvPath — both evaluate (VM tests are x86-only; CI runs them)

`~/downloads` is a symlink to /var/lib/agent-box-downloads/<user>, so a
write through it resolves outside /home and ProtectSystem=strict denied it
with EROFS. The agent unit's ReadWritePaths named the SITES dir and not the
downloads one — so the hand-off the canonical guide documents ("mv
./report.pdf ~/downloads/", then give out ${AGENT_BOX_URL}downloads/…) has
never worked from a session:

    $ touch /var/lib/agent-box-downloads/agent/probe
    touch: ... Read-only file system

Same guard as its neighbour: listed only when web.enable is on, since that
is what gates the tmpfiles rule that creates the dir, and an unconditional
entry fails the whole namespace setup with 226/NAMESPACE on a default
web-less box.

The test is the interesting part. web-surface.nix already exercised this
drop — but as `sudo -u agent` from the driver's root shell, which skips
ProtectSystem entirely, so it passed against a read-only directory. Both
documented flows now write through `nsenter -t <MainPID> -m -- runuser -u
agent`, the handle is hoisted so neither can drift back, and each asserts
its own dir is in the unit's ReadWritePaths.

Fixes #316.
@defangdevs
defangdevs merged commit e00de74 into master Aug 21, 2026
1 check passed
@defangdevs
defangdevs deleted the fix/316-downloads-writable branch August 21, 2026 20:53
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Agent-Box Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

~/downloads is read-only from a session: the agent unit's ReadWritePaths names the sites dir but not the downloads dir

2 participants