Skip to content

§6.4: add page-enforced write boundaries as a mitigation for agent over-reach #298

Description

@minjikim89

§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.

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 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.

Raw results (including a handEditSurvived field that is false in both arms, because the task is to rewrite that very field): https://github.com/minjikim89/redline/tree/main/evals/results

Where 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:

  • 2026-09-06: the opening listed three §6.4 mitigations; consequentialHint had landed in Add consequentialHint to ToolAnnotations #217 on 2026-09-03 and was missed. Corrected, and discussed in this comment.
  • 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions