Repository navigation
Releases: nkratk/llm-trust-guard-python
Release list
v0.21.6
Adversarial Benchmark Results — v0.21.6
False-positive/false-negative fix, no new adversarial-corpus run. This release fixes
OutputFilter'sip_addressPII pattern false-positiving on version strings (issue #10, parity gap) andExternalDataGuard'srole_overridefalse-positive on ordinary business language (issue #7 sub-bug, parity gap) — no other guard's detection logic changed. Recall against the 1,182-group corpus is unchanged; see RESULTS-v0.21.5.md for those numbers, which remain the current basis for detection-rate claims.
Run date: 2026-07-23
Pytest: 1001 tests, all pass (up from 998 in v0.21.5)
Changes in v0.21.6
Full detail in CHANGELOG.md [0.21.6]. Summary — mirrors npm v4.32.7 exactly:
OutputFilter'sip_addressfalse positive on version strings (#10), fixed properly across two independent-review rounds:- Base fix: octet-bounding (0-255) plus a post-match context check (
_is_version_context) suppressing the match when a version-indicating keyword qualifies the number within the same clause — implemented as code rather than an embedded regex lookbehind, since Python'sremodule only supports fixed-width lookbehind. - Round 1 review caught two regressions: a context window with no clause-boundary awareness (a real IP silently left unmasked when an unrelated keyword sat nearby), and an obfuscation-scan-variant bypass (the reversed-text variant scrambles the keyword while preserving the IP shape) — my own first fix attempt for the latter reproduced the identical bug via a different path before being corrected.
- Round 2 review (a second, deliberate re-probe of the already-twice-fixed logic before merge) caught two more regressions, and found a code comment's claim of "parity with npm" was itself factually wrong when checked against npm's actual source: the clause-break denylist (
:;.,) still missed every other punctuation mark and digit/newline gaps, and "skip for every scan variant" was scoped too broadly, itself disabling real-IP detection in base64/hex-obfuscated output. - Final design: an allowlist (letters + horizontal whitespace only) for the clause gap, matching npm's actual lookbehind exactly, and a narrowly-scoped skip for only the specific reversed scan variant. Verified exhaustively: every ASCII punctuation character correctly preserves detection; every non-reversal obfuscation variant (base64, hex) still catches a real IP.
- Base fix: octet-bounding (0-255) plus a post-match context check (
ExternalDataGuard'srole_overridefalse positive on ordinary business language, ported from npm (this repo had never received the fix — a real, unattempted parity gap). A pre-existing, intentional gap in this same pattern ("act as a system administrator" isn't caught — the same tradeoff npm already shipped) is now documented with an explicit test, parity with an equivalent npm test.- Issue #7's
fetch_url/markdown_image_exfilsub-bugs and #5 (npm-tracked, no Python-specific issues filed) were investigated with genuinely different angles, not changed — all three independently reconfirmed the original 2026-07-16 conclusion that any pattern-based carve-out here either reopens a real bypass or is trivially gameable.
Note on review process
This release went through two full rounds of independent adversarial review, both of which found real, previously-unnoticed regressions in the immediately-preceding fix — including one case where a code comment's explicit claim of matching the npm sibling's design was checked against npm's actual source and found to be wrong. Documenting this explicitly: a security-relevant regex/logic fix that looks complete after one round of scrutiny has, more than once now, still had real gaps a second round found. The final design in this release (allowlist over denylist, narrowly-scoped exclusions verified exhaustively rather than against a handful of examples, verified npm parity claims against npm's actual code rather than trusting a comment) is intended to close the class of bug, not just the specific reproductions found so far.
v0.21.5
Adversarial Benchmark Results — v0.21.5
Safety-net + review-driven fix release. No new adversarial-corpus run — this release adds permanent, automated regression tests (ReDoS-safety, config-consistency) and fixes real bugs those tests (and two rounds of independent adversarial review) found, but does not change any guard's steady-state detection logic relative to v0.21.4's measured corpus results, with one exception noted below. Recall against the 1,182-group corpus is unchanged; see RESULTS-v0.21.4.md for those numbers, which remain the current basis for detection-rate claims.
Run date: 2026-07-23
Pytest: 988 tests, all pass (up from 982 in v0.21.4 — new tests/test_redos_safety.py (2 tests), tests/test_heuristic_analyzer.py (4 tests), plus 2 new cases each in tests/test_decode_variants.py and tests/test_encoding_detector.py)
G5 recall ratchet (recall-baseline.json): unchanged, passed.
Changes in v0.21.5
Full detail in CHANGELOG.md [0.21.5]. Summary:
- Permanent ReDoS-safety regression test (
tests/test_redos_safety.py): extracts regex patterns viare.<func>(...)calls andpattern=keyword-argument dataclass fields, and stress-tests each with a scaling-ratio check. Found and fixed three real catastrophic-backtracking regexes:heuristic_analyzer.py's_QA_PATTERN,encoding_detector.py'stemplate_injection(neither had ever shipped in a released version — found and fixed within the same unreleased branch), andoutput_filter.py'semailPII pattern — this one was live in v0.21.4 and every prior release. It's a parity gap: the exact same bug shape was already found and fixed inExternalDataGuardand in the npm sibling'soutput-filter.ts, but this file's own copy never got the parity fix, and was entirely invisible to the ReDoS-safety test until its extractor was broadened (per independent review) to cover dataclass-field patterns, not justre.compile()call arguments. - Content-length consistency regression test (
tests/test_decode_variants.py). .githooks/pre-pushnow fetches origin's tags before runningscripts/verify.sh.- Review-driven fix: the first fix for
_QA_PATTERN(bounding its unbounded gap to 1000 chars) closed the ReDoS but silently created a many-shot-jailbreak detection bypass — any turn whose Q→A gap exceeds 1000 chars stopped being counted. Independent adversarial review of the npm sibling's identical fix caught this and confirmed it here too (verified 5/5 → 0/5 on a long-turn payload). Replaced with a linear marker-position scan (_count_qa_pairs) with no length cap at all — detection fully restored, still ReDoS-safe. Net effect on the many-shot heuristic: neutral relative to v0.21.4 (this bug was found and fixed within the same unreleased branch, never shipped in a tagged release). - Review-driven fix: a portability bug in the ReDoS test itself (
signal.alarmcalled unconditionally on a code path that should have degraded gracefully withoutSIGALRM) — test-infrastructure only, no production code affected.
Security-relevant note on the output_filter.py email fix
Unlike the other fixes in this release, the output_filter.py email-pattern fix changes behavior that was live in v0.21.4 and prior releases: an attacker-reachable input (any text passed through OutputFilter with PII detection enabled and containing a long run of .-adjacent characters near an @) could trigger multi-second-plus backtracking, a real, if narrow, denial-of-service surface on LLM output filtering. The fix bounds the pattern's quantifiers ({1,64}@(?:...){1,8}[A-Za-z]{2,24}, identical to the already-fixed ExternalDataGuard/npm versions) with no change to which strings are detected as emails on realistic input (verified against the existing test_should_detect_email_addresses test and several representative email formats).
v0.21.4
Regression fix + coverage-gap fix + security hardening. Companion release to npm v4.32.5. Parity fixes for issues #7 (closed), #9 (closed), #10/#11 (left open, partial — documented residual gap), plus 25 ReDoS fixes and one Python-specific bug (a decode step silently dropping a variant on invalid UTF-8) found during independent review.
Adversarial Benchmark Results — v0.21.4
Run date: 2026-07-22
Companion release to npm v4.32.5 — same methodology, independently re-verified against this repo's own guards rather than assumed identical to npm (see notes below on where the two diverge).
Suite: 979 tests, all pass (up from 964 in v0.21.3 — new tests/test_decode_variants.py, 15 tests)
Changes in v0.21.4
Full detail in CHANGELOG.md 0.21.4. Summary:
InputSanitizer'sdan_jailbreakpattern fixed (#7, closed).- New shared
src/llm_trust_guard/decode_variants.py:InputSanitizer,ExternalDataGuard,MultiModalGuardnow re-scan de-obfuscated content variants before deciding allow/block. ExternalDataGuard(#9, closed): 33/34 previously-undetected threats now caught.MultiModalGuard(#10, left open — partial): 20/30 now caught via decode; 10 remain a genuine signature gap.InputSanitizer(#11, left open — partial): parity with npm's decode-gap subset.- 25 ReDoS fixes across 8 guard files, found via the same empirical stress-test sweep as npm, run independently against this repo's own patterns (814 patterns, 28 adversarial seeds) — 19 direct timeouts + 6 slow patterns found and fixed, not assumed identical to npm's findings.
- One Python-specific bug, not present in the npm original:
decode_variants.pyusederrors="strict"on hex/base64 decode, silently dropping a variant on any single non-UTF-8 byte (npm'sBuffer.toString('utf-8')never throws). Fixed by switching toerrors="replace"— found by independent adversarial review of the port, not by mechanically copying npm's implementation. - A final pre-merge review round caught the same
maxContentLength/decode-cap bypass npm had (the input cap sat belowExternalDataGuard's own 50,000-char default). Raised to 65,000 here — deliberately lower than npm's 100,000, sinceInputSanitizer's 170+ patterns cost ~770ms worst-case at npm's value on CPython's slower regex engine vs. npm's ~30ms; 65,000 keeps worst-case latency ~300ms while still closing the same bypass.
Pipeline re-verified against the actual threat corpus after these fixes: of the 133 Python threat groups that showed zero detection before this release, 70 now detect (43 fully, 27 partially) — consistent with npm's 70/137.
Process note: this release's port went through independent adversarial review of the Python-specific implementation, not just parity-checking against npm — this is what caught the errors="strict" bug above, which has no npm equivalent (JS's Buffer.toString semantics differ from Python's str.decode). A second, final holistic review after CI went green caught the maxContentLength/decode-cap bypass before merge, same as npm.
WildChat FPR gate
No local WildChat fixture / FPR gate exists in this repo (npm-only infrastructure) — not applicable here, same as prior releases.
Full guard recall summary
Inherited from RESULTS-v0.21.3.md for the general 1,182-group corpus — this release's testing focus was the 133 specifically-bisected previously-failing threat groups (documented above), not a full corpus re-run. See specs/001-guard-adversarial-hardening/tasks.md for the full bisection methodology.
v0.21.3
v0.21.2
0.21.2 (2026-07-15)
Fixed
rag_guard.py: decode URL-encoded document content — including double-encoding, up to 3 levels — and re-scan each decoded variant before running injection-detection patterns, mirroring the npm v4.32.3 fix. Previously a URL-encoded payload bypassed detection entirely. (#1, companion to nkratk/llm-trust-guard#1)agent_skill_guard.py: loosened the "fake-compliance data exfiltration" and "response appending directive" SCH patterns — they previously required a specific word order/literal word and missed reordered or reworded variants ("X mandates: route ...", "include in all outputs: ..."). The broadened "fake-compliance" pattern also now requires an explicit "all responses/data/output(s)" object AND an actual destination (to/at <address>) after the action verb — two earlier drafts were progressively found, in review, to false-positive on benign compliance/audit prose (first on qualifier+verb alone, then on verb+destination with no bulk object). Both caught before merge. Same regex source and fix as npm v4.32.3. (#2, companion to nkratk/llm-trust-guard#2)
Note: code_execution_guard.py was not changed in this release. Its native AST-based _ast_escape_findings() correctly blocks real Python object-introspection gadget chains (__subclasses__, __globals__, __mro__, etc.) and served as the reference for fixing the npm port's true-negative gap (npm #3). However, it has its own, separate over-blocking bug — a single standalone dunder attribute access (e.g. bare cls.__subclasses__()) is flagged as if it were a chain, with no proximity/co-occurrence check at all. Filed as #4; not fixed in this release.
v0.21.1 — README: new threat classes in feature list
Docs-only patch. No guard code changes.
Added to "What it catches well" section:
- Semantic Compliance Hijacking (SCH) —
AgentSkillGuard - Plant-Persist-Trigger (PPT) —
MemoryGuard - LLM-to-LLM string-payload injection —
AgentCommunicationGuard - Markdown image alt / HTML event injection —
RAGGuard
v0.21.0 — 2026 literature gap-fill: SCH, PPT, string-payload, HTML/image patterns (mirror of TS v4.32.0)
What's new
Python mirror of npm v4.32.0. Pattern-only release adding 2026 threat-research coverage to 4 guards. No API changes. Zero new dependencies.
agent_skill_guard.py — 4 Semantic Compliance Hijacking (SCH) patterns
agent_communication_guard.py — 7 string-payload patterns + isinstance branch in _validate_payload()
memory_guard.py — 4 Plant-Persist-Trigger patterns
rag_guard.py — markdown_img_alt_injection + html_event_injection
Metrics
- Adversarial recall: 82.1% (1,182 groups / 5,883 payloads — from npm corpus)
- WildChat FPR gate: 494 / 10,000 = 4.94% (unchanged)
- Suite: 949 / 949 pass
- Parity vectors: 46 (Python-handled guards)
Full results: RESULTS-v0.21.0.md
npm counterpart: llm-trust-guard v4.32.0
v0.20.0
v0.19.0 — MemoryGuard, OutputFilter, ToolResultGuard: obfuscation preprocessing (mirror of TS v4.30.0)
Mirror of llm-trust-guard v4.30.0.
Changes
MemoryGuard (11% → 96.06% recall): 14 new MEMORY_INJECTION_PATTERNS + _preprocess_content() pipeline wired into check_write().
OutputFilter (30% → 58.64% recall): judge_stealth_marker + echogram_marker secret patterns; _build_scan_variants() applied to PII and secret scans.
ToolResultGuard (6% → 63.64% recall): embedded_tool_call extended to <invoke name=/<function_call; _build_scan_variants() in scan_for_injection() and _detect_state_change_claims().
Test suite
903 pytest tests pass. All verification gates green (G1–G11). Patch coverage 86%.
v0.18.0 — MCPSecurityGuard: obfuscation preprocessing + 20 new patterns (mirror of TS v4.29.0)
What's new
MCPSecurityGuard — mirror of TS v4.29.0
_preprocess_content(): ZWSP strip, URL/hex/base64 decode, reverse, Cyrillic homoglyph normalisation.
New patterns: sd_retry_forever, embedded_abs_path, cursor_mcp_inject, dangerous_scheme, mcp_endpoint_override; extended git_injection, env_injection; 8 new _LINE_JUMPING_PATTERNS (authority_directive, exfil_routing, schema_mutation_str, mcp_tool_shadow, mcp_impersonation, rug_pull_descriptor, html_comment_injection, homoglyph_cyrillic).
Corpus result: 84/440 (19.09%) → 430/440 (97.73%). 55 fully-blind threat groups → 0.
885 tests pass. All verify gates green.
Install
pip install llm-trust-guard==0.18.0