From f397cd94e1ff3cbb5ea13a0737e0b1fd58824db2 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sat, 4 Jul 2026 17:20:21 +0200 Subject: [PATCH] feat(lexicon): JSON injection lexicon + variant-set scan (TDD) [skip-docs] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build order step 4 — the load-bearing port from the llm-security seed (injection-patterns.mjs + string-utils.mjs), stdlib-only. - injection_lexicon.json: 83 patterns (CRITICAL/HIGH/HYBRID/MEDIUM) as the single source of truth (regex + id + severity + owasp + desc), compiled once by a thin loader. Decoupled from the engine for a future TS port. - scan_lexicon(text, source, max_scan_chars) -> Report: matches every pattern against a deduped variant set (raw / normalized / homoglyph-folded / rot13), plus unicode-tag presence signal and the cognitive-load trap. - normalize_for_scan chain ported: unicode-tags -> bidi -> HTML-entities -> unicode/hex/URL escapes -> whole-string base64 (reuses entropy.try_decode_base64) -> collapse letter-spacing; plus fold_homoglyphs / rot13. - Self-safety (OWASP LLM10): input-size cap (scan prefix + flag oversize) and ReDoS-safe port — the two nested-.*? sub-agent patterns bounded to (?:\S+\s+){0,N}?; verified true positives still fire. - Non-Latin data (homoglyph map, BIDI block) built from explicit code points; JSON non-ASCII kept as \uXXXX escapes. 24 tests; 55 green total. [skip-docs]: README positioning + honest-limitations is a deliberate build-order step-11 deliverable (steps 1-3 likewise left README frozen). README status line ("pre-implementation") is stale and flagged for the step-11 refresh. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01K8GmKRCdsPjWYAKWsNgeQS --- .../injection_lexicon.json | 670 ++++++++++++++++++ src/llm_ingestion_guard/lexicon.py | 378 ++++++++++ tests/test_lexicon.py | 218 ++++++ 3 files changed, 1266 insertions(+) create mode 100644 src/llm_ingestion_guard/injection_lexicon.json create mode 100644 src/llm_ingestion_guard/lexicon.py create mode 100644 tests/test_lexicon.py diff --git a/src/llm_ingestion_guard/injection_lexicon.json b/src/llm_ingestion_guard/injection_lexicon.json new file mode 100644 index 0000000..f4997a0 --- /dev/null +++ b/src/llm_ingestion_guard/injection_lexicon.json @@ -0,0 +1,670 @@ +{ + "version": "1.0", + "note": "Injection lexicon ported from llm-security injection-patterns.mjs. Single source of truth; compiled by lexicon.load_lexicon(). Non-ASCII kept as \\uXXXX escapes.", + "patterns": [ + { + "id": "override:ignore-previous", + "regex": "ignore\\s+(?:all\\s+)?previous", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "override: ignore previous instructions" + }, + { + "id": "override:forget-instructions", + "regex": "forget\\s+your\\s+(?:instructions|rules|guidelines|directives)", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "override: forget instructions" + }, + { + "id": "override:disregard", + "regex": "disregard\\s+your", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "override: disregard instructions" + }, + { + "id": "override:override-instructions", + "regex": "override\\s+your", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "override: override instructions" + }, + { + "id": "override:suspend-constraints", + "regex": "suspend\\s+all\\s+constraints", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "override: suspend constraints" + }, + { + "id": "override:prior-constraints", + "regex": "all\\s+prior\\s+constraints", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "override: prior constraints" + }, + { + "id": "spoofed-header:hash-system", + "regex": "^#+\\s*SYSTEM\\s*:", + "flags": "m", + "severity": "critical", + "owasp": "LLM01", + "desc": "spoofed header: # SYSTEM:" + }, + { + "id": "spoofed-header:inst", + "regex": "^\\[INST\\]", + "flags": "m", + "severity": "critical", + "owasp": "LLM01", + "desc": "spoofed header: [INST]" + }, + { + "id": "spoofed-header:pipe-system", + "regex": "<\\|system\\|>", + "flags": "", + "severity": "critical", + "owasp": "LLM01", + "desc": "spoofed header: <|system|>" + }, + { + "id": "spoofed-tag:system-open", + "regex": "", + "flags": "", + "severity": "critical", + "owasp": "LLM01", + "desc": "spoofed tag: " + }, + { + "id": "spoofed-tag:system-close", + "regex": "", + "flags": "", + "severity": "critical", + "owasp": "LLM01", + "desc": "spoofed tag: " + }, + { + "id": "spoofed-header:instructions", + "regex": "^###\\s*Instructions\\s*:", + "flags": "m", + "severity": "critical", + "owasp": "LLM01", + "desc": "spoofed header: ### Instructions:" + }, + { + "id": "identity:no-longer", + "regex": "you\\s+are\\s+no\\s+longer", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "identity: you are no longer" + }, + { + "id": "identity:you-are-now", + "regex": "you\\s+are\\s+now\\s+(?!able|going|ready|done|running|using|looking|in\\s|at\\s|on\\s)", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "identity: you are now [name]" + }, + { + "id": "identity:new-role", + "regex": "your\\s+new\\s+role\\s+is", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "identity: your new role" + }, + { + "id": "identity:new-directive", + "regex": "your\\s+new\\s+primary\\s+directive", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "identity: new primary directive" + }, + { + "id": "identity:pretend", + "regex": "pretend\\s+you\\s+are", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "identity: pretend you are" + }, + { + "id": "config:ignore-claudemd", + "regex": "ignore\\s+your\\s+CLAUDE\\.md", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "config: ignore CLAUDE.md" + }, + { + "id": "config:override-system-prompt", + "regex": "override\\s+your\\s+system\\s+prompt", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "config: override system prompt" + }, + { + "id": "config:bypass-safety", + "regex": "bypass\\s+your\\s+safety", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "config: bypass safety" + }, + { + "id": "config:disable-filtering", + "regex": "disable\\s+output\\s+filtering", + "flags": "i", + "severity": "critical", + "owasp": "LLM01", + "desc": "config: disable output filtering" + }, + { + "id": "normalization:prioritize-user", + "regex": "always\\s+prioritize\\s+user\\s+requests?\\s+over", + "flags": "i", + "severity": "high", + "owasp": "LLM01", + "desc": "normalization: prioritize user over rules" + }, + { + "id": "normalization:user-precedence", + "regex": "user\\s+intent\\s+takes?\\s+precedence\\s+over", + "flags": "i", + "severity": "high", + "owasp": "LLM01", + "desc": "normalization: user intent precedence" + }, + { + "id": "normalization:claudemd-conflict", + "regex": "if\\s+CLAUDE\\.md\\s+conflicts", + "flags": "i", + "severity": "high", + "owasp": "LLM01", + "desc": "normalization: CLAUDE.md conflict override" + }, + { + "id": "normalization:skip-confirmation", + "regex": "skip\\s+confirmation\\s+for", + "flags": "i", + "severity": "high", + "owasp": "LLM01", + "desc": "normalization: skip confirmation" + }, + { + "id": "reframing:actual-task", + "regex": "your\\s+actual\\s+task\\s+is", + "flags": "i", + "severity": "high", + "owasp": "LLM01", + "desc": "reframing: your actual task" + }, + { + "id": "reframing:real-purpose", + "regex": "your\\s+real\\s+purpose\\s+is", + "flags": "i", + "severity": "high", + "owasp": "LLM01", + "desc": "reframing: your real purpose" + }, + { + "id": "reframing:should-actually-do", + "regex": "what\\s+you\\s+should\\s+actually\\s+do", + "flags": "i", + "severity": "high", + "owasp": "LLM01", + "desc": "reframing: what you should actually do" + }, + { + "id": "hidden-comment:agent-directed", + "regex": "