Skip to content

fix(gateway): neutralise untrusted reply text in the reply prefix - #87229

Closed
iainlane wants to merge 1 commit into
NousResearch:mainfrom
iainlane:fix/neutralise-reply-prefix
Closed

fix(gateway): neutralise untrusted reply text in the reply prefix#87229
iainlane wants to merge 1 commit into
NousResearch:mainfrom
iainlane:fix/neutralise-reply-prefix

Conversation

@iainlane

Copy link
Copy Markdown

What does this PR do?

_prepare_inbound_message_text builds the [Replying to: "..."] pointer by
interpolating event.reply_to_text[:500] raw into the per-turn message. The
quoted text is another participant's content on every platform that populates
reply_to_text, and the prefix is prepended verbatim to the turn the model
reads. An embedded newline therefore let a crafted quote break out of the
bracketed line and pose as a fresh markdown section (a fake ## SYSTEM
heading) or forge framing of its own.

This PR runs the snippet through neutralize_untrusted_inline_text() before
building the prefix, the same treatment dbad6d4 (#5961) gave other
attacker-controllable prompt metadata. The helper collapses newlines and
control characters to spaces, so a well-behaved quote is unchanged while a
hostile one becomes visually inert. The existing 500-char cap is the intended
bound, so the helper's own truncation is disabled (max_chars=0). Because the
fix sits in the shared gateway path, it covers every platform that supplies
reply context.

One behaviour change to be aware of: a multi-line quote now renders as a
single space-joined line in the prefix. That is the helper's established
semantics.

Extracted from #51803, where review flagged this surface; the platform-wide
chokepoint fix stands alone, and #51803/#62088 build on it.

Related Issue

No existing issue; the surface was identified during review of #51803.

Type of Change

  • 🔒 Security fix

Changes Made

  • gateway/run.py: neutralise the reply snippet before interpolating it into
    the [Replying to ...] prefix (one call site, both the own-message and
    other-message variants).
  • tests/gateway/test_reply_to_injection.py: regression tests, written
    failing-first: framing in the quoted text cannot escape the prefix
    (parametrised over both variants), the snippet is truncated to 500 chars,
    and no prefix is emitted without reply text.

How to Test

  1. pytest tests/gateway/test_reply_to_injection.py -q (7 tests).
  2. To see the bug on main: revert the gateway/run.py hunk and re-run; the
    two test_framing_in_reply_text_cannot_break_out_of_the_prefix
    parametrisations fail, with the injected ## SYSTEM heading landing at
    line start in the composed message.
  3. Full sweep of the twelve test files covering the prefix and
    _prepare_inbound_message_text: 280 passed.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass (targeted suites run
    locally: 7/7 in the changed test file and 280 passed across the twelve
    files covering this code path; opened as draft so CI runs the full suite)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 26

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

_prepare_inbound_message_text builds the [Replying to: "..."] pointer
by interpolating event.reply_to_text[:500] raw into the per-turn
message. The quoted text is another participant's content on every
platform that populates reply_to_text, and the prefix is prepended
verbatim to the turn the model reads, so an embedded newline let a
crafted quote break out of the bracketed line and pose as a fresh
markdown section (a fake "## SYSTEM" heading) or forge framing of its
own.

Run the snippet through neutralize_untrusted_inline_text() before
building the prefix, the same treatment NousResearch#5961/dbad6d47 gave other
attacker-controllable prompt metadata. The helper collapses newlines
and control characters to spaces, keeping a well-behaved quote
byte-identical while making a hostile one visually inert. The existing
500-char cap is the intended bound, so the helper's own truncation is
disabled (max_chars=0). Because the fix sits in the shared gateway
path, it covers every platform that supplies reply context.

Adds regression tests: framing in the quoted text cannot escape the
prefix (for both the own-message and other-message variants), the
snippet is truncated to 500 chars, and no prefix is emitted without
reply text.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages duplicate This issue or pull request already exists labels Aug 15, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #65184: both patch the same reply-prefix interpolation sink with the same inline-text neutralization mechanism.

@iainlane

Copy link
Copy Markdown
Author

Closing as a duplicate of #65184, which predates this PR by a month and applies the same neutralisation at the same call site; the triage flag is correct and I had missed it when searching. One small delta that may be worth folding into #65184: my version also parametrises the breakout test over the reply_to_is_own_message branch, which formats through a second f-string at the same sink. Happy to see #65184 land.

@iainlane iainlane closed this Aug 15, 2026
@iainlane
iainlane deleted the fix/neutralise-reply-prefix branch August 15, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants