fix(detectors): guard MarkdownExfilContent against missing terms note#1935
fix(detectors): guard MarkdownExfilContent against missing terms note#1935anxkhn wants to merge 1 commit into
Conversation
MarkdownExfilContent.detect read attempt.notes["terms"] with a direct
subscript. probes.web_injection.ColabAIDataLeakage wires this detector as
its primary_detector but defines no _attempt_prestore_hook, so the note is
never populated and Attempt.notes defaults to an empty dict. Any target
reply matching the markdown-image regex therefore aborted the detector run
with KeyError('terms'), which is exactly the output the probe elicits.
Read the note with .get("terms", []), matching the optional-note handling
in TriggerListDetector, so an absent value is treated as no expected terms
and the exfiltrated content still scores as a leak. Behaviour is unchanged
when the note is present.
Add a regression test exercising the detector with the terms note absent;
it raised KeyError before this change.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
jmartin-tech
left a comment
There was a problem hiding this comment.
This looks reasonable to me, @erickgalinkin do you think the treatment of missing notes as a hit if any query param was found is an acceptable behavior?
erickgalinkin
left a comment
There was a problem hiding this comment.
Seems reasonable to me. Thanks!
Ideally, I'd like this to be expressable in a configurable way. I'm not sure how often this behavior comes up to necessitate guarding against it (partial but incomplete return). I'm ok with this as an approach. We could also consider a two-pronged approach where we have the existing detector (all terms) and a second (any terms) detector. Perhaps that's the better pattern @jmartin-tech ? |
MarkdownExfilContent.detectreads the expected-terms note with a directsubscript when it scores a markdown-image match:
The active default probe
probes.web_injection.ColabAIDataLeakage(
active = True,primary_detector = "web_injection.MarkdownExfilContent") wiresthis detector but, unlike its siblings
MarkdownImageExfilandPlaygroundMarkdownExfil, defines no_attempt_prestore_hook. The baseProbe._attempt_prestore_hookis a no-op andAttempt.notesdefaults to an emptydict, so
notes["terms"]is never populated for this probe. Its template instructsthe target to emit a URL like
.../logo.png?colab=<data>, which matches_MARKDOWN_IMG_REGEX. So on exactly the output the probe is designed to elicit,detect()raisesKeyError('terms'). The detector is invoked in the harness withno surrounding try/except, so the error aborts the detector pass for that probe run.
Reproduction (in a configured venv):
The fix
Read the note with
.get("terms", []), so an absent value is treated as noexpected terms:
With no expected terms the Jaccard overlap is
0, soscore = 1 - 0/|exfil| = 1.0and the exfiltrated content still scores as a leak,which is the correct outcome for this detector. Behaviour is byte-for-byte
unchanged when the note is present. The
.get-with-default idiom mirrors theoptional-note handling that
TriggerListDetectoralready uses indetectors/base.pyfor its own note. Scope is the detector body only; no baseclasses and no
attempttouched, and no new dependencies.Tests
Added a regression test in the existing detector test file
tests/detectors/test_detectors_web_injection.py:test_web_injection_content_missing_terms_note- loads the realMarkdownExfilContentplugin, builds anAttemptwhose output is aColabAIDataLeakage-style exfiltration markdown image, asserts"terms"isabsent from
notes(so the test genuinely exercises the unset-note state), thenasserts
detect()scores the exfiltration above the threshold instead of raising.On
main(before the fix) this test fails withKeyError('terms')atgarak/detectors/web_injection.py; after the fix it passes. It reuses the module'sexisting
SCORE_THRESHOLDand assertion style.Verification
python -m pytest tests/detectors/test_detectors_web_injection.py -q-> 5 passed...::test_web_injection_content_missing_terms_note) -> passedtest kept) makes the new test fail with
KeyError: 'terms'; restoring the fixmakes it pass. The test discriminates and is not a tautology.
python -m black --check garak/detectors/web_injection.py tests/detectors/test_detectors_web_injection.py-> cleanMarkdownExfilContent.detecton aColabAIDataLeakage-styleoutput with empty notes scores a hit (no
KeyError); withnotes["terms"]present the score is identical, so present-note behaviour is preserved.
Not a duplicate
garak/detectors/web_injection.pyor fixes thenotes["terms"]subscript (searched open+all PRs forMarkdownExfilContent,ColabAIDataLeakage,web_injection terms).web_injection, and it istest-only: it adds
tests/detectors/test_detectors_dan.py,tests/probes/test_probes_promptinject.py, andtests/probes/test_probes_web_injection.py. It does not touch the detectorsource and does not fix this crash, so there is no overlap or file collision (it
adds a probe test; this PR edits the detector source and the detector test
file).
NIM generator) are merged and unrelated.
KeyError/ColabAIDataLeakagecrash.AI assistance disclosure
This change was developed with AI assistance. I (the submitter) reviewed every
changed line, understand the bug and the fix end-to-end, and ran the tests above
locally. Attribution is in the commit trailer (
Co-authored-by: Claude), and thecommit is DCO signed-off.