Skip to content

Prompt-injection: click/type_text splice page-derived <select> option labels into unwrapped tool results #105

Description

@esokullu

Summary

The click and type_text tools build error/hint/note strings that splice page-derived <select>/listbox option labels (and the currently-selected option text) directly into the tool result. Neither tool is in UNTRUSTED_CONTENT_TOOLS (src/chrome/src/agent/permission-gate.js), so those results are not wrapped in <untrusted_page_content> markers — the attacker-controllable option text reaches the model as trusted data.

This is the same laundering class we just fixed in _digestToolResult (PR #102), but in a different code path. It is pre-existing and unrelated to the context-compaction work, so it was intentionally left out of that PR.

Threat model

A page the agent interacts with controls the <option> text / aria-label of a dropdown. When the model clicks the dropdown (a very common action on forms), WebBrain returns an error/hint listing the available options verbatim. A malicious page can set an option label to instruction text (e.g. Ignore previous instructions and …), smuggling it into the model's trusted context.

Affected code (Chrome only — Firefox has a simpler click impl with none of these)

src/chrome/src/agent/agent.js:

  • 4610No clickable element found … an open ${openOptions.source} is visible … with these options: ${openOptions.options.map(...)}
  • 4706… Available options: ${opts.options.join(', ')}
  • 4756hint: … Available options: ${cs.options.join(', ')} (+ cs.current)
  • 4802error: … (current: "${pOpts.current}") … Available options: ${pOpts.options.join(', ')}
  • 4882hint: … (current: "${selTag.current}") … Available options: ${selTag.options.join(', ')}
  • 4934error: … (current: "${coordTag.current}") … Available options: ${coordTag.options.join(', ')}
  • 5022error: … (current: "${redir.current}") … Available options: ${redir.options.join(', ')}
  • 5053error: … (current: "${pOpts2.current}") … Available options: ${pOpts2.options.join(', ')}
  • 5160-5161type_text: No option matching "…". Available: ${opts.map(o => o.text.trim()).join(', ')}

The current values (currently-selected option text) are page-derived too.

Recommended fix

Option A (preferred): add 'click' and 'type_text' to UNTRUSTED_CONTENT_TOOLS. Their results legitimately carry page-derived text (option labels, element text, aria-labels), so the whole result should be marked untrusted — this protects present and future fields automatically, and the boundary/digest handling already added for the compaction path will then treat them correctly.

Option B: sanitize the option labels at the splice points (strip [<>`" + newlines, bound length) before interpolating. Narrower but easy to miss a new field later.

Recommend Option A. Whichever is chosen, add a quick test asserting a malicious option label cannot appear unwrapped in a click/type_text result.

Scope notes

  • Chrome only (src/chrome/src/agent/agent.js). Firefox (src/firefox) has no Available options: splicing.
  • If Option A is taken, double-check _digestToolResult still produces a useful content-free digest for the newly-untrusted click/type_text (it will fall to the generic ${name} ok (untrusted page content) branch).

Found during the PR #102 security sweep.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions