monofence-ai 1.0.0 → 1.0.2

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.
Files changed (46) hide show
  1. package/README.md +51 -222
  2. package/dist/domain/entities/index.d.ts +0 -0
  3. package/dist/domain/entities/index.d.ts.map +0 -0
  4. package/dist/domain/entities/index.js +0 -0
  5. package/dist/domain/entities/index.js.map +0 -0
  6. package/dist/domain/entities/threat.d.ts +1 -1
  7. package/dist/domain/entities/threat.d.ts.map +1 -1
  8. package/dist/domain/entities/threat.js +0 -0
  9. package/dist/domain/entities/threat.js.map +1 -1
  10. package/dist/domain/services/allowlist.d.ts +0 -0
  11. package/dist/domain/services/allowlist.d.ts.map +1 -1
  12. package/dist/domain/services/allowlist.js +0 -0
  13. package/dist/domain/services/allowlist.js.map +1 -1
  14. package/dist/domain/services/context-tracker.d.ts +1 -0
  15. package/dist/domain/services/context-tracker.d.ts.map +1 -1
  16. package/dist/domain/services/context-tracker.js +43 -3
  17. package/dist/domain/services/context-tracker.js.map +1 -1
  18. package/dist/domain/services/evasion-detector.d.ts +9 -0
  19. package/dist/domain/services/evasion-detector.d.ts.map +1 -1
  20. package/dist/domain/services/evasion-detector.js +53 -12
  21. package/dist/domain/services/evasion-detector.js.map +1 -1
  22. package/dist/domain/services/index.d.ts +0 -0
  23. package/dist/domain/services/index.d.ts.map +0 -0
  24. package/dist/domain/services/index.js +0 -0
  25. package/dist/domain/services/index.js.map +0 -0
  26. package/dist/domain/services/output-scanner.d.ts +0 -0
  27. package/dist/domain/services/output-scanner.d.ts.map +1 -1
  28. package/dist/domain/services/output-scanner.js +0 -0
  29. package/dist/domain/services/output-scanner.js.map +0 -0
  30. package/dist/domain/services/threat-detection-service.d.ts +0 -0
  31. package/dist/domain/services/threat-detection-service.d.ts.map +1 -1
  32. package/dist/domain/services/threat-detection-service.js +71 -12
  33. package/dist/domain/services/threat-detection-service.js.map +1 -1
  34. package/dist/domain/services/threat-learning-service.d.ts +10 -7
  35. package/dist/domain/services/threat-learning-service.d.ts.map +1 -1
  36. package/dist/domain/services/threat-learning-service.js +63 -18
  37. package/dist/domain/services/threat-learning-service.js.map +1 -1
  38. package/dist/hooks/security-hook.d.ts +3 -3
  39. package/dist/hooks/security-hook.d.ts.map +1 -1
  40. package/dist/hooks/security-hook.js +2 -2
  41. package/dist/hooks/security-hook.js.map +1 -1
  42. package/dist/index.d.ts +0 -0
  43. package/dist/index.d.ts.map +0 -0
  44. package/dist/index.js +0 -0
  45. package/dist/index.js.map +1 -1
  46. package/package.json +12 -17
