@agent-wall/core 0.1.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/.turbo/turbo-build.log +17 -0
- package/.turbo/turbo-test.log +30 -0
- package/LICENSE +21 -0
- package/README.md +80 -0
- package/dist/index.d.ts +1297 -0
- package/dist/index.js +3067 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/src/audit-logger-security.test.ts +225 -0
- package/src/audit-logger.test.ts +93 -0
- package/src/audit-logger.ts +458 -0
- package/src/chain-detector.test.ts +100 -0
- package/src/chain-detector.ts +269 -0
- package/src/dashboard-server.test.ts +362 -0
- package/src/dashboard-server.ts +454 -0
- package/src/egress-control.test.ts +177 -0
- package/src/egress-control.ts +274 -0
- package/src/index.ts +137 -0
- package/src/injection-detector.test.ts +207 -0
- package/src/injection-detector.ts +397 -0
- package/src/kill-switch.test.ts +119 -0
- package/src/kill-switch.ts +198 -0
- package/src/policy-engine-security.test.ts +227 -0
- package/src/policy-engine.test.ts +453 -0
- package/src/policy-engine.ts +414 -0
- package/src/policy-loader.test.ts +202 -0
- package/src/policy-loader.ts +485 -0
- package/src/proxy.ts +786 -0
- package/src/read-buffer-security.test.ts +59 -0
- package/src/read-buffer.test.ts +135 -0
- package/src/read-buffer.ts +126 -0
- package/src/response-scanner.test.ts +464 -0
- package/src/response-scanner.ts +587 -0
- package/src/types.test.ts +152 -0
- package/src/types.ts +146 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +9 -0
- package/vitest.config.ts +12 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ResponseScanner — response content scanning and redaction.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import {
|
|
7
|
+
ResponseScanner,
|
|
8
|
+
createDefaultScanner,
|
|
9
|
+
type ResponseScannerConfig,
|
|
10
|
+
} from "./response-scanner.js";
|
|
11
|
+
|
|
12
|
+
describe("ResponseScanner", () => {
|
|
13
|
+
describe("basic functionality", () => {
|
|
14
|
+
it("should pass clean text through", () => {
|
|
15
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
16
|
+
const result = scanner.scan("Hello world, this is a normal response.");
|
|
17
|
+
expect(result.clean).toBe(true);
|
|
18
|
+
expect(result.action).toBe("pass");
|
|
19
|
+
expect(result.findings).toHaveLength(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should be disabled when enabled=false", () => {
|
|
23
|
+
const scanner = new ResponseScanner({
|
|
24
|
+
enabled: false,
|
|
25
|
+
detectSecrets: true,
|
|
26
|
+
});
|
|
27
|
+
// Even with secrets present, a disabled scanner passes everything
|
|
28
|
+
const result = scanner.scan("aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
|
|
29
|
+
expect(result.clean).toBe(true);
|
|
30
|
+
expect(result.action).toBe("pass");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should return original size in bytes", () => {
|
|
34
|
+
const scanner = new ResponseScanner();
|
|
35
|
+
const text = "hello world";
|
|
36
|
+
const result = scanner.scan(text);
|
|
37
|
+
expect(result.originalSize).toBe(Buffer.byteLength(text, "utf-8"));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("secret detection", () => {
|
|
42
|
+
it("should detect AWS access key IDs", () => {
|
|
43
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
44
|
+
const result = scanner.scan("Found key: AKIAIOSFODNN7EXAMPLE in config");
|
|
45
|
+
expect(result.clean).toBe(false);
|
|
46
|
+
expect(result.findings.some((f) => f.pattern === "aws-access-key")).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should detect AWS secret access keys", () => {
|
|
50
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
51
|
+
const result = scanner.scan(
|
|
52
|
+
"aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
|
|
53
|
+
);
|
|
54
|
+
expect(result.clean).toBe(false);
|
|
55
|
+
expect(result.action).toBe("redact");
|
|
56
|
+
expect(result.findings.some((f) => f.pattern === "aws-secret-key")).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should detect GitHub tokens", () => {
|
|
60
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
61
|
+
const result = scanner.scan(
|
|
62
|
+
"Token: ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn"
|
|
63
|
+
);
|
|
64
|
+
expect(result.clean).toBe(false);
|
|
65
|
+
expect(result.findings.some((f) => f.pattern === "github-token")).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should detect generic API keys", () => {
|
|
69
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
70
|
+
const result = scanner.scan(
|
|
71
|
+
'config = { api_key: "sk_live_1234567890abcdefghij" }'
|
|
72
|
+
);
|
|
73
|
+
expect(result.clean).toBe(false);
|
|
74
|
+
expect(result.findings.some((f) => f.pattern === "generic-api-key")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should detect Bearer tokens", () => {
|
|
78
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
79
|
+
const result = scanner.scan(
|
|
80
|
+
"Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.test.signature"
|
|
81
|
+
);
|
|
82
|
+
expect(result.clean).toBe(false);
|
|
83
|
+
// Could match bearer-token and/or jwt-token
|
|
84
|
+
expect(result.findings.length).toBeGreaterThan(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should detect JWT tokens", () => {
|
|
88
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
89
|
+
const result = scanner.scan(
|
|
90
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
|
|
91
|
+
);
|
|
92
|
+
expect(result.clean).toBe(false);
|
|
93
|
+
expect(result.findings.some((f) => f.pattern === "jwt-token")).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should BLOCK when a private key is detected", () => {
|
|
97
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
98
|
+
const result = scanner.scan(
|
|
99
|
+
"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----"
|
|
100
|
+
);
|
|
101
|
+
expect(result.clean).toBe(false);
|
|
102
|
+
expect(result.action).toBe("block");
|
|
103
|
+
expect(result.findings.some((f) => f.pattern === "private-key")).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should detect database connection strings", () => {
|
|
107
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
108
|
+
const result = scanner.scan(
|
|
109
|
+
"DATABASE_URL=postgres://admin:s3cret@db.example.com:5432/mydb"
|
|
110
|
+
);
|
|
111
|
+
expect(result.clean).toBe(false);
|
|
112
|
+
expect(result.findings.some((f) => f.pattern === "database-url")).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should detect password assignments", () => {
|
|
116
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
117
|
+
const result = scanner.scan('password = "SuperSecret123!"');
|
|
118
|
+
expect(result.clean).toBe(false);
|
|
119
|
+
expect(result.findings.some((f) => f.pattern === "password-assignment")).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should not detect secrets when detectSecrets=false", () => {
|
|
123
|
+
const scanner = new ResponseScanner({ detectSecrets: false });
|
|
124
|
+
const result = scanner.scan("aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
|
|
125
|
+
expect(result.clean).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("PII detection", () => {
|
|
130
|
+
it("should not detect PII by default", () => {
|
|
131
|
+
const scanner = new ResponseScanner({ detectSecrets: false, detectPII: false });
|
|
132
|
+
const result = scanner.scan("Email: john@example.com Phone: 555-123-4567");
|
|
133
|
+
expect(result.clean).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should detect email addresses when PII detection enabled", () => {
|
|
137
|
+
const scanner = new ResponseScanner({ detectPII: true, detectSecrets: false });
|
|
138
|
+
const result = scanner.scan("Contact: user@example.com for details");
|
|
139
|
+
expect(result.clean).toBe(false);
|
|
140
|
+
expect(result.findings.some((f) => f.pattern === "email-address")).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should detect phone numbers when PII detection enabled", () => {
|
|
144
|
+
const scanner = new ResponseScanner({ detectPII: true, detectSecrets: false });
|
|
145
|
+
const result = scanner.scan("Call us at (555) 123-4567");
|
|
146
|
+
expect(result.clean).toBe(false);
|
|
147
|
+
expect(result.findings.some((f) => f.pattern === "phone-number")).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should BLOCK SSN patterns", () => {
|
|
151
|
+
const scanner = new ResponseScanner({ detectPII: true, detectSecrets: false });
|
|
152
|
+
const result = scanner.scan("SSN: 123-45-6789");
|
|
153
|
+
expect(result.clean).toBe(false);
|
|
154
|
+
expect(result.action).toBe("block");
|
|
155
|
+
expect(result.findings.some((f) => f.pattern === "ssn")).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should BLOCK credit card numbers", () => {
|
|
159
|
+
const scanner = new ResponseScanner({ detectPII: true, detectSecrets: false });
|
|
160
|
+
const result = scanner.scan("Card: 4111111111111111");
|
|
161
|
+
expect(result.clean).toBe(false);
|
|
162
|
+
expect(result.action).toBe("block");
|
|
163
|
+
expect(result.findings.some((f) => f.pattern === "credit-card")).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("response size limits", () => {
|
|
168
|
+
it("should flag oversized responses", () => {
|
|
169
|
+
const scanner = new ResponseScanner({
|
|
170
|
+
maxResponseSize: 100,
|
|
171
|
+
oversizeAction: "redact",
|
|
172
|
+
detectSecrets: false,
|
|
173
|
+
});
|
|
174
|
+
const bigText = "A".repeat(200);
|
|
175
|
+
const result = scanner.scan(bigText);
|
|
176
|
+
expect(result.clean).toBe(false);
|
|
177
|
+
expect(result.findings.some((f) => f.pattern === "__oversize__")).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should block oversized when oversizeAction=block", () => {
|
|
181
|
+
const scanner = new ResponseScanner({
|
|
182
|
+
maxResponseSize: 50,
|
|
183
|
+
oversizeAction: "block",
|
|
184
|
+
detectSecrets: false,
|
|
185
|
+
});
|
|
186
|
+
const result = scanner.scan("A".repeat(100));
|
|
187
|
+
expect(result.action).toBe("block");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should redact (truncate) when oversizeAction=redact", () => {
|
|
191
|
+
const scanner = new ResponseScanner({
|
|
192
|
+
maxResponseSize: 50,
|
|
193
|
+
oversizeAction: "redact",
|
|
194
|
+
detectSecrets: false,
|
|
195
|
+
});
|
|
196
|
+
const result = scanner.scan("A".repeat(100));
|
|
197
|
+
expect(result.action).toBe("redact");
|
|
198
|
+
expect(result.redactedText).toBeDefined();
|
|
199
|
+
expect(result.redactedText!.length).toBeLessThan(200);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should not flag responses under the size limit", () => {
|
|
203
|
+
const scanner = new ResponseScanner({
|
|
204
|
+
maxResponseSize: 1000,
|
|
205
|
+
detectSecrets: false,
|
|
206
|
+
});
|
|
207
|
+
const result = scanner.scan("short text");
|
|
208
|
+
expect(result.clean).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should not check size when maxResponseSize=0", () => {
|
|
212
|
+
const scanner = new ResponseScanner({
|
|
213
|
+
maxResponseSize: 0,
|
|
214
|
+
detectSecrets: false,
|
|
215
|
+
});
|
|
216
|
+
const result = scanner.scan("A".repeat(10000));
|
|
217
|
+
expect(result.clean).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("custom patterns", () => {
|
|
222
|
+
it("should match user-defined patterns", () => {
|
|
223
|
+
const scanner = new ResponseScanner({
|
|
224
|
+
detectSecrets: false,
|
|
225
|
+
patterns: [
|
|
226
|
+
{
|
|
227
|
+
name: "internal-url",
|
|
228
|
+
pattern: "https?://internal\\.[a-z]+\\.corp",
|
|
229
|
+
action: "redact",
|
|
230
|
+
message: "Internal URL detected",
|
|
231
|
+
category: "custom",
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
const result = scanner.scan("API endpoint: https://internal.api.corp/v1/users");
|
|
236
|
+
expect(result.clean).toBe(false);
|
|
237
|
+
expect(result.findings.some((f) => f.pattern === "internal-url")).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("should block on custom block patterns", () => {
|
|
241
|
+
const scanner = new ResponseScanner({
|
|
242
|
+
detectSecrets: false,
|
|
243
|
+
patterns: [
|
|
244
|
+
{
|
|
245
|
+
name: "confidential-marker",
|
|
246
|
+
pattern: "\\[CONFIDENTIAL\\]",
|
|
247
|
+
action: "block",
|
|
248
|
+
message: "Confidential content detected",
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
const result = scanner.scan("This document is [CONFIDENTIAL] and should not be shared.");
|
|
253
|
+
expect(result.action).toBe("block");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("should skip invalid regex patterns without crashing", () => {
|
|
257
|
+
const scanner = new ResponseScanner({
|
|
258
|
+
detectSecrets: false,
|
|
259
|
+
patterns: [
|
|
260
|
+
{
|
|
261
|
+
name: "bad-pattern",
|
|
262
|
+
pattern: "[invalid(regex",
|
|
263
|
+
action: "block",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "good-pattern",
|
|
267
|
+
pattern: "findme",
|
|
268
|
+
action: "redact",
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
// Should not throw, and should still find the good pattern
|
|
273
|
+
const result = scanner.scan("please findme in this text");
|
|
274
|
+
expect(result.clean).toBe(false);
|
|
275
|
+
expect(result.findings.some((f) => f.pattern === "good-pattern")).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("should report match counts", () => {
|
|
279
|
+
const scanner = new ResponseScanner({
|
|
280
|
+
detectSecrets: false,
|
|
281
|
+
patterns: [
|
|
282
|
+
{
|
|
283
|
+
name: "word-secret",
|
|
284
|
+
pattern: "secret",
|
|
285
|
+
flags: "gi",
|
|
286
|
+
action: "redact",
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
});
|
|
290
|
+
const result = scanner.scan("secret one, SECRET two, Secret three");
|
|
291
|
+
expect(result.findings[0].matchCount).toBe(3);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe("action priority", () => {
|
|
296
|
+
it("block beats redact", () => {
|
|
297
|
+
const scanner = new ResponseScanner({
|
|
298
|
+
detectSecrets: false,
|
|
299
|
+
patterns: [
|
|
300
|
+
{ name: "p1", pattern: "aaa", action: "redact" },
|
|
301
|
+
{ name: "p2", pattern: "bbb", action: "block" },
|
|
302
|
+
],
|
|
303
|
+
});
|
|
304
|
+
const result = scanner.scan("aaa and bbb");
|
|
305
|
+
expect(result.action).toBe("block");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("redact beats pass", () => {
|
|
309
|
+
const scanner = new ResponseScanner({
|
|
310
|
+
detectSecrets: false,
|
|
311
|
+
patterns: [
|
|
312
|
+
{ name: "p1", pattern: "aaa", action: "pass" },
|
|
313
|
+
{ name: "p2", pattern: "bbb", action: "redact" },
|
|
314
|
+
],
|
|
315
|
+
});
|
|
316
|
+
const result = scanner.scan("aaa and bbb");
|
|
317
|
+
expect(result.action).toBe("redact");
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("redaction", () => {
|
|
322
|
+
it("should replace matched patterns with generic [REDACTED] marker", () => {
|
|
323
|
+
const scanner = new ResponseScanner({
|
|
324
|
+
detectSecrets: false,
|
|
325
|
+
patterns: [
|
|
326
|
+
{
|
|
327
|
+
name: "secret-word",
|
|
328
|
+
pattern: "s3cr3t",
|
|
329
|
+
flags: "gi",
|
|
330
|
+
action: "redact",
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
});
|
|
334
|
+
const result = scanner.scan("The password is s3cr3t okay?");
|
|
335
|
+
expect(result.redactedText).toContain("[REDACTED]");
|
|
336
|
+
// Security: redaction marker must NOT leak the pattern name
|
|
337
|
+
expect(result.redactedText).not.toContain("secret-word");
|
|
338
|
+
expect(result.redactedText).not.toContain("s3cr3t");
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe("MCP response scanning", () => {
|
|
343
|
+
it("should extract text from MCP content array", () => {
|
|
344
|
+
const scanner = new ResponseScanner({
|
|
345
|
+
detectSecrets: false,
|
|
346
|
+
patterns: [{ name: "test", pattern: "DANGER", action: "block" }],
|
|
347
|
+
});
|
|
348
|
+
const mcpResult = {
|
|
349
|
+
content: [
|
|
350
|
+
{ type: "text", text: "This is DANGER zone" },
|
|
351
|
+
],
|
|
352
|
+
};
|
|
353
|
+
const result = scanner.scanMcpResponse(mcpResult);
|
|
354
|
+
expect(result.clean).toBe(false);
|
|
355
|
+
expect(result.action).toBe("block");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("should handle string results", () => {
|
|
359
|
+
const scanner = new ResponseScanner({
|
|
360
|
+
detectSecrets: false,
|
|
361
|
+
patterns: [{ name: "test", pattern: "DANGER", action: "redact" }],
|
|
362
|
+
});
|
|
363
|
+
const result = scanner.scanMcpResponse("Contains DANGER data");
|
|
364
|
+
expect(result.clean).toBe(false);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("should handle null/undefined results", () => {
|
|
368
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
369
|
+
const result = scanner.scanMcpResponse(null);
|
|
370
|
+
expect(result.clean).toBe(true);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("should join multiple text content blocks", () => {
|
|
374
|
+
const scanner = new ResponseScanner({
|
|
375
|
+
detectSecrets: false,
|
|
376
|
+
patterns: [{ name: "multi", pattern: "part1.*part2", flags: "gs", action: "redact" }],
|
|
377
|
+
});
|
|
378
|
+
const mcpResult = {
|
|
379
|
+
content: [
|
|
380
|
+
{ type: "text", text: "part1" },
|
|
381
|
+
{ type: "text", text: "part2" },
|
|
382
|
+
],
|
|
383
|
+
};
|
|
384
|
+
const result = scanner.scanMcpResponse(mcpResult);
|
|
385
|
+
expect(result.clean).toBe(false);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("should skip non-text content blocks", () => {
|
|
389
|
+
const scanner = new ResponseScanner({
|
|
390
|
+
detectSecrets: false,
|
|
391
|
+
patterns: [{ name: "test", pattern: "image-data", action: "block" }],
|
|
392
|
+
});
|
|
393
|
+
const mcpResult = {
|
|
394
|
+
content: [
|
|
395
|
+
{ type: "image", data: "image-data" },
|
|
396
|
+
{ type: "text", text: "safe text" },
|
|
397
|
+
],
|
|
398
|
+
};
|
|
399
|
+
const result = scanner.scanMcpResponse(mcpResult);
|
|
400
|
+
expect(result.clean).toBe(true);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("configuration", () => {
|
|
405
|
+
it("should report pattern count", () => {
|
|
406
|
+
const scanner = new ResponseScanner({
|
|
407
|
+
detectSecrets: true,
|
|
408
|
+
detectPII: true,
|
|
409
|
+
patterns: [
|
|
410
|
+
{ name: "custom1", pattern: "test1", action: "pass" },
|
|
411
|
+
{ name: "custom2", pattern: "test2", action: "pass" },
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
// Built-in secrets + PII + 2 custom
|
|
415
|
+
expect(scanner.getPatternCount()).toBeGreaterThan(10);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("should update config dynamically", () => {
|
|
419
|
+
const scanner = new ResponseScanner({ detectSecrets: true });
|
|
420
|
+
expect(scanner.getConfig().detectSecrets).toBe(true);
|
|
421
|
+
|
|
422
|
+
scanner.updateConfig({ detectSecrets: false });
|
|
423
|
+
expect(scanner.getConfig().detectSecrets).toBe(false);
|
|
424
|
+
|
|
425
|
+
// After update, secrets should not be detected
|
|
426
|
+
const result = scanner.scan("AKIAIOSFODNN7EXAMPLE");
|
|
427
|
+
expect(result.clean).toBe(true);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
describe("createDefaultScanner", () => {
|
|
432
|
+
it("should create a scanner with sensible defaults", () => {
|
|
433
|
+
const scanner = createDefaultScanner();
|
|
434
|
+
expect(scanner.getConfig().enabled).toBe(true);
|
|
435
|
+
expect(scanner.getConfig().detectSecrets).toBe(true);
|
|
436
|
+
expect(scanner.getConfig().detectPII).toBe(false);
|
|
437
|
+
expect(scanner.getConfig().maxResponseSize).toBe(5 * 1024 * 1024);
|
|
438
|
+
expect(scanner.getPatternCount()).toBeGreaterThan(0);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("should detect a private key with the default scanner", () => {
|
|
442
|
+
const scanner = createDefaultScanner();
|
|
443
|
+
const result = scanner.scan("-----BEGIN PRIVATE KEY-----\nblahblah\n-----END PRIVATE KEY-----");
|
|
444
|
+
expect(result.action).toBe("block");
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe("preview generation", () => {
|
|
449
|
+
it("should create masked previews of matched content", () => {
|
|
450
|
+
const scanner = new ResponseScanner({
|
|
451
|
+
detectSecrets: false,
|
|
452
|
+
patterns: [
|
|
453
|
+
{ name: "long-match", pattern: "supersecretlongvalue", action: "redact" },
|
|
454
|
+
],
|
|
455
|
+
});
|
|
456
|
+
const result = scanner.scan("value is supersecretlongvalue here");
|
|
457
|
+
expect(result.findings[0].preview).toBeDefined();
|
|
458
|
+
const preview = result.findings[0].preview!;
|
|
459
|
+
// Should mask the middle
|
|
460
|
+
expect(preview).toContain("...");
|
|
461
|
+
expect(preview.length).toBeLessThan("supersecretlongvalue".length);
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
});
|