You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
§6.4's mitigations all address one direction: a site misleading an agent (input-length limits, shared attack evals, untrustedContentHint, and consequentialHint). §6.3.2 Current Gaps ends with "no verification mechanism … no behavioral contracts … agents must assume good faith from site developers."
The reverse direction has no listed mitigation: an agent, misled or simply over-eager, exceeding the scope a person delegated on a site that exposed write tools. That is the direction prompt injection actually exploits: the injected instruction lives in the site's content, and the damage lands on the site's own data.
Proposal
Name, in §6.4, a mitigation that does not depend on the model resisting injection. The page enforces, in the tool implementations:
(a) a person-owned write scope. Writes outside it are refused with a structured error naming the allowed targets.
(b) optimistic concurrency against the person's own edits. Writes over changes the agent has not read are refused with a diff.
(c) page-owned cancellation for long-running writes, so a stop both ends the write and reports what landed.
(d) enforcement by absence: not registering a tool whose action is not legal in the current state, so the call cannot be made at all.
The point is that none of these ask the model to behave. (a) through (c) are ordinary preconditions in the tool body, and they hold whatever the agent believes. (d) sits one step earlier and is a different instrument, described below.
On (c)
(c) is the person's stop rather than the caller's: a control on the page that ends a sweep and tells the agent what landed. On Chrome 152 it happened to be the only cancellation path as well, since execute received no signal there; CL 8025300 closes that in 153, verified, with thanks to @mysticalseeker24 for measuring it. The reporting half of this is #299.
Their implementation derives the registered set from page state: each tool carries an available(state) predicate, and an action that is not legal right now is simply absent from getTools(). The two instruments have genuinely different properties, which is why both seem worth naming rather than one standing in for the other:
preconditions in the tool body
absence from the registered set
Granularity
per target, per field, per revision
per state, coarse
What the agent learns
a structured refusal it can act on
nothing, unless the page says why
Failure mode if ignored
the write is refused
there is no call to ignore
Absence cannot express "you may edit these three slides", which is what (a) is for. And absence alone destroys context, which is #262: it needs a counterpart that names what is missing and what would make it legal again, or it is worse than a refusal rather than better. Both implementations landed on providing that counterpart from a tool that stays registered.
Measured
scripts/guardrail-eval.mts hands a model the same tool contracts the page registers and dispatches to the same implementations, with the guards on and off. Scale, stated plainly: one task, one site, two models, ten runs per arm.
arm
wrote over an unread hand edit
task completed
gpt-4.1 · guards ON
0/10
10/10
gpt-4.1 · guards OFF
10/10
10/10
gpt-5.4 · guards ON
0/9
9/9
gpt-5.4 · guards OFF
8/8
8/8
gpt-5.4 lost 3 of 20 runs to network errors, which were excluded; hence 9 and 8.
Two honest notes on the injection scenario. First, on gpt-4.1 the model followed the injected instruction in 0/10 runs with the guards on and 0/10 with them off, so it did not distinguish the arms, so that run says nothing about the guards. That is why a second, model-free arm exists: a scripted agent that follows the injection by construction rewrote 8 of 9 unmarked slides with the guards off and 0 of 9 with them on. The claim is about the page refusing the write, not about a model resisting a prompt.
A user agent that both invokes tools and automates the page can complete the page's own human-approval step #288 reports an agent that both calls tools and automates the page completing the page's own approval step. That is a real limit on what is proposed here: guards in the tool implementation bind the tool path only. An agent that can synthesize clicks can edit through the UI and never touch a tool. So this mitigation raises the floor for tool-mediated writes; it does not survive a user agent that also drives the page. Worth saying in §6.4 rather than leaving implementers to discover it, and worth saying in the same breath that the intuitive page-side hardening is closed off: anything separating synthesized input from physical input (CAPTCHA, dwell requirements, timing heuristics) is a barrier to switch access, voice control and screen reader users, and still would not establish authorization. The distinction that matters is agent-originated vs user-authorized, not synthesized vs physical. (Raised by @mysticalseeker24.)
2026-09-12: (d) added from @mysticalseeker24's implementation. (c) was briefly restated on their Chrome 152 measurements and then narrowed again once Chrome 153 was checked, where the signal reaches execute.
§6.4's mitigations all address one direction: a site misleading an agent (input-length limits, shared attack evals,
untrustedContentHint, andconsequentialHint). §6.3.2 Current Gaps ends with "no verification mechanism … no behavioral contracts … agents must assume good faith from site developers."The reverse direction has no listed mitigation: an agent, misled or simply over-eager, exceeding the scope a person delegated on a site that exposed write tools. That is the direction prompt injection actually exploits: the injected instruction lives in the site's content, and the damage lands on the site's own data.
Proposal
Name, in §6.4, a mitigation that does not depend on the model resisting injection. The page enforces, in the tool implementations:
The point is that none of these ask the model to behave. (a) through (c) are ordinary preconditions in the tool body, and they hold whatever the agent believes. (d) sits one step earlier and is a different instrument, described below.
On (c)
(c) is the person's stop rather than the caller's: a control on the page that ends a sweep and tells the agent what landed. On Chrome 152 it happened to be the only cancellation path as well, since
executereceived no signal there; CL 8025300 closes that in 153, verified, with thanks to @mysticalseeker24 for measuring it. The reporting half of this is #299.On (d), contributed by @mysticalseeker24
Their implementation derives the registered set from page state: each tool carries an
available(state)predicate, and an action that is not legal right now is simply absent fromgetTools(). The two instruments have genuinely different properties, which is why both seem worth naming rather than one standing in for the other:Absence cannot express "you may edit these three slides", which is what (a) is for. And absence alone destroys context, which is #262: it needs a counterpart that names what is missing and what would make it legal again, or it is worse than a refusal rather than better. Both implementations landed on providing that counterpart from a tool that stays registered.
Measured
scripts/guardrail-eval.mtshands a model the same tool contracts the page registers and dispatches to the same implementations, with the guards on and off. Scale, stated plainly: one task, one site, two models, ten runs per arm.gpt-5.4 lost 3 of 20 runs to network errors, which were excluded; hence 9 and 8.
Two honest notes on the injection scenario. First, on gpt-4.1 the model followed the injected instruction in 0/10 runs with the guards on and 0/10 with them off, so it did not distinguish the arms, so that run says nothing about the guards. That is why a second, model-free arm exists: a scripted agent that follows the injection by construction rewrote 8 of 9 unmarked slides with the guards off and 0 of 9 with them on. The claim is about the page refusing the write, not about a model resisting a prompt.
Raw results (including a
handEditSurvivedfield that is false in both arms, because the task is to rewrite that very field): https://github.com/minjikim89/redline/tree/main/evals/resultsWhere this sits, and what it does not cover
Implementation and error shapes: https://github.com/minjikim89/redline/blob/main/docs/pattern.md
Full notes from building against the spec: https://github.com/minjikim89/redline/blob/main/docs/findings.md
Live: https://minjikim89.github.io/redline/
Edits to this issue since filing, for the record:
consequentialHinthad landed in Add consequentialHint to ToolAnnotations #217 on 2026-09-03 and was missed. Corrected, and discussed in this comment.execute.