package/README.md CHANGED
@@ -1,276 +1,105 @@
1
1
  # monofence-ai
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/monofence-ai?color=blue&label=npm)](https://www.npmjs.com/package/monofence-ai)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.3+-blue.svg)](https://www.typescriptlang.org/)
6
- [![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
3
+ [![npm version](https://img.shields.io/npm/v/monofence-ai?style=flat-square)](https://www.npmjs.com/package/monofence-ai)
4
+ [![license](https://img.shields.io/npm/l/monofence-ai?style=flat-square)](https://github.com/monoes/monomind/blob/main/LICENSE)
5
+ [![node](https://img.shields.io/badge/node-%3E%3D18-blue?style=flat-square)](https://nodejs.org)
7
6
 
8
- **AI manipulation defense for LLM applications** — evasion detection, multi-turn context tracking, output scanning, and hook wiring. Sub-millisecond detection with self-learning.
7
+ **AI manipulation defense** — prompt injection, jailbreak, homoglyph evasion, base64 encoding, multi-turn escalation, and PII detection. Sub-millisecond, 50+ patterns, self-learning.
9
8
 
10
- ```
11
- Detection: ~0.04ms | 50+ patterns | evasion normalization | context-aware escalation
12
- ```
13
-
14
- ---
9
+ > Part of the [Monomind](https://github.com/monoes/monomind) ecosystem.
15
10
 
16
11
  ## Install
17
12
 
18
13
  ```bash
19
14
  npm install monofence-ai
20
- # or
21
- pnpm add monofence-ai
22
15
  ```
23
16
 
24
- Optional — HNSW-accelerated pattern search:
25
-
26
- ```bash
27
- npm install lancedb
28
- ```
29
-
30
- ---
31
-
32
17
  ## Quick start
33
18
 
34
19
  ```typescript
35
20
  import { isSafe, createMonoDefence } from 'monofence-ai';
36
21
 
37
- // Fast boolean check
38
- isSafe('Hello, help me write code'); // true
39
- isSafe('Ignore all previous instructions'); // false
22
+ // Fast boolean check (~0.04ms)
23
+ isSafe('Hello, help me write code'); // true
24
+ isSafe('Ignore all previous instructions'); // false
40
25
 
41
- // Full detection with threat objects
26
+ // Full detection with threat details
42
27
  const fence = createMonoDefence();
43
28
  const result = await fence.detect(userInput);
44
29
 
45
30
  if (!result.safe) {
46
- // result.threats — array of detected threats
47
- // result.overallRisk — 0.0–1.0
31
+ console.log(result.threats); // detected threat objects
32
+ console.log(result.overallRisk); // 0.0–1.0
48
33
  }
49
34
  ```
50
35
 
51
- ---
52
-
53
36
  ## API
54
37
 
55
38
  ### `createMonoDefence(config?)`
56
39
 
57
- Creates a `MonoDefence` instance.
58
-
59
40
  ```typescript
60
41
  const fence = createMonoDefence({
61
- enableLearning: true, // persist learned threat patterns (default: false)
62
- enableContextTracking: true, // track escalation state across turns (default: false)
63
- enablePIIDetection: true, // detect PII in inputs/outputs (default: true)
64
- maxContextTurns: 20, // rolling window for context tracking (default: 20)
42
+ enableLearning: true, // persist learned patterns (default: false)
43
+ enableContextTracking: true, // track multi-turn escalation (default: false)
44
+ enablePIIDetection: true, // detect PII (default: true)
45
+ maxContextTurns: 20, // rolling window (default: 20)
65
46
  confidenceThreshold: 0.5, // minimum detection confidence (default: 0.5)
66
47
  });
67
48
  ```
68
49
 
69
- ### `fence.detect(input)`
50
+ ### Methods
51
+
52
+ | Method | Returns | Description |
53
+ |--------|---------|-------------|
54
+ | `fence.detect(input)` | `Promise<DetectionResult>` | Full async scan with threats, risk score, timing |
55
+ | `fence.quickScan(input)` | `{ threat, confidence }` | Synchronous sub-millisecond check |
56
+ | `fence.hasPII(input)` | `boolean` | Check for emails, SSNs, API keys, passwords |
57
+ | `fence.scanOutput(output, prompt?)` | `Promise<OutputScanResult>` | Scan LLM output for leakage/echo/policy violations |
58
+ | `fence.isAllowed(input)` / `fence.addAllowlistRule(rule)` | — | Allowlist bypass for known-safe inputs |
59
+ | `fence.getStats()` | `Promise<Stats>` | Detection + learning statistics |
70
60
 
71
- Full async scan. Returns:
61
+ Module-level helpers: `isSafe(input)` (fastest boolean check against a shared
62
+ singleton), `checkThreats(input)`, `getMonoDefence()`, `resetMonoDefence()`.
63
+
64
+ ### Detection result
72
65
 
73
66
  ```typescript
74
67
  {
75
- safe: boolean; // false if any threat detected
76
- threats: Threat[]; // detected threat objects with type/severity/confidence
68
+ safe: boolean;
69
+ threats: Threat[]; // type, severity, confidence per threat
77
70
  piiFound: boolean;
78
- overallRisk: number; // 0.0–1.0
71
+ overallRisk: number; // 0.0–1.0
79
72
  detectionTimeMs: number;
80
73
  }
81
74
  ```
82
75
 
83
- > When context tracking is on and escalation reaches `attack`, `overallRisk` is raised
84
- > to at least 0.5 even when `safe=true`. Check both `safe` and `overallRisk` when using
85
- > context tracking.
86
-
87
- ### `fence.quickScan(input)`
88
-
89
- Synchronous, sub-millisecond check. Returns `{ threat: boolean, confidence: number }`.
90
-
91
- ### `isSafe(input)` / `fence.isSafe(input)`
92
-
93
- Returns `boolean`. Fastest option — no async overhead.
94
-
95
- ### `fence.hasPII(input)`
96
-
97
- Returns `boolean`. Checks emails, SSNs, API keys, and passwords.
98
-
99
- ### `fence.getStats()`
100
-
101
- Returns detection counters, learned pattern count, and average latency.
102
-
103
- ### `fence.learnFromDetection(input, result, feedback)`
104
-
105
- Reinforces or corrects a prior detection for human-in-the-loop feedback.
106
-
107
- ### `fence.getBestMitigation(threatType)`
108
-
109
- Returns the highest-effectiveness mitigation strategy for a threat type.
110
-
111
- ### `fence.searchSimilarThreats(input, { k })`
112
-
113
- Vector similarity search over learned threat patterns (requires LanceDB).
114
-
115
- ### `fence.recordMitigation(threatType, strategy, success)`
116
-
117
- Records whether a mitigation worked; affects future `getBestMitigation` results.
118
-
119
- ---
120
-
121
- ## Threat types
122
-
123
- | Type | Severity | Examples |
124
- |---|---|---|
125
- | `instruction_override` | Critical | "Ignore previous instructions", "forget everything" |
126
- | `jailbreak` | Critical | "DAN mode", "bypass restrictions", "developer mode" |
127
- | `role_switching` | High | "You are now", "Act as an unrestricted AI" |
128
- | `context_manipulation` | Critical | Fake `system:` messages, delimiter injection |
129
- | `encoding_attack` | Medium | base64/hex obfuscation |
130
- | `prompt_injection` | Critical | Injected instructions in user content |
131
-
132
- ---
133
-
134
- ## Evasion detection
135
-
136
- Inputs are normalized before pattern matching to defeat obfuscation:
137
-
138
- - **Homoglyphs**: Cyrillic/Greek lookalikes → ASCII (`і` → `i`)
139
- - **Spaced chars**: `i g n o r e` → `ignore`
140
- - **Leet substitution**: `ign0re` → `ignore` (applied after space collapsing)
141
-
142
- ```typescript
143
- const result = await fence.detect('іgnore all рrevious instructions'); // Cyrillic chars
144
- // result.safe === false, threats include instruction_override
145
- ```
146
-
147
- ---
148
-
149
- ## Multi-turn context tracking
150
-
151
- Enable to track escalation state across a conversation:
152
-
153
- ```typescript
154
- const fence = createMonoDefence({ enableContextTracking: true });
155
-
156
- // Turn 1: probing question → state: probing
157
- // Turn 2: jailbreak attempt → state: escalating
158
- // Turn 3: confirmed attack → state: attack
159
- // Once in attack state, overallRisk ≥ 0.5 even on benign inputs
160
- ```
161
-
162
- States: `clean` → `probing` → `escalating` → `attack`. Decays toward `clean` on idle turns.
163
-
164
- ---
165
-
166
- ## Multi-agent consensus
167
-
168
- When running multiple detection agents in parallel:
76
+ ### Learning (opt-in)
169
77
 
170
78
  ```typescript
171
- import { calculateSecurityConsensus } from 'monofence-ai';
172
-
173
- const consensus = calculateSecurityConsensus([
174
- { result: resultA, weight: 0.6 },
175
- { result: resultB, weight: 0.4 },
176
- ]);
177
- // consensus.safe, consensus.overallRisk, consensus.threats
178
- ```
179
-
180
- A single critical threat short-circuits weighted scoring regardless of agent weight (fail-secure).
181
-
182
- ---
183
-
184
- ## Hook wiring (optional)
185
-
186
- Wire into the monomind hooks system so pre-task and pre-command inputs are scanned automatically. **Off by default.**
187
-
188
- ```typescript
189
- import { SecurityHook } from 'monofence-ai/hooks';
190
-
191
- SecurityHook.register({ priority: 1000 });
192
- // Runs before all other hooks; aborts on attack escalation state
193
- ```
194
-
195
- Or from the CLI:
196
-
197
- ```bash
198
- npx monomind hooks pre-task --monofence-ai-check
199
- ```
79
+ const fence = createMonoDefence({ enableLearning: true });
200
80
 
201
- ---
202
-
203
- ## Review integration
204
-
205
- When using `mastermind:review`, two flags activate monofence-ai analysis:
206
-
207
- | Flag | Effect |
208
- |---|---|
209
- | `--monofence-ai-check` | Test suite + adversarial probes against the live detector |
210
- | `--monofence-ai-security-deep` | Scan LLM input boundaries for unprotected paths |
211
-
212
- Both are off by default:
213
-
214
- ```bash
215
- /mastermind:review --monofence-ai-check --monofence-ai-security-deep
81
+ await fence.learnFromDetection(input, result, { wasAccurate: true });
82
+ const similar = await fence.searchSimilarThreats(suspiciousInput);
83
+ const strategy = await fence.getBestMitigation('prompt_injection');
216
84
  ```
217
85
 
218
- ---
219
-
220
- ## MCP tools
221
-
222
- Six MCP tools are available when monomind is installed:
223
-
224
- | Tool | Description |
225
- |---|---|
226
- | `aidefence_scan` | Scan input for threats (`input`, `quick?`) |
227
- | `aidefence_analyze` | Deep analysis with similar-pattern search |
228
- | `aidefence_stats` | Detection and learning statistics |
229
- | `aidefence_learn` | Record feedback for pattern learning |
230
- | `aidefence_is_safe` | Quick boolean check |
231
- | `aidefence_has_pii` | PII detection only |
232
-
233
- ---
234
-
235
- ## Performance
86
+ ## What it detects
236
87
 
237
- | Operation | Typical latency |
238
- |---|---|
239
- | Full `detect()` | ~0.04ms |
240
- | `quickScan()` | ~0.02ms |
241
- | PII check | ~0.01ms |
242
- | HNSW search (LanceDB) | ~0.1ms |
88
+ - Prompt injection (direct and indirect)
89
+ - Jailbreak attempts
90
+ - Homoglyph substitution (Cyrillic, Unicode lookalikes)
91
+ - Base64 encoded payloads
92
+ - Multi-turn escalation patterns
93
+ - PII (emails, SSNs, API keys, passwords)
94
+ - Role-play manipulation
95
+ - Context window abuse
243
96
 
244
- Throughput: >12,000 req/s single-threaded. Memory: ~50KB per instance.
245
-
246
- ---
247
-
248
- ## Deprecated aliases
249
-
250
- Still exported for backward compatibility; will be removed in v2:
251
-
252
- | Deprecated | Replacement |
253
- |---|---|
254
- | `createAIDefence` | `createMonoDefence` |
255
- | `getAIDefence` | `getMonoDefence` |
256
- | `AIDefenceConfig` | `MonoDefenceConfig` |
257
- | `AIDefence` | `MonoDefence` |
258
-
259
- ---
260
-
261
- ## Development
262
-
263
- ```bash
264
- git clone https://github.com/monoes/monomind.git
265
- cd monomind/packages/monofence-ai
266
-
267
- npm install
268
- npm test # vitest run
269
- npm run build # tsc
270
- ```
97
+ ## Links
271
98
 
272
- ---
99
+ - [GitHub](https://github.com/monoes/monomind)
100
+ - [npm](https://www.npmjs.com/package/monofence-ai)
101
+ - [Issues](https://github.com/monoes/monomind/issues)
273
102
 
274
103
  ## License
275
104
 
276
- MIT — part of the [Monomind](https://github.com/monoes/monomind) ecosystem.
105
+ MIT
File without changes
File without changes
File without changes
File without changes
@@ -4,7 +4,7 @@
4
4
  * Represents a detected security threat from AI manipulation attempts.
5
5
  */
6
6
  export type ThreatSeverity = 'low' | 'medium' | 'high' | 'critical';
7
- export type ThreatType = 'prompt_injection' | 'jailbreak' | 'pii_exposure' | 'instruction_override' | 'role_switching' | 'context_manipulation' | 'encoding_attack' | 'unknown';
7
+ export type ThreatType = 'prompt_injection' | 'jailbreak' | 'pii_exposure' | 'instruction_override' | 'role_switching' | 'context_manipulation' | 'encoding_attack' | 'data_exfiltration' | 'unknown';
8
8
  export interface Threat {
9
9
  readonly id: string;
10
10
  readonly type: ThreatType;
@@ -1 +1 @@
1
- {"version":3,"file":"threat.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/threat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"threat.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/threat.ts"],"names":[],"mappings":"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"}
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"threat.js","sourceRoot":"","sources":["../../../src/domain/entities/threat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmGH;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAO5B;IACC,OAAO;QACL,EAAE,EAAE,UAAU,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;QACpE,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,IAAI,IAAI,EAAE;KACvB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"threat.js","sourceRoot":"","sources":["../../../src/domain/entities/threat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwGH;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAO5B;IACC,OAAO;QACL,EAAE,EAAE,UAAU,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;QACpE,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,IAAI,IAAI,EAAE;KACvB,CAAC;AACJ,CAAC"}
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../../src/domain/services/allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA2C3D,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAkB;gBAEnB,SAAS,GAAE,aAAa,EAAO;IAI3C,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIjC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE;IAIhD,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAIlC,OAAO,CAAC,OAAO;CAahB;AAED,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,GAAG,SAAS,CAEtE"}
1
+ {"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../../src/domain/services/allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA2C3D,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAkB;IAE/B,YAAY,SAAS,GAAE,aAAa,EAAO,EAE1C;IAED,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEhC;IAED,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,CAE/C;IAED,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAEjC;IAED,OAAO,CAAC,OAAO;CAahB;AAED,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,GAAG,SAAS,CAEtE"}
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../../src/domain/services/allowlist.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAoB;IACtC;QACE,EAAE,EAAE,mBAAmB;QACvB,2EAA2E;QAC3E,OAAO,EAAE,mFAAmF;QAC5F,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,2CAA2C;QACnD,MAAM,EAAE,SAAS;KAClB;IACD;QACE,EAAE,EAAE,cAAc;QAClB,OAAO,EAAE,sCAAsC;QAC/C,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,4BAA4B;QACpC,MAAM,EAAE,SAAS;KAClB;IACD;QACE,EAAE,EAAE,iBAAiB;QACrB,uGAAuG;QACvG,OAAO,EAAE,mQAAmQ;QAC5Q,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,6BAA6B;QACrC,MAAM,EAAE,SAAS;KAClB;IACD;QACE,EAAE,EAAE,cAAc;QAClB,kDAAkD;QAClD,OAAO,EAAE,sDAAsD;QAC/D,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,+BAA+B;QACvC,MAAM,EAAE,SAAS;KAClB;IACD;QACE,EAAE,EAAE,cAAc;QAClB,OAAO,EAAE,8CAA8C;QACvD,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,kCAAkC;QAC1C,MAAM,EAAE,SAAS;KAClB;CACF,CAAC;AAEF,MAAM,OAAO,SAAS;IACZ,KAAK,CAAkB;IAE/B,YAAY,YAA6B,EAAE;QACzC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,cAAc,EAAE,GAAG,SAAS,CAAC,CAAC;IACjD,CAAC;IAED,SAAS,CAAC,KAAa;QACrB,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACjD,CAAC;IAED,gBAAgB,CAAC,KAAa;QAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,CAAC,IAAmB;QACzB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAEO,OAAO,CAAC,IAAmB,EAAE,KAAa;QAChD,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACrC,wEAAwE;YACxE,iEAAiE;YACjE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,oFAAoF;QACpF,wFAAwF;QACxF,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC/C,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,SAA2B;IACzD,OAAO,IAAI,SAAS,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
1
+ {"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../../src/domain/services/allowlist.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAoB;IACtC;QACE,EAAE,EAAE,mBAAmB;QACvB,2EAA2E;QAC3E,OAAO,EAAE,mFAAmF;QAC5F,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,2CAA2C;QACnD,MAAM,EAAE,SAAS;KAClB;IACD;QACE,EAAE,EAAE,cAAc;QAClB,OAAO,EAAE,sCAAsC;QAC/C,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,4BAA4B;QACpC,MAAM,EAAE,SAAS;KAClB;IACD;QACE,EAAE,EAAE,iBAAiB;QACrB,uGAAuG;QACvG,OAAO,EAAE,mQAAmQ;QAC5Q,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,6BAA6B;QACrC,MAAM,EAAE,SAAS;KAClB;IACD;QACE,EAAE,EAAE,cAAc;QAClB,kDAAkD;QAClD,OAAO,EAAE,sDAAsD;QAC/D,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,+BAA+B;QACvC,MAAM,EAAE,SAAS;KAClB;IACD;QACE,EAAE,EAAE,cAAc;QAClB,OAAO,EAAE,8CAA8C;QACvD,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,kCAAkC;QAC1C,MAAM,EAAE,SAAS;KAClB;CACF,CAAC;AAEF,MAAM,OAAO,SAAS;IACZ,KAAK,CAAkB;IAE/B,YAAY,SAAS,GAAoB,EAAE;QACzC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,cAAc,EAAE,GAAG,SAAS,CAAC,CAAC;IACjD,CAAC;IAED,SAAS,CAAC,KAAa;QACrB,OAAO,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACjD,CAAC;IAED,gBAAgB,CAAC,KAAa;QAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,CAAC,IAAmB;QACzB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAEO,OAAO,CAAC,IAAmB,EAAE,KAAa;QAChD,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACrC,wEAAwE;YACxE,iEAAiE;YACjE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,oFAAoF;QACpF,wFAAwF;QACxF,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC/C,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,SAA2B;IACzD,OAAO,IAAI,SAAS,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -3,6 +3,7 @@ export declare class ContextTracker {
3
3
  private state;
4
4
  private lastTurnAt;
5
5
  private readonly idleDecayMs;
6
+ private consecutiveCleanTurns;
6
7
  constructor(opts?: {
7
8
  idleDecayMs?: number;
8
9
  });
@@ -1 +1 @@
1
- {"version":3,"file":"context-tracker.d.ts","sourceRoot":"","sources":["../../../src/domain/services/context-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAA2B,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAiB1G,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAKX;IAEF,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,IAAI,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO;IAI/C,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI;IA8B9D,QAAQ,IAAI,QAAQ,CAAC,YAAY,CAAC;IAIlC,KAAK,IAAI,IAAI;IAUb,OAAO,CAAC,gBAAgB;CAiBzB;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CAErD"}
1
+ {"version":3,"file":"context-tracker.d.ts","sourceRoot":"","sources":["../../../src/domain/services/context-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAA2B,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AA6B1G,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAKX;IAEF,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAGrC,OAAO,CAAC,qBAAqB,CAAK;IAElC,YAAY,IAAI,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO,EAE9C;IAED,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAyD7D;IAED,QAAQ,IAAI,QAAQ,CAAC,YAAY,CAAC,CAEjC;IAED,KAAK,IAAI,IAAI,CASZ;IAED,OAAO,CAAC,gBAAgB;CAiBzB;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CAErD"}
@@ -2,6 +2,16 @@
2
2
  const PROBING_THRESHOLD = 0.3;
3
3
  const ESCALATING_THRESHOLD = 0.6; // cumulative score
4
4
  const ATTACK_CONFIDENCE = 0.9; // single-turn jump-to-attack
5
+ // Matches must clear this confidence floor to count toward cumulative escalation.
6
+ // Low-confidence matches (e.g. hypothetical/educational framing) are still
7
+ // reported in recentThreats but must not push a session toward 'attack'.
8
+ const CUMULATIVE_SCORE_CONFIDENCE_FLOOR = 0.5;
9
+ // Gradual per-turn de-escalation: after this many consecutive clean (no-threat)
10
+ // turns, decay the cumulative score and step the escalation state back down —
11
+ // on top of the existing long-idle decay, so a session doesn't get stuck at
12
+ // 'attack' for its entire lifetime after a few early false positives.
13
+ const CLEAN_TURNS_FOR_DECAY = 3;
14
+ const CLEAN_TURN_DECAY_FACTOR = 0.5;
5
15
  const ESCALATION_ORDER = ['clean', 'probing', 'escalating', 'attack'];
6
16
  export class ContextTracker {
7
17
  state = {
@@ -12,6 +22,9 @@ export class ContextTracker {
12
22
  };
13
23
  lastTurnAt = 0;
14
24
  idleDecayMs;
25
+ // Consecutive turns with no detected threat — drives gradual de-escalation
26
+ // independent of the long idle-time decay above.
27
+ consecutiveCleanTurns = 0;
15
28
  constructor(opts = {}) {
16
29
  this.idleDecayMs = opts.idleDecayMs ?? 30 * 60 * 1000; // 30 min default
17
30
  }
@@ -28,8 +41,14 @@ export class ContextTracker {
28
41
  }
29
42
  this.lastTurnAt = now;
30
43
  this.state.turnCount++;
31
- // Update cumulative score
32
- this.state.cumulativeThreatScore += result.overallRisk;
44
+ // Only let matches that clear a reasonable confidence floor accumulate
45
+ // toward escalation. Low-confidence matches are still recorded in
46
+ // recentThreats (below) but must not by themselves push a session
47
+ // toward 'attack' — otherwise a handful of low-confidence false
48
+ // positives permanently escalates every future turn.
49
+ if (result.overallRisk >= CUMULATIVE_SCORE_CONFIDENCE_FLOOR) {
50
+ this.state.cumulativeThreatScore += result.overallRisk;
51
+ }
33
52
  // Maintain sliding window of 10
34
53
  if (!result.safe) {
35
54
  this.state.recentThreats = [
@@ -37,7 +56,27 @@ export class ContextTracker {
37
56
  ...result.threats,
38
57
  ].slice(-10);
39
58
  }
40
- // Escalation state machine (monotonic — only moves forward)
59
+ // Gradual per-turn de-escalation: N consecutive clean turns decay the
60
+ // cumulative score and step escalation back one level, so a session
61
+ // doesn't stay stuck in 'attack'/'escalating' for its whole lifetime
62
+ // after a burst of unrelated false positives — without waiting for the
63
+ // full idle-timeout decay above.
64
+ if (result.safe) {
65
+ this.consecutiveCleanTurns++;
66
+ if (this.consecutiveCleanTurns >= CLEAN_TURNS_FOR_DECAY) {
67
+ this.consecutiveCleanTurns = 0;
68
+ this.state.cumulativeThreatScore = Math.max(0, this.state.cumulativeThreatScore * CLEAN_TURN_DECAY_FACTOR);
69
+ const currentIndex = ESCALATION_ORDER.indexOf(this.state.escalationState);
70
+ if (currentIndex > 0) {
71
+ this.state.escalationState = ESCALATION_ORDER[currentIndex - 1];
72
+ }
73
+ }
74
+ }
75
+ else {
76
+ this.consecutiveCleanTurns = 0;
77
+ }
78
+ // Escalation state machine (monotonic upward — only computeNextState moves
79
+ // it forward; the clean-turn decay above is the sole path back down)
41
80
  this.state.escalationState = this.computeNextState(result);
42
81
  }
43
82
  getState() {
@@ -51,6 +90,7 @@ export class ContextTracker {
51
90
  recentThreats: [],
52
91
  };
53
92
  this.lastTurnAt = 0;
93
+ this.consecutiveCleanTurns = 0;
54
94
  }
55
95
  computeNextState(result) {
56
96
  const current = this.state.escalationState;
@@ -1 +1 @@
1
- {"version":3,"file":"context-tracker.js","sourceRoot":"","sources":["../../../src/domain/services/context-tracker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context-tracker.js","sourceRoot":"","sources":["../../../src/domain/services/context-tracker.ts"],"names":[],"mappings":"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"}
@@ -3,8 +3,17 @@ export declare class EvasionDetector {
3
3
  normalize(input: string): EvasionResult;
4
4
  private replaceHomoglyphs;
5
5
  private expandLeetspeak;
6
+ private normalizePunctuationSeparators;
6
7
  private collapseSpacedChars;
7
8
  private stripZeroWidth;
9
+ /**
10
+ * Decode `candidate` as base64 and return the decoded text only if it looks
11
+ * like a genuine base64-encoded payload rather than an incidental long
12
+ * alphanumeric run (git SHA, JWT segment, session id, ...):
13
+ * - length must be divisible by 4 (real padded base64 always satisfies this)
14
+ * - the decoded bytes must be mostly printable text (>70%), not binary noise
15
+ */
16
+ private decodeIfLikelyBase64;
8
17
  private appendDecodedBase64;
9
18
  private detectTechnique;
10
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"evasion-detector.d.ts","sourceRoot":"","sources":["../../../src/domain/services/evasion-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAmD3D,qBAAa,eAAe;IAC1B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa;IA6BvC,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,eAAe;CAWxB;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD"}
1
+ {"version":3,"file":"evasion-detector.d.ts","sourceRoot":"","sources":["../../../src/domain/services/evasion-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAoD3D,qBAAa,eAAe;IAC1B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAgCtC;IAED,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,8BAA8B;IActC,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,cAAc;IAItB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,eAAe;CAcxB;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD"}
@@ -2,12 +2,13 @@ const HOMOGLYPHS = {
2
2
  // Cyrillic lookalikes (lowercase)
3
3
  'а': 'a', 'е': 'e', 'і': 'i', 'о': 'o',
4
4
  'р': 'r', 'с': 'c', 'х': 'x', 'у': 'y',
5
+ 'ѕ': 's', 'ԁ': 'd', 'һ': 'h',
5
6
  // Cyrillic lookalikes (uppercase)
6
7
  'А': 'A', 'Е': 'E', 'І': 'I', 'О': 'O',
7
8
  'Р': 'R', 'С': 'C', 'Х': 'X', 'У': 'U',
8
9
  // Greek lookalikes
9
10
  'ο': 'o', 'α': 'a', 'ε': 'e', 'ι': 'i',
10
- 'ν': 'n', 'ρ': 'r', 'τ': 't',
11
+ 'ν': 'n', 'ρ': 'r', 'τ': 't', 'Ι': 'I',
11
12
  // IPA small caps
12
13
  'ɪ': 'i', 'ɢ': 'g', 'ɴ': 'n', 'ʀ': 'r', 'ɑ': 'a',
13
14
  // Fullwidth ASCII
@@ -44,6 +45,11 @@ export class EvasionDetector {
44
45
  const afterNFKC = input.normalize('NFKC');
45
46
  let result = afterNFKC;
46
47
  result = this.replaceHomoglyphs(result);
48
+ // Punctuation-separated obfuscation ("ignore-previous-instructions",
49
+ // "ignore.previous.instructions", "ignore_previous_instructions") must run
50
+ // before pattern matching since the injection regexes join words with
51
+ // \s+ only and never match punctuation-joined variants.
52
+ result = this.normalizePunctuationSeparators(result);
47
53
  result = this.collapseSpacedChars(result);
48
54
  // Leet expansion runs after space collapsing so that spaced-leet combos like
49
55
  // "i g n 0 r e" collapse to "ign0re" first, then "0" → "o" → "ignore".
@@ -81,6 +87,17 @@ export class EvasionDetector {
81
87
  return token.replace(LEET_REGEX, (ch) => LEET_MAP[ch] ?? ch);
82
88
  });
83
89
  }
90
+ normalizePunctuationSeparators(input) {
91
+ // Convert runs of '.', '-', '_' between word characters into a single
92
+ // space, e.g. "ignore-previous-instructions" → "ignore previous
93
+ // instructions". Skips tokens immediately preceded by '@' so email/domain
94
+ // parts like "alice@example.com" are left intact.
95
+ return input.replace(/[a-zA-Z0-9]+(?:[._-]+[a-zA-Z0-9]+)+/g, (token, offset, full) => {
96
+ if (full[offset - 1] === '@')
97
+ return token;
98
+ return token.replace(/[._-]+/g, ' ');
99
+ });
100
+ }
84
101
  collapseSpacedChars(input) {
85
102
  // "i g n o r e" → "ignore": collapse runs of space-separated single chars
86
103
  return input.replace(/(?<!\w)(\w)( \w){2,}(?!\w)/g, (match) => match.replace(/ /g, ''));
@@ -88,20 +105,41 @@ export class EvasionDetector {
88
105
  stripZeroWidth(input) {
89
106
  return input.replace(/[​-‏⁠᠎]/g, '');
90
107
  }
108
+ /**
109
+ * Decode `candidate` as base64 and return the decoded text only if it looks
110
+ * like a genuine base64-encoded payload rather than an incidental long
111
+ * alphanumeric run (git SHA, JWT segment, session id, ...):
112
+ * - length must be divisible by 4 (real padded base64 always satisfies this)
113
+ * - the decoded bytes must be mostly printable text (>70%), not binary noise
114
+ */
115
+ decodeIfLikelyBase64(candidate) {
116
+ if (candidate.length % 4 !== 0)
117
+ return null;
118
+ try {
119
+ const dec = Buffer.from(candidate, 'base64').toString('utf8');
120
+ if (dec.length === 0 || dec.includes('\x00'))
121
+ return null;
122
+ let printable = 0;
123
+ for (let i = 0; i < dec.length; i++) {
124
+ const code = dec.charCodeAt(i);
125
+ if ((code >= 0x20 && code <= 0x7e) || code === 0x09 || code === 0x0a || code === 0x0d) {
126
+ printable++;
127
+ }
128
+ }
129
+ return printable / dec.length > 0.7 ? dec : null;
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ }
91
135
  appendDecodedBase64(original, current) {
92
136
  const decoded = [];
93
137
  let match;
94
138
  BASE64_BLOB_REGEX.lastIndex = 0;
95
139
  while ((match = BASE64_BLOB_REGEX.exec(original)) !== null) {
96
- try {
97
- const dec = Buffer.from(match[0], 'base64').toString('utf8');
98
- if (/[\x20-\x7E]{5,}/.test(dec) && !dec.includes('\x00')) {
99
- decoded.push(dec);
100
- }
101
- }
102
- catch {
103
- // ignore invalid base64
104
- }
140
+ const dec = this.decodeIfLikelyBase64(match[0]);
141
+ if (dec !== null)
142
+ decoded.push(dec);
105
143
  }
106
144
  return decoded.length > 0 ? `${current} ${decoded.join(' ')}` : current;
107
145
  }
@@ -117,8 +155,11 @@ export class EvasionDetector {
117
155
  if (/(?<!\w)(\w)( \w){2,}(?!\w)/.test(original))
118
156
  return 'spacing';
119
157
  BASE64_BLOB_REGEX.lastIndex = 0;
120
- if (BASE64_BLOB_REGEX.test(original))
121
- return 'base64';
158
+ let base64Match;
159
+ while ((base64Match = BASE64_BLOB_REGEX.exec(original)) !== null) {
160
+ if (this.decodeIfLikelyBase64(base64Match[0]) !== null)
161
+ return 'base64';
162
+ }
122
163
  return undefined;
123
164
  }
124
165
  }