Repository navigation
fix(bridges): a stranger's line ends nothing, and a web page cannot drive a robot - #57
Merged
Merged
Conversation
…rive a robot Replayed against the real daemons with their own fake bodies, one line ended the Open Duck bridge's only thread, token or no token, and a browser-shaped POST got bot.stand accepted on a tokenless ToddlerBot, through the ssh tunnel the docs recommend. tests/test_bridges_hostile_peers.py replays each, and 25 of its first 31 tests failed on the daemons as they were. The line daemons close a connection that opens with an HTTP request or an oversized line, and keep their tolerance of a bad line mid-session. The HTTP daemons refuse a request carrying Origin. No real client sends either, and a person opening a frame in a tab still gets it. ToddlerBot caps a line at 64 KiB. quackd serve-mcp --lock-duckfile, off by default, refuses a file asking for more than the startup contract. A project .env choosing where a key from the shell goes gets one line on stderr and nothing is refused, and quackd ui off loopback says it is plain HTTP. An adversarial review drove real Chrome against this and found what the first version missed: a URL long enough to push the request line out of first place, method words other than capitals, thousands of brackets, and a connection cap and first-line deadline that let a stranger lock a real pilot out. That cap and deadline are gone, and the line cap alone bounds the memory they were there for. It also found the warning could reach stdout with stderr closed, where a script's JSON and the MCP protocol are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…before it cli.py: each side added one import, and both stay. Both added under [Unreleased], and the headings stay in the order every release uses. 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.
Two ways a stranger could reach a robot's daemon, closed, and three small things that are opt in or only ever say something. Stacked on #56's branch.
One line could end the Open Duck bridge.
7,[],null, a stray byte, a token that was not ASCII or a thousand brackets each raised out of the only thread the server has, token or no token, and left the duck stopped, torqued and unreachable. Whatever one client's bytes raise now costs that client alone. A client that had not authenticated could also stop a duck somebody else was walking, by sayingduck.commandand hanging up.A web page could drive a robot. A page the operator visits can POST to
127.0.0.1, which is where the ssh tunnel the docs recommend ends. On a daemon with no token, which is how the README starts ToddlerBot's, it read the state and gotbot.standaccepted: the headers are no JSON and were skipped, and the body's line was read as a command. A MOSS bridge with no token took/twistand/arm/cyclethe same way.test_a_line_that_is_not_a_request_does_not_cost_the_sessionstill holds thatOrigin. Nothing real sends it, and a person opening a frame in a tab still gets itThese are files you copy to the robot, so a robot has them once you copy them again.
Opt in, or only ever a line of text. Nothing is refused that worked:
quackd serve-mcp --lock-duckfile, off by default: refuses a contract wider than the startup--duckfile.envchoosing where a key from your shell goes gets one line on stderr, naming the variable and the host. stdout,--jsonand a run's saved terminal are untouchedquackd uioff loopback says the page is plain HTTPHow it was checked.
tests/test_bridges_hostile_peers.pyreplays every attack against the real daemons with their own fake bodies, and 25 of its first 31 tests failed on the daemons as they were. The six that passed are the ones that say a real client still works.An adversarial review then drove real Chrome against this, twice. It found what my first version missed:
Host:was read as the first line and the body still ranmaindoes not allow that, so they are gone, and the line cap alone bounds the memory they were there forAll of those are fixed and have tests. On the second pass, 32 real Chrome fetches at URLs from 1 KB to 200 KB reached nothing.
What is still open, and in
SECURITY.md: a page on a name it rebinds to127.0.0.1can read a frame from an HTTP daemon with no token. Refusing an unfamiliarHostwould close it and would also refusetailscale serveand anybody else's proxy, so I left it. A token closes it where there is one, and the camera daemon has none.Nothing moved. Every existing bridge test passes unmodified but one, whose spy now sees the token compared as bytes.
🤖 Generated with Claude Code