mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(fence): guard detection_pattern against an empty tag set
Address PR review feedback: - detection_pattern(()) with an empty tag set compiled to an overly-broad regex (the empty alternation matches any [start ...]/[end ...] run), which would turn the forgery scanner into a false-positive generator. Reject an empty or all-empty tag set up front. Not reachable from the sole caller today, but it is a public, security-relevant helper. - Clarify build_operator_instruction_declaration's docstring: the trusted region is delimited by both the start and end markers (each carrying the nonce), not just the opening marker.
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from turnstone.core import fence
|
||||
|
||||
|
||||
@@ -154,3 +156,12 @@ class TestDetectionPattern:
|
||||
assert pat.search("x [ end tool_output_abcd] y") # leading whitespace
|
||||
assert pat.search("x [end tool_output_abcd] y") # run after keyword
|
||||
assert pat.search("x [start system-reminder] y") # bare, run after keyword
|
||||
|
||||
def test_empty_tag_set_rejected(self) -> None:
|
||||
# An empty (or all-empty) tag set would compile to an overly-broad regex
|
||||
# matching any "[start …]"/"[end …]" run — reject it rather than turn the
|
||||
# forgery scanner into a false-positive generator.
|
||||
with pytest.raises(ValueError):
|
||||
fence.detection_pattern(())
|
||||
with pytest.raises(ValueError):
|
||||
fence.detection_pattern(("",))
|
||||
|
||||
@@ -141,8 +141,14 @@ def detection_pattern(tags: Iterable[str]) -> re.Pattern[str]:
|
||||
can tell a leaked exact-nonce marker from a bare or wrong-nonce forgery.
|
||||
Only the tag prefix is anchored, so a marker is caught whether or not its hex
|
||||
matches a real nonce.
|
||||
|
||||
Raises ``ValueError`` on an empty tag set: an empty alternation would compile
|
||||
to ``(?:)`` and match *any* ``[start …]`` / ``[end …]`` run, turning the
|
||||
forgery scanner into a false-positive generator.
|
||||
"""
|
||||
alt = "|".join(re.escape(t) for t in tags)
|
||||
alt = "|".join(re.escape(t) for t in tags if t)
|
||||
if not alt:
|
||||
raise ValueError("detection_pattern requires at least one non-empty tag")
|
||||
return re.compile(
|
||||
rf"\[\s*(?:{_OPEN_KW}|{_CLOSE_KW})\s+(?:{alt})(_[0-9a-f]+)?",
|
||||
re.IGNORECASE,
|
||||
|
||||
@@ -92,9 +92,10 @@ def build_operator_instruction_declaration(nonce: str) -> str:
|
||||
|
||||
Declares the per-session *nonce* as the sole trusted ``system-reminder``
|
||||
marker so the fold (:func:`turnstone.core.fence.wrap`) can rely on it: the
|
||||
model trusts only ``[start system-reminder_{nonce}]`` blocks and treats every
|
||||
other ``system-reminder``-style marker (e.g. one forged in tool output,
|
||||
files, or web pages) as untrusted data. Emitted only when the model uses
|
||||
model trusts only the region delimited by ``[start system-reminder_{nonce}]``
|
||||
… ``[end system-reminder_{nonce}]`` (both boundaries carry the nonce) and
|
||||
treats every other ``system-reminder``-style marker (e.g. one forged in tool
|
||||
output, files, or web pages) as untrusted data. Emitted only when the model uses
|
||||
the fold path — the native mid-conversation-system path (claude-opus-4-8,
|
||||
claude-fable-5) delivers operator turns as real ``{"role":"system"}``
|
||||
messages with no fence, so no marker appears.
|
||||
|
||||
Reference in New Issue
Block a user