@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.
- package/DETECTION_CLAIMS.md +69 -1
- package/README.md +93 -50
- package/dist/analyzers/classifier.d.ts +72 -0
- package/dist/analyzers/classifier.d.ts.map +1 -0
- package/dist/analyzers/classifier.js +164 -0
- package/dist/analyzers/classifier.js.map +1 -0
- package/dist/analyzers/injection-detector.d.ts.map +1 -1
- package/dist/analyzers/injection-detector.js +39 -3
- package/dist/analyzers/injection-detector.js.map +1 -1
- package/dist/analyzers/injection-model.d.ts +9 -0
- package/dist/analyzers/injection-model.d.ts.map +1 -0
- package/dist/analyzers/injection-model.js +6 -0
- package/dist/analyzers/injection-model.js.map +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +35 -6
- package/dist/constants.js.map +1 -1
- package/package.json +1 -1
- package/src/analyzers/classifier.ts +156 -0
- package/src/analyzers/injection-detector.ts +40 -3
- package/src/analyzers/injection-model.ts +5 -0
- package/src/constants.ts +35 -6
- package/tests/analyzers/classifier.test.ts +84 -0
- package/tests/analyzers/directives.test.ts +53 -0
- package/tests/analyzers/injection-and-code.test.ts +16 -0
package/DETECTION_CLAIMS.md
CHANGED
|
@@ -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
|
[](https://www.npmjs.com/package/@weave_protocol/mund)
|
|
6
6
|
[](https://www.npmjs.com/package/@weave_protocol/mund)
|
|
7
7
|
[](https://registry.modelcontextprotocol.io/)
|
|
8
|
-
[](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
|
-
|
|
12
|
+
## 🆕 What's new
|
|
13
13
|
|
|
14
|
-
**
|
|
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
|
-
|
|
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** |
|
|
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** |
|
|
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 {
|
|
93
|
+
import { scan } from '@weave_protocol/mund';
|
|
64
94
|
|
|
65
|
-
const
|
|
66
|
-
|
|
95
|
+
const { issues, risk } = await scan(untrustedText);
|
|
96
|
+
// risk: { score: 0-100, label: 'none' | 'low' | 'medium' | 'high' | 'critical', counts }
|
|
67
97
|
|
|
68
|
-
if (
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
```
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
|
606
|
-
│ ├── mund_intel_status Health & coverage
|
|
607
|
-
│ ├── mund_list_intel_sources Show intel sources
|
|
608
|
-
│ ├── mund_threat_scan Scan with intel
|
|
609
|
-
│ ├── mund_add_intel_source Add custom feed
|
|
610
|
-
│ ├── mund_remove_intel_source Remove feed
|
|
611
|
-
│ ├── mund_list_patterns Browse patterns
|
|
612
|
-
│ └── mund_toggle_pattern Enable/disable
|
|
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
|
|
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
|
-
##
|
|
666
|
+
## 🕸️ Part of Weave Protocol
|
|
631
667
|
|
|
632
|
-
|
|
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 |
|
|
635
|
-
|
|
636
|
-
|
|
|
637
|
-
|
|
|
638
|
-
|
|
|
639
|
-
|
|
|
640
|
-
|
|
|
641
|
-
|
|
|
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
|
-
|
|
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;
|
|
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
|
-
[
|
|
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.'
|