Close the world server to cross-origin control; irreversible acts need the grant - #25
Merged
Merged
Conversation
…d the grant The shared world server sent Access-Control-Allow-Origin: * on every response and checked no credential, Host or Origin, so any page the operator visited could POST /act, /autopilot and /upload to 127.0.0.1:8808. A text/plain POST skips the browser preflight, so the wildcard was not even needed to write. - Bind loopback only; a non-loopback host raises instead of warning. - Generate a token per run, print it in the URL, and require it on every API route (X-World-Token; the event stream alone also takes ?token=). - Refuse requests whose Host or Origin is not the server's own, which also stops DNS rebinding; refuse cross-site Sec-Fetch-Site and non-JSON POSTs. - Send no CORS headers; OPTIONS always refuses. - The web UI keeps the token in the tab and strips it from the address bar. - Clamp a negative Content-Length, drain small bodies before refusing, and refuse the world root itself as a write target (it crashed the handler). Irreversible actuation now needs scope.allowed_irreversible_actions in the grant to name the action kind. allow_irreversible=True stays as a second opt-in that can only narrow; alone it authorizes nothing. No wildcard, boolean or string value counts. Tests that pre-authorize an irreversible act now say so in the grant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
python -m accountable_surface.world.server, port 8808) sentAccess-Control-Allow-Origin: *on every response and checked no token, Host or Origin. Any web page the operator visited could POST/act,/autopilotand/upload. Atext/plainPOST skips the browser preflight, so it could write even without the wildcard. Reproduced onmain(d5a7c10): a cross-sitetext/plainPOST fromOrigin: http://evil.examplereturned 200 and wrote the file.allow_irreversible=Truealone authorized an irreversible act. Any caller holding a grant for the action could pass the flag.Change
World server (
world/access.py,world/server.py,web/token.js):ACCOUNTABLE_WORLD_HOST=0.0.0.0no longer publishes it.X-World-Token; the event stream alone also takes?token=because EventSource cannot set headers). Constant-time compare.Sec-Fetch-Site, refuses non-JSON POSTs.Irreversible actuation (
grant.py,surface.py):scope.allowed_irreversible_actions. Only a list of exact kind strings counts: no wildcard, boolean or string (a string would turn membership into a substring test).allow_irreversible=Truestays as a second opt-in that can only narrow. The MCP path still passesFalse, so remote irreversible acts stayneeds-human.tests/_irreversible.py).Tests
tests/test_world_access.py(29) andtests/test_irreversible_grant.py(12). Againstmainthey were 0 passed (17 failed, 24 errors).test_world_access.pylooped 10 more times, all green./watchstreams, and a tokenless/worldreturns 401.Limits
Grants are unsigned dicts, so the grant is authority only as far as the code that builds it is the operator's. On the MCP path the authority store builds it and the flag stays
False. Not a version release.🤖 Generated with Claude Code