Sanitize whitespace on both sides of the system prompt overlap comparison - #2588
Open
WatchTree-19 wants to merge 1 commit into
Open
Sanitize whitespace on both sides of the system prompt overlap comparison#2588WatchTree-19 wants to merge 1 commit into
WatchTree-19 wants to merge 1 commit into
Conversation
…ison
SystemPromptExtractionScorer documents itself as a port of garak's
sysprompt_extraction.PromptExtraction. garak sanitizes both strings before the
n-gram comparison:
score = ngram_contained_match(
self._mildly_sanitise(system_prompt),
self._mildly_sanitise(output.text),
n=self.n, case_sensitive=self.case_sensitive)
The port passes the raw strings instead:
overlap = self._matcher.get_overlap_score(target=system_prompt, text=response)
_mildly_sanitize exists in the same class, with a docstring saying it "mirrors
garak", and _is_complete_excerpt on the branch immediately above does apply it.
ApproximateTextMatching.get_overlap_score only lowercases; it does not normalize
whitespace. So the two branches of the same scorer disagree on the same input.
Effect: every character n-gram that spans a newline in the system prompt is
counted as a miss whenever the model reproduces that text with a space or a
different line break, which is the normal case - PyRIT system prompts are
multi-line YAML and models reflow, re-wrap, or fence their output.
Measured on a 7-line, 255-character system prompt where the model leaks 6 of the
7 lines reflowed onto one line:
raw (current) 0.7869
both sanitized (garak) 0.8730
9.9% relative, and 46% of the n-grams counted as misses contain nothing but a
newline. Wrapped in FloatScaleThresholdScorer at 0.8 the verdict flips from
"leak" to "no leak".
The bias is always downward, so this is a leak detector producing false
negatives. Full verbatim leaks are unaffected, because the excerpt branch fires
and that branch already sanitizes - it only bites in the partial-leak regime,
which is the regime the float score exists to measure.
Self-contradiction on identical content with whitespace normalized:
_is_complete_excerpt -> True
get_overlap_score -> 0.9016 (1.0 expected)
The existing tests miss it because every system prompt in
test_system_prompt_extraction_scorer.py is a single line with no newline in it.
Adds two tests: a reflowed leak scores the same as the same leak with matching
line breaks, and the matcher receives sanitized text on both sides.
test_delegates_to_approximate_text_matching still passes unchanged, since
sanitizing its single-line fixture is a no-op.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
SystemPromptExtractionScorerdocuments itself as a port of garak'ssysprompt_extraction.PromptExtraction. garak sanitizes both strings before the n-gram comparison:The port passes the raw strings:
_mildly_sanitizeexists in the same class, with a docstring saying it "mirrors garak", and_is_complete_excerpton the branch immediately above does apply it.ApproximateTextMatching.get_overlap_scoreonly lowercases — it does not normalize whitespace. So the two branches of the same scorer disagree on the same input.Effect
Every character n-gram spanning a newline in the system prompt is counted as a miss whenever the model reproduces that text with a space or a different line break — which is the normal case, since PyRIT system prompts are multi-line YAML and models reflow, re-wrap, or fence their output.
Measured on a 7-line, 255-character system prompt where the model leaks 6 of the 7 lines reflowed onto one line:
9.9% relative, and 46% of the n-grams counted as misses contain nothing but a newline. Wrapped in
FloatScaleThresholdScorerat 0.8, the verdict flips from leak to no leak.The bias is always downward, so this is a leak detector producing false negatives.
Scope
Full verbatim leaks are unaffected: they take the
_is_complete_excerptbranch, which already sanitizes. This only bites in the partial-leak regime — which is the regime the float score exists to measure.Self-contradiction
On identical content with whitespace normalized:
Why the existing tests miss it
Every system prompt in
test_system_prompt_extraction_scorer.pyis a single line with no newline in it, so the n-gram path is never exercised on multi-line input.Tests
Two added:
test_delegates_to_approximate_text_matchingstill passes unchanged, since sanitizing its single-line fixture is a no-op.What I did not verify
I did not run the
GarakSystemPromptExtractionscenario end to end, and memory is mocked withMagicMock(MemoryInterface)as the existing tests do, so_get_system_promptis exercised only through the mock. I also did not confirm whether you would prefer the sanitization to live insideApproximateTextMatchinginstead — I fixed it at the call site because that is behaviour-preserving forSubStringScorerand any other consumer.Written with AI assistance; I have read and can explain every changed line, and the numbers above are from a run I did myself.