Let the policy see the Enter a type action presses - #104
Conversation
A form has three doors and the policy could see two. `computer_type` takes a `submit` flag that presses Enter once the text is in, and the gateway passed no key for it, so an agent refused on clicking "Submit order" and refused again on pressing Enter typed into the field with `submit: true` and the order went through. The audit row said a field had been filled in, because it had no key to say otherwise. The deployment following the product's own advice was the one with the door open. Both shipped copies of the rule, the example in `.env.example` and the "Never submit a form" preset on the Boundaries page, named `computer_key` alone. Three changes, and the last is the one that closes it for anybody who has not written their own rule: - the gateway carries `key: "Enter"` for a type that submits, so a rule about Enter decides on it and the trail records it; - `intentOf` reports that action as `activate` rather than `type`, on the same reasoning the keypress path already used: what Enter does is press whatever has focus; - both shipped rules name `computer_type` alongside `computer_key`. The clause still guards `key` behind a tool name so it short-circuits on actions that have no keypress in them, which is what keeps a browser rule from refusing MCP calls. The preset's behaviour is now pinned by a test that runs the shipped string through the button, the keypress and the submit flag, and confirms a Bot can still fill a form in. Two strings in two files drift otherwise, and the drift is silent.
7360bf5 to
1f73a91
Compare
|
The hole is real and the diagnosis is exactly right: on main a What blocks it is the intent reclassification ( |
davidmckayv
left a comment
There was a problem hiding this comment.
Verified against main at 4f072bf, which has moved a long way since this was cut. Merges clean; 1145 pass, 8 skip, 0 fail on the merged tree, typecheck and format clean.
The bypass is real and I confirmed it independently before reading this. type() hands govern only ref and snapshotId, so a call carrying submit: true reaches the policy with no key, and the shipped preset's tool.name == "computer_key" && key == "Enter" clause cannot match. The first clause does not save it either: on that path the element is the field, so element.name is the field's label rather than the button's.
I have a particular reason to care about this one. Earlier tonight I validated that preset by driving it: applied Never submit a form, told a Bot to press Submit order on httpbin.org/forms/post, and got a refusal naming the rule, with carriedOut: false on the row. That validation was incomplete, and this PR is why. I proved one door was shut while a second stood open, and would have signed the boundary off on that basis.
What makes the fix right rather than merely effective:
- The key is set only when
submitis. The obvious fix is to report everycomputer_typeas a keypress, which would start refusing ordinary text under a rule about Enter. The test "typing without a submit is still typing" is the one that matters here, and it is present. intentmoves with it. A submit reportsactivate, so the clause the preset actually leads with catches it too, not only a rule someone thought to write aboutkey.- Both shipped copies are updated,
.env.exampleand the Boundaries preset. Fixing the engine and leaving the advice stale would have left every existing deployment carrying the hole. - The row carries the key. Without it the trail says a field was filled in, not that a form was sent, which is the second half of the same problem.
policy.tsis corrected rather than left flattering. "A form has two doors" becomes three, and the remaining limitation is still stated plainly.
One note for afterwards, not a blocker: the fallthrough from computer_key into computer_type is deliberate and reads correctly, but it is the kind of line a later edit can break silently by inserting a case between them. The tests would catch it, which is the answer.
Good find, and the write-up on #101 with the three-row table is what made it quick to confirm.
Closes #101.
A form has three doors and the policy could see two.
computer_typepresses Enter whensubmitis set, and the gateway passed no key for it, so an agent refused at the button and refused at the keypress typed into the field withsubmit: trueinstead and the form went through. Both shipped copies of the "never submit" rule namecomputer_keyalone, so the deployment following the product's own advice was the one with the door open.What it does
gateway.typecarrieskey: "Enter"when the call submits. That is the fieldpolicy.tsadded for exactly this reason, and this path was not setting it, so a rule about Enter now decides on it and the audit row records it. Without the key the trail said a field had been filled in and not that the form had been sent.intentOfreports that action asactivaterather thantype, foldingcomputer_typeinto the branchcomputer_keyalready used. What Enter does is press whatever has focus, whichever tool asked for it.computer_typealongsidecomputer_key, in.env.exampleand in the Boundaries preset.The clause still guards
keybehind a tool name. That guard is load-bearing for a reason unrelated to this: an unevaluable deny expression matches, so an unguarded rule naming a browser field refuses MCP tool calls, whichserver/tests/plugin-store.integration.test.ts:243already covers. Dropping it to shorten the rule would have traded this bug for that one.Verification
Five new cases in
server/tests/computer-gateway.test.ts, driving the real gateway and the real policy engine.Fails before, passes after: the submit is refused by a rule about Enter, the audit row carries
key: Enter, and the submit is refused by a rule aboutintent == "activate".Must not move, and did not: typing without
submitis still permitted under a rule that refuses Enter. That is the case a careless fix breaks, by reporting every type as a keypress.The shipped preset now has its own test, running the exact string through the button, the keypress and the submit flag, and confirming a Bot can still fill a form in and take a screenshot. It lives in two files, one of them a string in the browser bundle, and they drift silently otherwise.
I also reverted each half of the change with the tests live to confirm they fail: dropping the key fails three, dropping the intent change fails one.
serversuite failure set is identical tomain.bun run --filter server typecheckandbunx biome checkclean on the changed files.Scope
An operator who wrote their own rule naming
computer_keystill has to widen it, and the preset change does not reach a policy already configured in an environment variable. That is unavoidable: the deployment's rule is the deployment's. What changes for everybody is that the key and the intent are now true, so a rule written about either catches this call.