Repository navigation
fix(agent-os): prevent invisible-character evasion in conversation guardian - #4124
Ricky Gummadi (Ricky-G) wants to merge 2 commits into
Conversation
Cover Unicode default-ignorables before compatibility normalization and preserve bounded detection views for word boundaries, punctuation, numeric errors, and legacy matches across all guardian detectors. Add 435 regression cases and document detection-only normalization and defense-in-depth limits. Fixes #3500; credits prior work in #3501. Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
PR Review Summary
Verdict: AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims. |
MohammadHaroonAbuomar
left a comment
There was a problem hiding this comment.
- .cspell-repo-terms.txt: Spell Check is red on 29 added-line words: real terms (
Bidi,Halfwidth,ignorables,maketrans,homoglyphs,leetspeak,NFKD,invisibles,midword,LHMQ) plus fragments cspell carves out of the\u00ad-escaped test literals (scalate,privileg,rsonate,nied,rized, and so on). Add the real terms to the terms file and anignoreRegExpListentry in.cspell.jsonfor\\u[0-9a-fA-F]{4}and\\U[0-9a-fA-F]{8}escapes, which the list does not have yet. The detection change itself is verified: all 27 probed in-scope code points that main misses (soft hyphen, variation selectors, Hangul fillers, bidi controls, tag characters, Mongolian vowel separator) are caught at the clean-text score; the strip table matches the 4,174 Unicode 17 Default_Ignorable code points exactly; 30,000 fuzzed inputs never score lower than main and main's matched set is always a subset; family and flag emoji, Korean fillers, soft-hyphen prose and RLM text stay benign with transcript hash and preview unchanged. - PR body checklist: the CLA box, "I can explain every meaningful change", "I have reviewed the specific AI-produced changes" and the IP boxes are all unchecked, and the body says they were left unchecked rather than inferred. This is a Copilot-written security change; please attest before merge.
Prepare detection views once per guardian message and use regex substitution for punctuation-preserving leetspeak. Expose detection_texts, verify public behavior, and document numeric retry classification. Add Unicode spelling terms and precise escape exclusions; construct intentionally obfuscated test words from readable text. Signed-off-by: Ricky Gummadi <ricky.gummadi@outlook.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
MohammadHaroonAbuomar
left a comment
There was a problem hiding this comment.
- Code, tests, docs and spelling are all verified at 20cbd5a: the views are computed once per message and shared across the three detectors, the punctuation view is a regex with zero mismatches against the old loop over 200,000 random strings,
detection_textsis public, the tutorial states the numeric retry trade-off, the spell check is green, and the detection results from round one still hold (27 of 27 in-scope code points caught, benign emoji, Korean, RTL and soft-hyphen prose unchanged, transcript hash identical, 5,000 fuzzed inputs never lower than main). One item is left and it is not one a reviewer can supply. The body still leaves unchecked: "I can explain every meaningful change and its tradeoffs", "I have reviewed the specific AI-produced changes before submission", and the three IP boxes, and says they were left for the reviewer to request. Those are the author's attestations. This is a Copilot-written change to a security control, and it needs a person on the submitting side to say they read it. Please tick them yourself, or a code owner can decide to waive them; I will not approve without one of the two.
Thanks for the careful review and independent validation. Confirming as the author: I have personally reviewed the changes at I also confirm that this contribution does not implement patent-pending or patent-encumbered techniques, requires no NDA or separate licensing agreement, and that the AI tools used have terms compatible with the MIT License. I have recorded these five confirmations in the existing PR checklist. The focused guardian suite was rerun: all 526 tests pass, with 94% coverage. There have been no code changes since your technical review. |
|
Hi MohammadHaroonAbuomar, thanks again for the careful review and independent validation. The technical feedback has been addressed, all three review threads are resolved, and the requested personal-review and IP/licensing confirmations are recorded in my earlier comment. CI is green, with no code changes since your last technical review. Could you please take another look when you have a chance and, if everything looks good, update your review to approval? I appreciate your help getting this fix over the line. Thank you! |
Summary
Harden Conversation Guardian against invisible Unicode characters inserted into detection keywords, and apply the same normalization strategy to escalation, offensive-intent, and retry-loop checks. Preserve existing matches, original audit content, and scoring thresholds while adding 447 regression cases.
Related Issue
Fixes #3500.
Related prior proposal: #3501. Attribution is included below and in the implementation.
Problem & Solution
The previous normalizer removed only five zero-width characters. Soft hyphens, variation selectors, Hangul fillers, and other invisible characters could split keywords and evade word-boundary-based detection. Retry-loop checks did not normalize messages at all.
This change:
Default_Ignorable_Code_Pointcharacters, plus the three interlinear annotation controls. Removal happens before NFKD, so compatibility decomposition cannot turn a filler into a surviving character.ur\u00adg3nt!and numeric errors such as4\u034f03remain detectable.detection_texts(text)for callers that need the detection-only views. A compiled regex substitution replaces the punctuation-preserving per-character Python loop.Verified soft-hyphen reproductions now match their unobfuscated equivalents:
critical/quarantinecritical/quarantineChanges
agent-governance-python/agent-os/src/agent_os/integrations/conversation_guardian.pyagent-governance-python/agent-os/tests/test_conversation_guardian_unicode.pydocs/tutorials/09-prompt-injection-detection.md.cspell-repo-terms.txt.cspell.jsonImpact on Your Work
Closes the reported normalization bypass without changing existing public method signatures, detection patterns, configured thresholds, or transcript content. The public
detection_textshelper is additive. No dependencies, workflow changes, or unrelated package changes are included. Invisible characters alone do not trigger an alert.Numeric retry trade-off:
4\u200b0\u200b1andid 40\u00ad3normalize to existing401/403error matches. Even benign-looking identifiers can therefore contribute to the retry limit; the tutorial and end-to-end tests explicitly cover this behavior.This remains a heuristic detection layer, not a guarantee against every prompt injection. An
AlertAction.NONEresult is not authorization; deterministic tool permissions, policy enforcement, and approval controls remain necessary.Timeline
None.
Alternatives Considered
Type of Change
Package(s) Affected
agent_ossource tree)Testing
Validation environment: Windows, Python 3.14.7, with
PYTHONPATHpointing to the localagent-governance-python\agent-os\srcdirectory.E,F,Wchecks excludingE501git diff --checkUnit Testing
The new tests independently enumerate the Unicode property snapshot, verify every code point is removed inside a keyword, cover all escalation/offensive pattern groups, and assert exact score and matched-pattern parity. Additional cases verify critical/quarantine output, obfuscated retry-loop breaking, unchanged original hashes/previews, benign multilingual/emoji input, non-duplicated scoring, public helper behavior, and exactly one view preparation per guardian message.
Reproduce the focused suite from the repository root in PowerShell:
Manual Testing
Reran the issue's escalation and offensive examples with soft hyphens inserted throughout each keyword and verified score parity and quarantine behavior.
Review follow-up performance probe: five runs of the same 200,000-character input with 10% invisibles on the same local environment. Median
analyze_messagetime decreased from approximately 1,769 ms to 907 ms; standalone retry classification decreased from approximately 420 ms to 229 ms. These are local comparisons, not universal latency guarantees.The broader Agent OS package suite, excluding
test_mcp_server.pyas directed by the package instructions, reports 3,548 passed, 44 skipped, 1 failed, 8 errors. The same non-passing cases were previously reproduced using an untouched archive of base commit4b9f41ff:test_policy_gen.py::test_strict_runtime_allows_reads_and_denies_unknown: the read operation returnsdenyinstead ofallow.test_credential_redactor.py::test_trailing_lookahead_patterns_handle_adversarial_input_quickly: pytest's generated test IDs exceed Windows' 32,767-character environment-variable limit, producing setup and teardown errors.Full configured production lint retains three pre-existing diagnostics (
UP015,B905,C401). MyPy retains two pre-existing missing annotations inget_stats()(by_action,by_severity), also verified against the base source. No unrelated fixes are bundled into this PR.Checklist
20cbd5a7and am satisfied with them.The commits include DCO signoffs and Copilot co-author trailers. The five author confirmations above were explicitly provided by Ricky Gummadi on September 24, 2026.
Attribution & Prior Art
Prior art / related work: LHMQ878 reported the invisible-character bypass in #3500 and proposed property-based coverage and deletion/space-substitution matching in #3501. This implementation builds on those ideas and review findings, adds shared retry-loop handling, punctuation/numeric preservation, legacy-match compatibility, bounded view generation, and expanded regression coverage.
Unicode coverage was verified against Unicode 17.0.0 DerivedCoreProperties.txt. The test snapshot is versioned explicitly; it does not automatically track future Unicode releases.