Skip to content

Confine browser_upload_file sources to granted session roots#190

Open
yoshibase wants to merge 1 commit into
andrewyng:mainfrom
yoshibase:security/browser-upload-root-confine
Open

Confine browser_upload_file sources to granted session roots#190
yoshibase wants to merge 1 commit into
andrewyng:mainfrom
yoshibase:security/browser-upload-root-confine

Conversation

@yoshibase

Copy link
Copy Markdown

Summary

Fixes #189.

browser_upload_file resolved a caller-supplied path and uploaded it with no session-root
check
. The tool is registry kind writeRiskClass.EXTERNAL, and Mode.AUTO returns
"full access" with no path scoping — so a model-controlled path could exfiltrate
~/.config/coworker/secrets.json (or an SSH key) through a page file input.

This is the source sibling of #45 (which confines browser_screenshot's destination).
Outgoing email attachments already confine; upload did not.

What changed

  • make_browser_automation_tools(roots=…) — same seam email tools (and Confine browser_screenshot destination to the session's writable roots #45) use
  • Before opening Playwright, resolve the upload path and require it under a granted
    root (any granted root is fine — this is a read/exfil boundary, not a write)
  • Relative paths resolve against the primary granted root
  • Clear errors: "no granted session directory…" / "…is outside the session's granted directories"
  • Tool description updated so the model knows the path must be inside a granted directory
  • make_integration_tools forwards roots into the browser factory

Test plan

Hermetic — confinement runs before the browser opens, so no Playwright needed:

$ pytest tests/test_browser_upload_confine.py tests/test_send_target_resolution.py -q
13 passed

New cases:

  • absolute path outside every granted root → rejected; file unread
  • ../ traversal escape → rejected
  • no roots configured → rejected
  • path inside a read-only granted root → confinement passes (exfil boundary ≠ write)
  • path inside a writable root → confinement passes, then reaches the browser call

No new dependencies. Complementary to #45 (screenshot); the roots= parameter is additive
and defaults to None, so the two PRs compose cleanly either merge order.

browser_upload_file resolved a caller-supplied path and uploaded it with no
session-root check. The tool is kind=write → EXTERNAL risk, and Mode.AUTO
returns full access with no path scoping — so a model-controlled path could
exfiltrate ~/.config/coworker/secrets.json (or an SSH key) through a page
file input.

Thread the session roots into make_browser_automation_tools (same seam the
email tools and the open browser_screenshot fix use) and require the upload
source to resolve under a granted root before Playwright opens. Any granted
root is fine — this is a read/exfil boundary, not a write. Relative paths
resolve against the primary root.

Hermetic tests assert the refusal paths without Playwright: absolute escape,
../ traversal, no-roots, and that a path inside a read-only granted root
still passes confinement.
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.

browser_upload_file can exfiltrate any readable path (no session-root confinement)

1 participant