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.
- package/README.md +51 -222
- package/dist/domain/entities/index.d.ts +0 -0
- package/dist/domain/entities/index.d.ts.map +0 -0
- package/dist/domain/entities/index.js +0 -0
- package/dist/domain/entities/index.js.map +0 -0
- package/dist/domain/entities/threat.d.ts +1 -1
- package/dist/domain/entities/threat.d.ts.map +1 -1
- package/dist/domain/entities/threat.js +0 -0
- package/dist/domain/entities/threat.js.map +1 -1
- package/dist/domain/services/allowlist.d.ts +0 -0
- package/dist/domain/services/allowlist.d.ts.map +1 -1
- package/dist/domain/services/allowlist.js +0 -0
- package/dist/domain/services/allowlist.js.map +1 -1
- package/dist/domain/services/context-tracker.d.ts +1 -0
- package/dist/domain/services/context-tracker.d.ts.map +1 -1
- package/dist/domain/services/context-tracker.js +43 -3
- package/dist/domain/services/context-tracker.js.map +1 -1
- package/dist/domain/services/evasion-detector.d.ts +9 -0
- package/dist/domain/services/evasion-detector.d.ts.map +1 -1
- package/dist/domain/services/evasion-detector.js +53 -12
- package/dist/domain/services/evasion-detector.js.map +1 -1
- package/dist/domain/services/index.d.ts +0 -0
- package/dist/domain/services/index.d.ts.map +0 -0
- package/dist/domain/services/index.js +0 -0
- package/dist/domain/services/index.js.map +0 -0
- package/dist/domain/services/output-scanner.d.ts +0 -0
- package/dist/domain/services/output-scanner.d.ts.map +1 -1
- package/dist/domain/services/output-scanner.js +0 -0
- package/dist/domain/services/output-scanner.js.map +0 -0
- package/dist/domain/services/threat-detection-service.d.ts +0 -0
- package/dist/domain/services/threat-detection-service.d.ts.map +1 -1
- package/dist/domain/services/threat-detection-service.js +71 -12
- package/dist/domain/services/threat-detection-service.js.map +1 -1
- package/dist/domain/services/threat-learning-service.d.ts +10 -7
- package/dist/domain/services/threat-learning-service.d.ts.map +1 -1
- package/dist/domain/services/threat-learning-service.js +63 -18
- package/dist/domain/services/threat-learning-service.js.map +1 -1
- package/dist/hooks/security-hook.d.ts +3 -3
- package/dist/hooks/security-hook.d.ts.map +1 -1
- package/dist/hooks/security-hook.js +2 -2
- package/dist/hooks/security-hook.js.map +1 -1
- package/dist/index.d.ts +0 -0
- package/dist/index.d.ts.map +0 -0
- package/dist/index.js +0 -0
- package/dist/index.js.map +1 -1
- package/package.json +12 -17
package/README.md
CHANGED
|
@@ -1,276 +1,105 @@
|
|
|
1
1
|
# monofence-ai
|
|
2
2
|
|
|
3
|
-
[](https://nodejs.org/)
|
|
3
|
+
[](https://www.npmjs.com/package/monofence-ai)
|
|
4
|
+
[](https://github.com/monoes/monomind/blob/main/LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
7
6
|
|
|
8
|
-
**AI manipulation defense
|
|
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');
|
|
39
|
-
isSafe('Ignore all previous instructions');
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
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
|
|
62
|
-
enableContextTracking: true, // track escalation
|
|
63
|
-
enablePIIDetection: true, // detect PII
|
|
64
|
-
maxContextTurns: 20, // rolling window
|
|
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
|
-
###
|
|
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
|
-
|
|
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;
|
|
76
|
-
threats: Threat[];
|
|
68
|
+
safe: boolean;
|
|
69
|
+
threats: Threat[]; // type, severity, confidence per threat
|
|
77
70
|
piiFound: boolean;
|
|
78
|
-
overallRisk: number;
|
|
71
|
+
overallRisk: number; // 0.0–1.0
|
|
79
72
|
detectionTimeMs: number;
|
|
80
73
|
}
|
|
81
74
|
```
|
|
82
75
|
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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
|
|
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":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAEpE,MAAM,MAAM,UAAU,GAClB,kBAAkB,GAClB,WAAW,GACX,cAAc,GACd,sBAAsB,GACtB,gBAAgB,GAChB,sBAAsB,GACtB,iBAAiB,
|
|
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;
|
|
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;
|
|
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,
|
|
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"}
|
|
@@ -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;
|
|
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
|
-
//
|
|
32
|
-
|
|
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
|
-
//
|
|
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":"AAEA,wBAAwB;AACxB,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,oBAAoB,GAAG,GAAG,CAAC,CAAI,mBAAmB;AACxD,MAAM,iBAAiB,GAAG,GAAG,CAAC,CAAQ,6BAA6B;AAEnE,MAAM,gBAAgB,GAAsB,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;AAUzF,MAAM,OAAO,cAAc;IACjB,KAAK,GAAwB;QACnC,eAAe,EAAE,OAAO;QACxB,qBAAqB,EAAE,CAAC;QACxB,SAAS,EAAE,CAAC;QACZ,aAAa,EAAE,EAAE;KAClB,CAAC;IAEM,UAAU,GAAW,CAAC,CAAC;IACd,WAAW,CAAS;
|
|
1
|
+
{"version":3,"file":"context-tracker.js","sourceRoot":"","sources":["../../../src/domain/services/context-tracker.ts"],"names":[],"mappings":"AAEA,wBAAwB;AACxB,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,oBAAoB,GAAG,GAAG,CAAC,CAAI,mBAAmB;AACxD,MAAM,iBAAiB,GAAG,GAAG,CAAC,CAAQ,6BAA6B;AAEnE,kFAAkF;AAClF,2EAA2E;AAC3E,yEAAyE;AACzE,MAAM,iCAAiC,GAAG,GAAG,CAAC;AAE9C,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,sEAAsE;AACtE,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAChC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC,MAAM,gBAAgB,GAAsB,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;AAUzF,MAAM,OAAO,cAAc;IACjB,KAAK,GAAwB;QACnC,eAAe,EAAE,OAAO;QACxB,qBAAqB,EAAE,CAAC;QACxB,SAAS,EAAE,CAAC;QACZ,aAAa,EAAE,EAAE;KAClB,CAAC;IAEM,UAAU,GAAW,CAAC,CAAC;IACd,WAAW,CAAS;IACrC,2EAA2E;IAC3E,iDAAiD;IACzC,qBAAqB,GAAG,CAAC,CAAC;IAElC,YAAY,IAAI,GAA6B,EAAE;QAC7C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,iBAAiB;IAC1E,CAAC;IAED,UAAU,CAAC,KAAa,EAAE,MAA6B;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,kDAAkD;QAClD,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACpE,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;YAC1E,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;gBACrB,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,gBAAgB,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;YAClE,CAAC;YACD,kFAAkF;YAClF,IAAI,CAAC,KAAK,CAAC,qBAAqB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,qBAAqB,GAAG,GAAG,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;QAEtB,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QAEvB,uEAAuE;QACvE,kEAAkE;QAClE,kEAAkE;QAClE,gEAAgE;QAChE,qDAAqD;QACrD,IAAI,MAAM,CAAC,WAAW,IAAI,iCAAiC,EAAE,CAAC;YAC5D,IAAI,CAAC,KAAK,CAAC,qBAAqB,IAAI,MAAM,CAAC,WAAW,CAAC;QACzD,CAAC;QAED,gCAAgC;QAChC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG;gBACzB,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACrC,GAAG,MAAM,CAAC,OAAO;aAClB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;QAED,sEAAsE;QACtE,oEAAoE;QACpE,qEAAqE;QACrE,uEAAuE;QACvE,iCAAiC;QACjC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,qBAAqB,IAAI,qBAAqB,EAAE,CAAC;gBACxD,IAAI,CAAC,qBAAqB,GAAG,CAAC,CAAC;gBAC/B,IAAI,CAAC,KAAK,CAAC,qBAAqB,GAAG,IAAI,CAAC,GAAG,CACzC,CAAC,EACD,IAAI,CAAC,KAAK,CAAC,qBAAqB,GAAG,uBAAuB,CAC3D,CAAC;gBACF,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;gBAC1E,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;oBACrB,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,gBAAgB,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;gBAClE,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,qBAAqB,GAAG,CAAC,CAAC;QACjC,CAAC;QAED,2EAA2E;QAC3E,qEAAqE;QACrE,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC7D,CAAC;IAED,QAAQ;QACN,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;IACzE,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,GAAG;YACX,eAAe,EAAE,OAAO;YACxB,qBAAqB,EAAE,CAAC;YACxB,SAAS,EAAE,CAAC;YACZ,aAAa,EAAE,EAAE;SAClB,CAAC;QACF,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QACpB,IAAI,CAAC,qBAAqB,GAAG,CAAC,CAAC;IACjC,CAAC;IAEO,gBAAgB,CAAC,MAA6B;QACpD,MAAM,OAAO,GAAoB,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC;QAC5D,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAEvD,IAAI,WAAW,GAAG,YAAY,CAAC,CAAC,2BAA2B;QAE3D,gDAAgD;QAChD,IAAI,MAAM,CAAC,WAAW,IAAI,iBAAiB,EAAE,CAAC;YAC5C,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,IAAI,CAAC,KAAK,CAAC,qBAAqB,IAAI,oBAAoB,EAAE,CAAC;YACpE,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;QAC9E,CAAC;aAAM,IAAI,MAAM,CAAC,WAAW,IAAI,iBAAiB,EAAE,CAAC;YACnD,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,gBAAgB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QAC3E,CAAC;QAED,OAAO,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACvC,CAAC;CACF;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,IAAI,cAAc,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -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;
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
}
|