mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-27 06:14:48 -06:00
95e67dc768
Four Copilot findings onc6041c6— all confirmed valid, all bounded to authenticated-user prompt-injection scenarios but worth closing before merge. Wrapper-detect bypass (string + list branches of ``_apply_reminders_for_provider``): The round-2 fix used ``content.startswith("<tool_output>\\n")`` to detect already-wrapped content and skip ``escape_wrapper_tags``. A tool whose RAW output starts with that prefix (e.g. ``echo '<tool_output>'``) would match and have its escape skipped, letting literal ``<tool_output>`` / ``<system-reminder>`` tags reach the model and impersonate a system envelope. Replace the prefix check with ``extract_advisories_from_tool_envelope(content) is not None`` — parsing requires the open AND matching close tags AND a structurally valid envelope, raising the bypass bar significantly. Mirror fix in the list-content branch so a tool emitting an unmatched envelope as a text part can't bypass the per-text-part escape. ``_build_history`` legitimate-envelope drop: The list-content drop path previously removed any text part starting with ``<tool_output>\\n``. A tool that legitimately outputs a well-formed envelope (documentation viewer, code analyzer demoing the wrapper, an echo tool) would have that part silently disappear on replay. Tighten the drop heuristic to require BOTH ``cleaned_text == ""`` AND at least one extracted advisory — the structural signature of the injected ``wrap_tool_result("", advisories)`` carrier we produce in ``session.py`` for list-typed tool output. A legitimate envelope has non-empty inner body or no advisory blocks and survives the projection. Empty advisory body: ``queue_message`` accepts any non-None text including ``""`` and whitespace-only strings. ``_classify_advisory`` would return a ``user_interjection`` advisory with empty / whitespace body, which ``replayAdvisoriesAfterTool`` then renders as a featureless empty user bubble. Filter empty / whitespace-only bodies at classification time so the wire-shape contract is uniform: no empty advisories ever ride the wire. Tests: * ``test_apply_reminders_escapes_tool_output_starting_with_envelope_prefix`` pins the structural-parser bypass close: a string starting with the envelope prefix but lacking a close tag still gets escaped. * ``test_apply_reminders_escapes_list_text_part_with_unmatched_envelope_prefix`` mirrors for the list-content branch. * ``test_build_history_keeps_legitimate_envelope_text_part_with_body`` pins that legitimate envelope output stays in the projected list. * ``test_decorate_suppresses_empty_advisory_body`` and ``test_decorate_suppresses_whitespace_only_advisory_body`` pin the empty-body filter in ``_classify_advisory``. Tests: 5923 passed, 3 deselected. Lint + format + mypy clean. (cherry picked from commitc2cb6a7ea5)