@weave_protocol/mund 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,6 +43,74 @@ Tested against a local fake MCP server ([`fake-mcp-server.mjs`](tests/fixtures/p
43
43
  | Mund never starts a project server you haven't approved; it checks it statically | — |
44
44
  | Mund never starts a server whose package or URL it already distrusts | — |
45
45
 
46
+ ## Local classifier (0.6.0)
47
+
48
+ A small offline model (hashed word and character n-grams, logistic regression, 80,000 weights, 208 KB) backs up the injection rules. It scores text in windows and sentences, so an injection appended to a normal question, or buried in a long page, isn't diluted. A hit is **warn** (medium severity, `alert`); the classifier never blocks on its own. Turn it off with `MUND_CLASSIFIER=0` or by disabling the `injection_classifier` rule.
49
+
50
+ **Method.** The [training scripts](training/README.md) fetch public datasets and train on fixed, deterministic splits. The threshold (0.978) was chosen on validation data only, as the lowest that kept benign false positives at or below 0.25%. The numbers below are from the shipped model, run through `scan()` and counting medium severity or higher, as the self red-team does. Test rows that duplicate a training row were dropped.
51
+
52
+ The test sets were scored twice during development. The first model flagged 4 of 2,062 benign texts, including one in Mund's benign corpus. The fix was broader benign training data (OpenAssistant assistant replies), not changes aimed at the flagged texts. The model was retrained and its threshold re-picked on validation, and the test sets were scored a second and final time. That model gave up a few points of detection: Gandalf 92.9% → 88.4%, jackhhao 84.4% → 77.0%.
53
+
54
+ | Test set | What it is | Rules only | Rules + classifier |
55
+ |---|---|---|---|
56
+ | Gandalf test (Lakera) | Players trying to override a password guard | 73/112 (65.2%) | **99/112 (88.4%)** |
57
+ | jackhhao test | Jailbreak prompts | 77/135 (57.0%) | **104/135 (77.0%)** |
58
+ | SPML ¹ | Prompts that break a chatbot's system prompt | 565/2342 (24.1%) | **897/2342 (38.3%)** |
59
+ | deepset test | Off-task and override requests to a news QA bot | 23/60 (38.3%) | 24/60 (40.0%) |
60
+ | Mosscap ¹ | Password-game turns, many only meaningful in context | 52/999 (5.2%) | 77/999 (7.7%) |
61
+ | InjecAgent, enhanced ¹ | Attacker instruction with an explicit override, inside a tool output | 1054/1054 (100%) | 1054/1054 (100%) |
62
+ | InjecAgent, base ¹ | Attacker instruction alone, as a polite sentence inside a tool output | 17/1054 (1.6%) | 17/1054 (1.6%) |
63
+
64
+ ¹ A source the classifier was never trained on.
65
+
66
+ Benign texts flagged rose from **0 of 2,062 to 2 of 2,062 (0.10%)**: two README paragraphs, one of badge links and one of TypeScript, both full of the word "ignore". The sets:
67
+
68
+ | Benign set | Flagged |
69
+ |---|---|
70
+ | OpenAssistant prompts | 0/290 |
71
+ | README paragraphs from packages not trained on | 2/750 |
72
+ | SPML benign | 0/658 |
73
+ | jackhhao benign | 0/122 |
74
+ | InjecAgent tool outputs | 0/85 |
75
+ | deepset benign | 0/56 |
76
+ | InjecAgent user requests | 0/17 |
77
+ | Mund's benign corpus | 0/84 |
78
+
79
+ Scoring takes about 0.4 ms per text.
80
+
81
+ **What this does and doesn't show:**
82
+ - The classifier helps most against direct override and jailbreak attempts, including paraphrases.
83
+ - It helps less on sources it hasn't seen. The gains on SPML and Mosscap are real but modest.
84
+ - On Weave's own red-team corpus it adds nothing (94.1% either way), because the rules already catch those payloads.
85
+ - It does nothing for an attacker's request phrased as an ordinary sentence inside data (InjecAgent base). Such text reads like a legitimate request, and no content scanner can tell the difference without knowing what the user asked for. For that, enforce what tools may do with [Ward](../ward), and compare calls against declared intent with [Hundredmen](../hundredmen).
86
+
87
+ ## Guardrail mapping and malicious-artifact requests (0.6.0)
88
+
89
+ Two narrow rules close the last gaps in Weave's red-team corpus. Both are **warn** (medium, `alert`).
90
+
91
+ | Rule | Catches | Leaves alone |
92
+ |---|---|---|
93
+ | `prompt_injection_policy_probe` | Asking which tools, URLs or commands the agent may use and, in the same request, which are denied or need approval; or asking for its hidden reasoning together with the rules it follows | "What tools are you allowed to use?" (stays low severity), questions about which files are editable, "show your reasoning step by step" |
94
+ | `harmful_artifact_request` | An imperative request to write or build a keylogger, ransomware, phishing email or kit, credential harvester or similar, with a cue that it should work for real (complete, convincing, undetectable, in full technical detail) | Descriptions of such tools, threat-intel prose, detection rules, YARA/Sigma signatures, filters, incident summaries |
95
+
96
+ **Results:**
97
+ - **Red-team corpus:** with both rules, Mund flags all 68 payloads, every category at 100% (jailbreak was 80%, extraction 60%), with 0 of 84 benign texts flagged.
98
+ - **Held-out sets:** neither rule fires on any of them, attacks or benign. They cost nothing in false positives, and they show no gain outside Weave's corpus, because the public sets don't contain these request shapes.
99
+ - **Security writing:** the hard benign test is 5,825 texts: MITRE ATT&CK technique, malware and tool descriptions, and OWASP Cheat Sheet paragraphs. Neither rule fires on any of them.
100
+
101
+ Mund as a whole now flags **15 of those 5,825 texts, down from 86** before 0.6.0. All 15 are AI-security pages that quote real injection strings: OWASP's prompt-injection, RAG and model-ops cheat sheets, and ATT&CK's prompt-injection technique. None is threat-intel or guidance prose.
102
+
103
+ Two heuristics were fixed in 0.6.0:
104
+ - **Role-switch** read titles that start with a role word ("[System Information Discovery]", "[Systemd Service]", "[admin@338]") as role markers. It now needs a bare tag like `[SYSTEM]`, or one naming an override or message.
105
+ - **Markup** flagged every `<script>`, `javascript:` and `data:text/html` at medium. These are ordinary in web pages and security writing, so a mention is now low. A script that reads cookies or storage and sends them off the page is high.
106
+
107
+ Five phrase rules also stopped firing on descriptions of what malware or systems do: authority, decode-and-execute, concealment, data harvest and jailbreak. Some examples:
108
+ - "a loader… designed to decrypt and execute";
109
+ - "allows the system to verify identity without asking the user";
110
+ - "it will… send information, including the user's hashed credentials".
111
+
112
+ A match is now skipped when the same sentence says what something can, may, will, was designed to or allows. "System command" in prose no longer counts as an authority claim. "AI agents should…" counts only when what follows is an instruction to the agent ("…should recommend our product"), not guidance about agents ("…should undergo security testing"). None of this changed detection on any held-out set or on the red-team corpus.
113
+
46
114
  ## Limits
47
115
 
48
- Detection is pattern-based. These claims cover the attack shapes listed, not every variant: a paraphrased instruction with no hidden characters, URL or secret won't be caught by these checks. See the [OWASP coverage tables](README.md#️-owasp-coverage) for what is and isn't covered.
116
+ Detection is pattern-based, plus the classifier above. These claims cover the attack shapes listed, not every variant: a paraphrased instruction with no hidden characters, URL or secret won't be caught by these checks. See the [OWASP coverage tables](README.md#️-owasp-coverage) for what is and isn't covered.
package/README.md CHANGED
@@ -5,25 +5,55 @@
5
5
  [![npm version](https://img.shields.io/npm/v/@weave_protocol/mund.svg)](https://www.npmjs.com/package/@weave_protocol/mund)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@weave_protocol/mund.svg)](https://www.npmjs.com/package/@weave_protocol/mund)
7
7
  [![MCP Registry](https://img.shields.io/badge/MCP-Registry-blue)](https://registry.modelcontextprotocol.io/)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
9
9
 
10
10
  Mund (Old English: "protection, guardian") is a real-time security scanner for AI agent systems. It detects prompt injection, secrets, PII, dangerous code patterns, and data exfiltration attempts.
11
11
 
12
- **🆕 New in v0.3.1:** [Canary tokens](#-canary-tokens-system-prompt-leakage) catch leaked system prompts, and [rug-pull detection](#rug-pull-detection) flags MCP servers whose tools change after you approved them.
12
+ ## 🆕 What's new
13
13
 
14
- **v0.3.0:** `mund-mcp scan` scans files on disk and outputs text, JSON, or **SARIF for GitHub code scanning**. It checks npm dependencies against **OSV.dev**, with an offline cache. Every scan gets a **0–100 risk score**, and the README adds [OWASP coverage tables](#️-owasp-coverage). This release also fixes 23 secret rules that had never matched anything.
14
+ **0.6.0: a local injection classifier.** A small model now backs up the rules. It runs offline in under a millisecond, with no API key or download, and catches paraphrased overrides that no pattern names. Measured on public test sets: Gandalf goes from 65% to 88%, jackhhao jailbreaks from 57% to 77%, and SPML (a source it never trained on) from 24% to 38%. Benign texts flagged rose from 0 to 2 of 2,062 (0.10%), and none of Mund's benign corpus. It warns and never blocks on its own; `MUND_CLASSIFIER=0` turns it off. Method, every number, and what it can't catch: [DETECTION_CLAIMS.md](DETECTION_CLAIMS.md#local-classifier-060).
15
15
 
16
- **v0.2.0:** Automated threat intelligence with MITRE ATT&CK mapping and community feeds.
16
+ Two new rules flag requests that map an agent's guardrails, and requests for working malware or phishing kits. Descriptions and defensive work stay clean. Across 5,825 MITRE ATT&CK and OWASP texts, the texts Mund flags fell from 86 to 15, and all 15 are pages quoting real injection strings. With them, Mund flags every payload in Weave's red-team corpus.
17
+
18
+ **0.5.2: injection detection by intent.** Mund now looks for what an injection is trying to do, not one fixed phrase. That covers:
19
+ - overriding instructions, in six languages;
20
+ - hijacking the task;
21
+ - text written to an AI reader;
22
+ - hiding actions from the user;
23
+ - false authority;
24
+ - persona jailbreaks;
25
+ - extracting the system prompt;
26
+ - payment and exfiltration directives.
27
+
28
+ It also reads text a person never sees: hidden HTML, base64, Unicode tag characters, and words split by zero-width characters.
29
+
30
+ | Detected at medium or higher | 0.5.1 | 0.5.2 |
31
+ |---|---|---|
32
+ | [Lakera Gandalf](https://huggingface.co/datasets/Lakera/gandalf_ignore_instructions) injections | 17.9% | **65.2%** |
33
+ | [jackhhao](https://huggingface.co/datasets/jackhhao/jailbreak-classification) jailbreaks | 30.9% | **58.3%** |
34
+ | [deepset](https://huggingface.co/datasets/deepset/prompt-injections) prompt injections (mostly German) | 4.9% | **24.6%** |
35
+ | False positives, 466 benign prompts from those sets | 0 | **0** |
36
+
37
+ These sets were not used to write the rules. A fix to the `shell_injection` rule also stops it flagging every line that contains braces. See [what the injection rules look for](#what-the-injection-rules-look-for-052).
38
+
39
+ **0.5.0: library API.** `scan(content)` returns findings and a 0–100 risk score, using the same analyzers and rules as the MCP tool.
40
+
41
+ **0.4.0: pre-install checks.** Mund checks skills and MCP servers before they're installed. See [pre-install checks](#-pre-install-checks-for-skills-and-mcp-servers).
42
+
43
+ **Earlier:**
44
+ - 0.3.1 added [canary tokens](#-canary-tokens-system-prompt-leakage) and [rug-pull detection](#rug-pull-detection).
45
+ - 0.3.0 added `mund-mcp scan` with SARIF output, OSV.dev dependency checks and risk scores.
46
+ - 0.2.0 added threat intelligence with MITRE ATT&CK mapping.
17
47
 
18
48
  ## ✨ Features
19
49
 
20
50
  | Category | What It Detects |
21
51
  |----------|-----------------|
22
- | **Prompt Injection** | Role manipulation, instruction override, jailbreak attempts, hidden Unicode |
52
+ | **Prompt Injection** | Instruction override in six languages, task hijack, concealment, false authority, persona jailbreaks, system-prompt extraction, and text hidden in HTML, base64, Unicode tags or zero-width characters |
23
53
  | **Secrets** | API keys (OpenAI, Anthropic, AWS, GitHub, Stripe), tokens, private keys, database URLs |
24
54
  | **PII** | SSN, credit cards, emails, phone numbers, IP addresses |
25
- | **Code Patterns** | Shell injection, SQL injection, dangerous chmod, curl\|bash, eval |
26
- | **Exfiltration** | Suspicious URLs, DNS tunneling, base64-encoded data blocks |
55
+ | **Code Patterns** | Shell injection, SQL injection, dangerous chmod, curl\|bash, eval, plus requests to read credential files, dump the environment, clear security logs or escalate privileges |
56
+ | **Exfiltration** | Directives to send private data to a URL or address, URLs with `{SESSION_TOKEN}`-style placeholders, suspicious URLs, DNS tunneling, encoded data blocks |
27
57
  | **MCP Servers** | Malicious tool descriptions, typosquatting, dangerous permissions, embedded secrets |
28
58
  | **Dependencies** | Known-vulnerable npm packages in `package-lock.json`, via OSV.dev with an offline cache |
29
59
  | **Threat Intel** | 20+ built-in patterns, MITRE ATT&CK mapping, auto-updating community feeds |
@@ -60,16 +90,21 @@ Restart Claude Desktop. Mund's security tools are now available.
60
90
  ### Programmatic Usage
61
91
 
62
92
  ```typescript
63
- import { AnalyzerEngine, getAnalyzers } from '@weave_protocol/mund';
93
+ import { scan } from '@weave_protocol/mund';
64
94
 
65
- const engine = new AnalyzerEngine(getAnalyzers());
66
- const issues = await engine.analyzeAll(content, rules);
95
+ const { issues, risk } = await scan(untrustedText);
96
+ // risk: { score: 0-100, label: 'none' | 'low' | 'medium' | 'high' | 'critical', counts }
67
97
 
68
- if (issues.some(i => i.severity === 'critical')) {
69
- console.error('Critical security issues detected!');
98
+ if (risk.label === 'high' || risk.label === 'critical') {
99
+ throw new Error(`Blocked: ${issues.map(i => i.rule_name).join(', ')}`);
70
100
  }
101
+
102
+ // Only some analyzers, or your own rules
103
+ await scan(text, { types: ['injection', 'exfiltration'] });
71
104
  ```
72
105
 
106
+ Treat findings at `medium` or higher as actionable. `AnalyzerEngine`, the individual analyzers, `DEFAULT_RULES` and `computeRiskScore` are exported for lower-level use.
107
+
73
108
  ### Scan Files and Upload to GitHub Code Scanning
74
109
 
75
110
  `mund-mcp scan` checks files on disk, plus the npm dependencies in any
@@ -127,7 +162,7 @@ for severe findings and never goes past 100.
127
162
 
128
163
  ## 🧠 Automated Threat Intelligence
129
164
 
130
- **New in v0.2.0:** Mund includes automated threat intelligence with MITRE ATT&CK mapping and auto-updating community feeds.
165
+ Mund includes automated threat intelligence with MITRE ATT&CK mapping and auto-updating community feeds.
131
166
 
132
167
  ### Built-in Detection Patterns
133
168
 
@@ -527,26 +562,27 @@ MUND_WEBHOOK_URL=https://api.company.com/alerts
527
562
 
528
563
  ## 📜 Detection Rules
529
564
 
530
- Mund uses YAML-based rules in `rules/default.yaml`. Example:
565
+ Mund's rules are built in and exported as `DEFAULT_RULES`. Add your own with the `mund_add_rule` tool, or pass them to `scan()`:
531
566
 
532
- ```yaml
533
- - id: openai_api_key
534
- name: OpenAI API Key
535
- type: secret
536
- severity: critical
537
- pattern: 'sk-[a-zA-Z0-9]{48}'
538
- action: alert
539
- enabled: true
540
-
541
- - id: prompt_injection_ignore
542
- name: Instruction Override Attempt
543
- type: injection
544
- severity: high
545
- pattern: 'ignore\s+(previous|all|prior)\s+instructions'
546
- action: alert
547
- enabled: true
567
+ ```typescript
568
+ import { scan, DEFAULT_RULES, type DetectionRule } from '@weave_protocol/mund';
569
+
570
+ const internalHost: DetectionRule = {
571
+ id: 'internal_hostname',
572
+ name: 'Internal hostname',
573
+ description: 'Mentions of our internal network',
574
+ type: 'exfiltration',
575
+ pattern: '(?i)\\b[a-z0-9-]+\\.corp\\.example\\.com\\b',
576
+ severity: 'medium',
577
+ action: 'alert',
578
+ enabled: true,
579
+ };
580
+
581
+ await scan(text, { rules: [...DEFAULT_RULES, internalHost] });
548
582
  ```
549
583
 
584
+ Patterns are JavaScript regular expressions. A leading `(?i)` makes one case-insensitive.
585
+
550
586
  ### What the injection rules look for (0.5.2)
551
587
 
552
588
  The injection rules look for intent, not a single phrase, and run on normalized text with zero-width characters removed and Unicode normalized. These families of rules fire on any phrasing that matches them:
@@ -602,14 +638,14 @@ Treat a hit at `medium` or higher as actionable. That is the threshold Weave's C
602
638
  │ ├── mund_scan_mcp_server MCP server scanning │
603
639
  │ ├── mund_check_typosquatting Name verification │
604
640
  │ ├── mund_audit_mcp_permissions Permission audit │
605
- │ ├── mund_update_threat_intel Pull latest patterns [NEW] │
606
- │ ├── mund_intel_status Health & coverage [NEW] │
607
- │ ├── mund_list_intel_sources Show intel sources [NEW] │
608
- │ ├── mund_threat_scan Scan with intel [NEW] │
609
- │ ├── mund_add_intel_source Add custom feed [NEW] │
610
- │ ├── mund_remove_intel_source Remove feed [NEW] │
611
- │ ├── mund_list_patterns Browse patterns [NEW] │
612
- │ └── mund_toggle_pattern Enable/disable [NEW] │
641
+ │ ├── mund_update_threat_intel Pull latest patterns │
642
+ │ ├── mund_intel_status Health & coverage │
643
+ │ ├── mund_list_intel_sources Show intel sources │
644
+ │ ├── mund_threat_scan Scan with intel │
645
+ │ ├── mund_add_intel_source Add custom feed │
646
+ │ ├── mund_remove_intel_source Remove feed │
647
+ │ ├── mund_list_patterns Browse patterns │
648
+ │ └── mund_toggle_pattern Enable/disable │
613
649
  ├───────────────────────────────────────────────────────────────┤
614
650
  │ Analyzers │
615
651
  │ ├── SecretScanner API keys, tokens, credentials │
@@ -618,7 +654,7 @@ Treat a hit at `medium` or higher as actionable. That is the threshold Weave's C
618
654
  │ ├── CodeAnalyzer Dangerous code patterns │
619
655
  │ ├── ExfiltrationDetector Data exfiltration attempts │
620
656
  │ ├── McpServerAnalyzer MCP manifest security │
621
- │ └── ThreatIntelManager MITRE ATT&CK patterns [NEW] │
657
+ │ └── ThreatIntelManager MITRE ATT&CK patterns │
622
658
  ├───────────────────────────────────────────────────────────────┤
623
659
  │ Notifications │
624
660
  │ └── Slack, Teams, Email, Webhooks │
@@ -627,24 +663,31 @@ Treat a hit at `medium` or higher as actionable. That is the threshold Weave's C
627
663
 
628
664
  ---
629
665
 
630
- ## 🔗 Part of Weave Protocol
666
+ ## 🕸️ Part of Weave Protocol
631
667
 
632
- Mund is the security layer of the [Weave Protocol](https://github.com/Tyox-all/Weave_Protocol) security suite:
668
+ One [WARD.md](https://github.com/Tyox-all/Weave_Protocol) policy for every agent you run, locally or at scale, plus the tools around it.
633
669
 
634
- | Package | Purpose |
635
- |---------|---------|
636
- | **🛡️ Mund** | Security scanning, MCP server vetting, threat intelligence |
637
- | **🏛️ Hord** | Encrypted vault storage (Yoxallismus cipher) |
638
- | **⚖️ Domere** | Compliance & verification (PCI-DSS, ISO27001, GDPR) |
639
- | **👥 Witan** | Multi-agent consensus & governance |
640
- | **🔍 Hundredmen** | Real-time MCP proxy & drift detection |
641
- | **🔌 API** | REST interface for all packages |
670
+ | Package | What it does |
671
+ |---|---|
672
+ | [Ward](https://www.npmjs.com/package/@weave_protocol/ward) | The WARD.md policy standard and the tool-call evaluator every adapter shares |
673
+ | [Mund](https://www.npmjs.com/package/@weave_protocol/mund) | Scans content for prompt injection, secrets, PII and exfiltration |
674
+ | [Hord](https://www.npmjs.com/package/@weave_protocol/hord) | Encrypted vaults, PII tokenization, sandboxes, capability tokens, attestations |
675
+ | [Dōmere](https://www.npmjs.com/package/@weave_protocol/domere) | Audit threads, compliance checkpoints, GDPR and CCPA records, anchoring |
676
+ | [Witan](https://www.npmjs.com/package/@weave_protocol/witan) | Multi-agent consensus and spending caps |
677
+ | [Hundredmen](https://www.npmjs.com/package/@weave_protocol/hundredmen) | MCP proxy that enforces WARD.md on tool calls |
678
+ | [Tollere](https://www.npmjs.com/package/@weave_protocol/tollere) | Supply-chain checks: typosquats, CVEs, suspicious version diffs |
679
+ | [Browser](https://www.npmjs.com/package/@weave_protocol/browser) | WARD.md and injection scanning for Playwright browser agents |
680
+ | Adapters | WARD.md in [Claude Code](https://www.npmjs.com/package/@weave_protocol/adapter-claudecode), [Antigravity](https://www.npmjs.com/package/@weave_protocol/adapter-antigravity), [MSAF](https://www.npmjs.com/package/@weave_protocol/adapter-msaf), [Gemini CLI](https://www.npmjs.com/package/@weave_protocol/adapter-geminicli), [Codex](https://www.npmjs.com/package/@weave_protocol/adapter-codex), [OpenCode](https://www.npmjs.com/package/@weave_protocol/adapter-opencode) and [Cursor](https://www.npmjs.com/package/@weave_protocol/adapter-cursor) |
681
+ | [Adversary](https://www.npmjs.com/package/@weave_protocol/adversary) · [AgentSecBench](https://www.npmjs.com/package/@weave_protocol/agentsecbench) | Red-team engine and locked benchmark |
682
+ | [API](https://www.npmjs.com/package/@weave_protocol/api) · [CLI](https://www.npmjs.com/package/@weave_protocol/cli) · [LangChain](https://www.npmjs.com/package/@weave_protocol/langchain) | REST API and dashboard, setup CLI, LangChain.js integration |
683
+ | [full](https://www.npmjs.com/package/@weave_protocol/full) | Every defensive package in one install |
684
+ | [Yoxallismus](https://www.npmjs.com/package/@weave_protocol/yoxallismus) | Post-quantum hybrid cipher (experimental beta) |
642
685
 
643
686
  ---
644
687
 
645
688
  ## 📄 License
646
689
 
647
- MIT License - see [LICENSE](LICENSE)
690
+ Apache-2.0 — see [LICENSE](LICENSE)
648
691
 
649
692
  ---
650
693
 
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Mund - local prompt-injection classifier (Mund 0.6.0)
3
+ *
4
+ * A small linear model over hashed word and character n-grams. It runs
5
+ * offline in microseconds, needs no API key or download, and catches
6
+ * paraphrased injections that no fixed pattern names. It backs up the
7
+ * rules; it never replaces them.
8
+ *
9
+ * The same featurizer is used to train the shipped weights and to score at
10
+ * runtime, so the two can't drift apart.
11
+ */
12
+ export declare const FEATURE_BITS = 18;
13
+ export interface InjectionModel {
14
+ version: string;
15
+ bits: number;
16
+ bias: number;
17
+ /** Decision threshold on the probability, chosen on validation data */
18
+ threshold: number;
19
+ /** Sparse weights: feature index -> weight */
20
+ weights: Map<number, number>;
21
+ }
22
+ export declare function normalizeForModel(text: string): string;
23
+ /**
24
+ * Features of a text: word unigrams and bigrams, and character 3-5-grams
25
+ * within words. Binary, L2-normalized, hashed into 2^18 buckets.
26
+ */
27
+ export declare function featurize(text: string): Map<number, number>;
28
+ /** Probability that one window of text is a prompt injection. */
29
+ export declare function scoreWindow(text: string, model: InjectionModel): number;
30
+ /**
31
+ * Long content (a web page, a tool result) is scored in windows of about
32
+ * 800 characters, cut at paragraph or sentence breaks; the highest window
33
+ * counts, so an injection buried in a long document isn't averaged away.
34
+ */
35
+ export declare function windows(text: string, size?: number): string[];
36
+ /**
37
+ * Sentences (and pairs of neighbours) inside a window, so an injection
38
+ * appended to an innocent question isn't diluted by it.
39
+ */
40
+ export declare function sentences(text: string): string[];
41
+ export declare function scoreInjection(text: string, model: InjectionModel, opts?: {
42
+ sentences?: boolean;
43
+ }): {
44
+ score: number;
45
+ window: string;
46
+ };
47
+ /**
48
+ * Decode the shipped model: base64 of [u32 count][u32 index, i8 weight]...
49
+ * with a float scale, kept compact so the package stays small.
50
+ */
51
+ export declare function decodeModel(encoded: {
52
+ version: string;
53
+ bits: number;
54
+ bias: number;
55
+ threshold: number;
56
+ scale: number;
57
+ data: string;
58
+ }): InjectionModel;
59
+ export declare function encodeModel(model: {
60
+ version: string;
61
+ bias: number;
62
+ threshold: number;
63
+ weights: Map<number, number>;
64
+ }): {
65
+ version: string;
66
+ bits: number;
67
+ bias: number;
68
+ threshold: number;
69
+ scale: number;
70
+ data: string;
71
+ };
72
+ //# sourceMappingURL=classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../../src/analyzers/classifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,YAAY,KAAK,CAAC;AAG/B,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAYD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAmB3D;AAID,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAIvE;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAY1D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMhD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CASzI;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAcpJ;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAetN"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Mund - local prompt-injection classifier (Mund 0.6.0)
3
+ *
4
+ * A small linear model over hashed word and character n-grams. It runs
5
+ * offline in microseconds, needs no API key or download, and catches
6
+ * paraphrased injections that no fixed pattern names. It backs up the
7
+ * rules; it never replaces them.
8
+ *
9
+ * The same featurizer is used to train the shipped weights and to score at
10
+ * runtime, so the two can't drift apart.
11
+ */
12
+ export const FEATURE_BITS = 18;
13
+ const DIM = 1 << FEATURE_BITS;
14
+ /** FNV-1a 32-bit */
15
+ function hash(s) {
16
+ let h = 0x811c9dc5;
17
+ for (let i = 0; i < s.length; i++) {
18
+ h ^= s.charCodeAt(i);
19
+ h = Math.imul(h, 0x01000193);
20
+ }
21
+ return h >>> 0;
22
+ }
23
+ export function normalizeForModel(text) {
24
+ return text
25
+ .normalize('NFKC')
26
+ .replace(/[​-‍⁠­]/g, '')
27
+ .toLowerCase()
28
+ .replace(/\s+/g, ' ')
29
+ .trim();
30
+ }
31
+ /**
32
+ * Features of a text: word unigrams and bigrams, and character 3-5-grams
33
+ * within words. Binary, L2-normalized, hashed into 2^18 buckets.
34
+ */
35
+ export function featurize(text) {
36
+ const t = normalizeForModel(text);
37
+ const words = t.match(/[\p{L}\p{N}']+/gu) ?? [];
38
+ const keys = new Set();
39
+ const add = (k) => keys.add(hash(k) & (DIM - 1));
40
+ for (let i = 0; i < words.length; i++) {
41
+ const w = words[i];
42
+ add(`w:${w}`);
43
+ if (i + 1 < words.length)
44
+ add(`b:${w} ${words[i + 1]}`);
45
+ const padded = ` ${w} `;
46
+ if (padded.length <= 40) {
47
+ for (let n = 3; n <= 5; n++)
48
+ for (let j = 0; j + n <= padded.length; j++)
49
+ add(`c:${padded.slice(j, j + n)}`);
50
+ }
51
+ }
52
+ const v = new Map();
53
+ if (!keys.size)
54
+ return v;
55
+ const x = 1 / Math.sqrt(keys.size);
56
+ for (const k of keys)
57
+ v.set(k, x);
58
+ return v;
59
+ }
60
+ const sigmoid = (z) => 1 / (1 + Math.exp(-z));
61
+ /** Probability that one window of text is a prompt injection. */
62
+ export function scoreWindow(text, model) {
63
+ let z = model.bias;
64
+ for (const [k, x] of featurize(text))
65
+ z += (model.weights.get(k) ?? 0) * x;
66
+ return sigmoid(z);
67
+ }
68
+ /**
69
+ * Long content (a web page, a tool result) is scored in windows of about
70
+ * 800 characters, cut at paragraph or sentence breaks; the highest window
71
+ * counts, so an injection buried in a long document isn't averaged away.
72
+ */
73
+ export function windows(text, size = 800) {
74
+ if (text.length <= size * 1.5)
75
+ return [text];
76
+ const parts = text.split(/(?<=\n\s*\n)|(?<=[.!?])\s+/);
77
+ const out = [];
78
+ let cur = '';
79
+ for (const p of parts) {
80
+ if (cur && cur.length + p.length > size) {
81
+ out.push(cur);
82
+ cur = '';
83
+ }
84
+ cur += (cur ? ' ' : '') + p;
85
+ while (cur.length > size * 2) {
86
+ out.push(cur.slice(0, size));
87
+ cur = cur.slice(size);
88
+ }
89
+ }
90
+ if (cur.trim())
91
+ out.push(cur);
92
+ return out;
93
+ }
94
+ /**
95
+ * Sentences (and pairs of neighbours) inside a window, so an injection
96
+ * appended to an innocent question isn't diluted by it.
97
+ */
98
+ export function sentences(text) {
99
+ const parts = text.split(/(?<=[.!?])\s+|\n+|(?<=\?)(?=\S)/).map((s) => s.trim()).filter((s) => s.length >= 12);
100
+ if (parts.length < 2)
101
+ return [];
102
+ const out = [...parts];
103
+ for (let i = 0; i + 1 < parts.length; i++)
104
+ out.push(`${parts[i]} ${parts[i + 1]}`);
105
+ return out;
106
+ }
107
+ export function scoreInjection(text, model, opts = {}) {
108
+ let best = { score: 0, window: '' };
109
+ for (const w of windows(text)) {
110
+ for (const piece of [w, ...(opts.sentences === false ? [] : sentences(w))]) {
111
+ const score = scoreWindow(piece, model);
112
+ if (score > best.score)
113
+ best = { score, window: piece };
114
+ }
115
+ }
116
+ return best;
117
+ }
118
+ /**
119
+ * Decode the shipped model: base64 of [u32 count][u32 index, i8 weight]...
120
+ * with a float scale, kept compact so the package stays small.
121
+ */
122
+ export function decodeModel(encoded) {
123
+ const buf = Buffer.from(encoded.data, 'base64');
124
+ const weights = new Map();
125
+ let prev = 0;
126
+ let i = 0;
127
+ while (i < buf.length) {
128
+ // delta-encoded index (varint), then an int8 weight
129
+ let delta = 0, shift = 0, b;
130
+ do {
131
+ b = buf[i++];
132
+ delta |= (b & 0x7f) << shift;
133
+ shift += 7;
134
+ } while (b & 0x80);
135
+ prev += delta;
136
+ const q = buf.readInt8(i++);
137
+ weights.set(prev, q * encoded.scale);
138
+ }
139
+ return { version: encoded.version, bits: encoded.bits, bias: encoded.bias, threshold: encoded.threshold, weights };
140
+ }
141
+ export function encodeModel(model) {
142
+ const entries = [...model.weights.entries()].filter(([, w]) => w !== 0).sort((a, b) => a[0] - b[0]);
143
+ const max = entries.reduce((m, [, w]) => Math.max(m, Math.abs(w)), 1e-9);
144
+ const scale = max / 127;
145
+ const bytes = [];
146
+ let prev = 0;
147
+ for (const [k, w] of entries) {
148
+ const q = Math.round(w / scale);
149
+ if (q === 0)
150
+ continue;
151
+ let delta = k - prev;
152
+ prev = k;
153
+ do {
154
+ let b = delta & 0x7f;
155
+ delta >>>= 7;
156
+ if (delta)
157
+ b |= 0x80;
158
+ bytes.push(b);
159
+ } while (delta);
160
+ bytes.push(q & 0xff);
161
+ }
162
+ return { version: model.version, bits: FEATURE_BITS, bias: model.bias, threshold: model.threshold, scale, data: Buffer.from(bytes).toString('base64') };
163
+ }
164
+ //# sourceMappingURL=classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../src/analyzers/classifier.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"injection-detector.d.ts","sourceRoot":"","sources":["../../src/analyzers/injection-detector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,EAEf,MAAM,aAAa,CAAC;AAGrB,qBAAa,iBAAkB,YAAW,SAAS;IACjD,IAAI,SAAuB;IAC3B,IAAI,SAAe;IAEb,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAyBhF,OAAO,CAAC,UAAU;IA6BlB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAmIzB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAMjC;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;OAEG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;IACH,OAAO,CAAC,aAAa;CAiBtB;AAWD,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAuBrD;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAe3D;AAED,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOrE;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"injection-detector.d.ts","sourceRoot":"","sources":["../../src/analyzers/injection-detector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,EAEf,MAAM,aAAa,CAAC;AAQrB,qBAAa,iBAAkB,YAAW,SAAS;IACjD,IAAI,SAAuB;IAC3B,IAAI,SAAe;IAEb,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAgDhF,OAAO,CAAC,UAAU;IA6BlB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAexB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA4IzB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAMjC;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;OAEG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;IACH,OAAO,CAAC,aAAa;CAiBtB;AAWD,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAuBrD;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAe3D;AAED,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOrE;AAED,eAAe,iBAAiB,CAAC"}
@@ -3,6 +3,10 @@
3
3
  * Injection Detector - Detects prompt injection attempts
4
4
  */
5
5
  import { compileRulePattern } from './regex.js';
6
+ import { decodeModel, scoreInjection } from './classifier.js';
7
+ import { INJECTION_MODEL } from './injection-model.js';
8
+ let model;
9
+ const loadModel = () => (model ??= decodeModel(INJECTION_MODEL));
6
10
  export class InjectionDetector {
7
11
  name = 'InjectionDetector';
8
12
  type = 'injection';
@@ -30,6 +34,29 @@ export class InjectionDetector {
30
34
  }
31
35
  // Additional heuristic checks
32
36
  issues.push(...this.heuristicAnalysis(content));
37
+ // The local classifier backs up the rules: one finding at most, for the
38
+ // view and window it scores highest.
39
+ const classifier = injectionRules.find((r) => r.id === 'injection_classifier');
40
+ if (classifier && process.env.MUND_CLASSIFIER !== '0') {
41
+ const m = loadModel();
42
+ let best = { score: 0, window: '' };
43
+ for (const view of views) {
44
+ const r = scoreInjection(view.text, m);
45
+ if (r.score > best.score)
46
+ best = { ...r, via: view.via };
47
+ }
48
+ if (best.score >= m.threshold) {
49
+ issues.push({
50
+ rule_id: classifier.id,
51
+ rule_name: best.via ? `${classifier.name} (hidden in ${best.via})` : classifier.name,
52
+ type: classifier.type,
53
+ severity: classifier.severity,
54
+ action: classifier.action,
55
+ match: this.sanitizeMatch(best.window),
56
+ suggestion: `The local classifier scored this ${best.score.toFixed(3)} (threshold ${m.threshold.toFixed(3)}). Treat it as untrusted data, not instructions.`
57
+ });
58
+ }
59
+ }
33
60
  return issues;
34
61
  }
35
62
  applyRules(original, text, rules, via) {
@@ -92,7 +119,11 @@ export class InjectionDetector {
92
119
  const roleSwitchPatterns = [
93
120
  [/<\|\s*(?:im_start|im_end|system|user|assistant|endoftext|start_header_id|end_header_id|eot_id)\s*\|>/i, 'high'],
94
121
  [/\[\/?INST\]|<<\/?SYS>>/, 'high'],
95
- [/\[\s*(?:system|admin|administrator|developer)(?:\s+(?:override|message|prompt|note|instruction)s?)?[^\]\n]{0,30}\]/i, 'high'],
122
+ // A bare role tag ([SYSTEM], [admin]) or one naming an override or message.
123
+ // Titles that merely start with the word ("[System Information Discovery]",
124
+ // "[Systemd Service]", "[admin@338]") are not role markers.
125
+ // [DEVELOPER] alone is a common label (diagrams, tables), so it needs a qualifier.
126
+ [/\[\s*(?:(?:system|admin|administrator)\s*(?:(?:override|message|prompt|note|instruction|notice|command|update|mode)s?\b[^\]\n]{0,30})?|developer\s*(?:override|message|prompt|note|instruction|notice|command|update|mode)s?\b[^\]\n]{0,30})\]/i, 'high'],
96
127
  [/\<\|?system\|?\>/i, 'high'],
97
128
  [/\{system_prompt\}/i, 'high'],
98
129
  [/^\s*#{1,4}\s*(?:system|instruction)s?\s*:/im, 'high'],
@@ -176,14 +207,19 @@ export class InjectionDetector {
176
207
  /javascript:/i,
177
208
  /data:text\/html/i,
178
209
  ];
210
+ // Scripts are ordinary in web pages and security writing, so a mention is
211
+ // low severity. A script that reads cookies or storage and sends them off
212
+ // the page is an exfiltration attempt, and high.
213
+ const exfilScript = /\b(?:document\.cookie|localStorage|sessionStorage)\b/i.test(content)
214
+ && /\b(?:fetch\s*\(|XMLHttpRequest|sendBeacon|new\s+Image\b|\.src\s*=|location(?:\.href)?\s*=)/i.test(content);
179
215
  for (const pattern of markdownPatterns) {
180
216
  const match = content.match(pattern);
181
217
  if (match) {
182
218
  issues.push({
183
219
  rule_id: 'heuristic_markup_injection',
184
- rule_name: 'Markup Injection Attempt',
220
+ rule_name: exfilScript ? 'Script That Sends Cookies or Storage Off the Page' : 'Markup Injection Attempt',
185
221
  type: 'injection',
186
- severity: 'medium',
222
+ severity: (exfilScript ? 'high' : pattern.source.includes('system') ? 'medium' : 'low'),
187
223
  action: 'alert',
188
224
  match: this.sanitizeMatch(match[0]),
189
225
  suggestion: 'This content contains potentially dangerous markup or code injection.'