shieldcortex 2.6.4 → 2.7.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/README.md +36 -5
- package/dashboard/.next/standalone/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/standalone/dashboard/.next/build-manifest.json +2 -2
- package/dashboard/.next/standalone/dashboard/.next/prerender-manifest.json +3 -3
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/pages/500.html +2 -2
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/standalone/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dist/defence/__tests__/credential-leak.test.d.ts +8 -0
- package/dist/defence/__tests__/credential-leak.test.d.ts.map +1 -0
- package/dist/defence/__tests__/credential-leak.test.js +403 -0
- package/dist/defence/__tests__/credential-leak.test.js.map +1 -0
- package/dist/defence/credential-leak/entropy.d.ts +42 -0
- package/dist/defence/credential-leak/entropy.d.ts.map +1 -0
- package/dist/defence/credential-leak/entropy.js +105 -0
- package/dist/defence/credential-leak/entropy.js.map +1 -0
- package/dist/defence/credential-leak/index.d.ts +54 -0
- package/dist/defence/credential-leak/index.d.ts.map +1 -0
- package/dist/defence/credential-leak/index.js +168 -0
- package/dist/defence/credential-leak/index.js.map +1 -0
- package/dist/defence/credential-leak/patterns.d.ts +26 -0
- package/dist/defence/credential-leak/patterns.d.ts.map +1 -0
- package/dist/defence/credential-leak/patterns.js +304 -0
- package/dist/defence/credential-leak/patterns.js.map +1 -0
- package/dist/defence/index.d.ts +2 -0
- package/dist/defence/index.d.ts.map +1 -1
- package/dist/defence/index.js +2 -0
- package/dist/defence/index.js.map +1 -1
- package/dist/defence/pipeline.d.ts +1 -1
- package/dist/defence/pipeline.d.ts.map +1 -1
- package/dist/defence/pipeline.js +24 -2
- package/dist/defence/pipeline.js.map +1 -1
- package/dist/defence/types.d.ts +1 -0
- package/dist/defence/types.d.ts.map +1 -1
- package/dist/defence/types.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- /package/dashboard/.next/standalone/dashboard/.next/static/{ku0ZhFF_US0fWJ3A7pvJa → KiOawOT3npeojP9VgWK6u}/_buildManifest.js +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{ku0ZhFF_US0fWJ3A7pvJa → KiOawOT3npeojP9VgWK6u}/_clientMiddlewareManifest.json +0 -0
- /package/dashboard/.next/standalone/dashboard/.next/static/{ku0ZhFF_US0fWJ3A7pvJa → KiOawOT3npeojP9VgWK6u}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Leak Detection Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests each credential type, entropy calculation, redaction,
|
|
5
|
+
* false positive handling, and pipeline integration.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
|
|
8
|
+
import { scanForCredentials, redactCredentials, shannonEntropy } from '../credential-leak/index.js';
|
|
9
|
+
import { initDatabase, closeDatabase } from '../../database/init.js';
|
|
10
|
+
// ── API Key Detection ──
|
|
11
|
+
describe('Credential Leak Detection', () => {
|
|
12
|
+
describe('OpenAI API Keys', () => {
|
|
13
|
+
it('should detect OpenAI sk- keys', () => {
|
|
14
|
+
const result = scanForCredentials('My key is sk-abcdefghijklmnopqrstuvwxyz1234');
|
|
15
|
+
expect(result.leaked).toBe(true);
|
|
16
|
+
expect(result.findings).toHaveLength(1);
|
|
17
|
+
expect(result.findings[0].type).toBe('api_key');
|
|
18
|
+
expect(result.findings[0].provider).toBe('openai');
|
|
19
|
+
expect(result.findings[0].severity).toBe('critical');
|
|
20
|
+
expect(result.findings[0].action).toBe('blocked');
|
|
21
|
+
});
|
|
22
|
+
it('should not trigger on short sk- prefixes like "sk-etch"', () => {
|
|
23
|
+
const result = scanForCredentials('I like to sk-etch drawings');
|
|
24
|
+
expect(result.leaked).toBe(false);
|
|
25
|
+
expect(result.findings).toHaveLength(0);
|
|
26
|
+
});
|
|
27
|
+
it('should not trigger on "sk-ip" or similar short words', () => {
|
|
28
|
+
const result = scanForCredentials('Let me sk-ip that part');
|
|
29
|
+
expect(result.leaked).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('Anthropic API Keys', () => {
|
|
33
|
+
it('should detect Anthropic sk-ant- keys', () => {
|
|
34
|
+
const result = scanForCredentials('export ANTHROPIC_API_KEY=sk-ant-api03-abcdefghijklmnopqrstuvwxyz');
|
|
35
|
+
expect(result.leaked).toBe(true);
|
|
36
|
+
expect(result.findings.some(f => f.provider === 'anthropic')).toBe(true);
|
|
37
|
+
expect(result.findings.find(f => f.provider === 'anthropic')?.severity).toBe('critical');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('AWS Keys', () => {
|
|
41
|
+
it('should detect AWS Access Key IDs', () => {
|
|
42
|
+
const result = scanForCredentials('AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE');
|
|
43
|
+
expect(result.leaked).toBe(true);
|
|
44
|
+
expect(result.findings.some(f => f.provider === 'aws')).toBe(true);
|
|
45
|
+
expect(result.findings.find(f => f.provider === 'aws')?.severity).toBe('critical');
|
|
46
|
+
});
|
|
47
|
+
it('should detect AWS Secret Access Keys', () => {
|
|
48
|
+
const result = scanForCredentials('aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
|
|
49
|
+
expect(result.leaked).toBe(true);
|
|
50
|
+
expect(result.findings.some(f => f.provider === 'aws')).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('GitHub Tokens', () => {
|
|
54
|
+
it('should detect GitHub personal access tokens (ghp_)', () => {
|
|
55
|
+
const result = scanForCredentials('token: ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij');
|
|
56
|
+
expect(result.leaked).toBe(true);
|
|
57
|
+
expect(result.findings.some(f => f.provider === 'github')).toBe(true);
|
|
58
|
+
expect(result.findings.find(f => f.provider === 'github')?.severity).toBe('critical');
|
|
59
|
+
});
|
|
60
|
+
it('should detect GitHub OAuth tokens (gho_)', () => {
|
|
61
|
+
const result = scanForCredentials('gho_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij');
|
|
62
|
+
expect(result.leaked).toBe(true);
|
|
63
|
+
expect(result.findings.some(f => f.provider === 'github')).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it('should detect GitHub fine-grained PATs', () => {
|
|
66
|
+
const result = scanForCredentials('github_pat_11ABCDEFG0abcdefghijklmn');
|
|
67
|
+
expect(result.leaked).toBe(true);
|
|
68
|
+
expect(result.findings.some(f => f.provider === 'github')).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('Stripe Keys', () => {
|
|
72
|
+
it('should detect Stripe live keys as critical', () => {
|
|
73
|
+
// Split to avoid GitHub push protection false positive
|
|
74
|
+
const key = 'sk_live_' + '51ABCDEFGHIJKLMNOPQRSTUVwx';
|
|
75
|
+
const result = scanForCredentials(key);
|
|
76
|
+
expect(result.leaked).toBe(true);
|
|
77
|
+
expect(result.findings.find(f => f.provider === 'stripe')?.severity).toBe('critical');
|
|
78
|
+
});
|
|
79
|
+
it('should detect Stripe test keys as medium', () => {
|
|
80
|
+
const key = 'sk_test_' + '51ABCDEFGHIJKLMNOPQRSTUVwx';
|
|
81
|
+
const result = scanForCredentials(key);
|
|
82
|
+
expect(result.leaked).toBe(true);
|
|
83
|
+
expect(result.findings.find(f => f.provider === 'stripe')?.severity).toBe('medium');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('Other API Keys', () => {
|
|
87
|
+
it('should detect SendGrid keys', () => {
|
|
88
|
+
const result = scanForCredentials('SG.abcdefghijklmnopqrstuv.wxyzABCDEFGHIJKLMNOPQRS');
|
|
89
|
+
expect(result.leaked).toBe(true);
|
|
90
|
+
expect(result.findings.some(f => f.provider === 'sendgrid')).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('should detect Google API keys', () => {
|
|
93
|
+
const result = scanForCredentials('key=AIzaSyDabcdefghijklmnopqrstuvwxyz123456');
|
|
94
|
+
expect(result.leaked).toBe(true);
|
|
95
|
+
expect(result.findings.some(f => f.provider === 'google')).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
it('should detect npm tokens', () => {
|
|
98
|
+
const result = scanForCredentials('//registry.npmjs.org/:_authToken=npm_abcdefghijklmnopqrstuvwxyz1234567890');
|
|
99
|
+
expect(result.leaked).toBe(true);
|
|
100
|
+
expect(result.findings.some(f => f.provider === 'npm')).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
it('should detect Slack bot tokens', () => {
|
|
103
|
+
// Split to avoid GitHub push protection false positive
|
|
104
|
+
const token = 'xoxb-' + '1234567890-abcdefghijklmnopqrstuvwx';
|
|
105
|
+
const result = scanForCredentials(`SLACK_BOT_TOKEN=${token}`);
|
|
106
|
+
expect(result.leaked).toBe(true);
|
|
107
|
+
expect(result.findings.some(f => f.provider === 'slack')).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
// ── JWT Tokens ──
|
|
111
|
+
describe('JWT Tokens', () => {
|
|
112
|
+
it('should detect JWT tokens', () => {
|
|
113
|
+
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U';
|
|
114
|
+
const result = scanForCredentials(`Authorization: Bearer ${jwt}`);
|
|
115
|
+
expect(result.leaked).toBe(true);
|
|
116
|
+
expect(result.findings.some(f => f.type === 'jwt')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
// ── Bearer/Basic Auth ──
|
|
120
|
+
describe('Auth Headers', () => {
|
|
121
|
+
it('should detect Bearer token headers', () => {
|
|
122
|
+
const result = scanForCredentials('Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.dG9rZW4.sig');
|
|
123
|
+
expect(result.leaked).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
it('should detect Basic auth headers', () => {
|
|
126
|
+
const result = scanForCredentials('Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=');
|
|
127
|
+
expect(result.leaked).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
// ── Private Keys ──
|
|
131
|
+
describe('Private Keys', () => {
|
|
132
|
+
it('should detect RSA private keys', () => {
|
|
133
|
+
const pem = '-----BEGIN RSA PRIVATE KEY-----\nMIIBogIBAAJBALRiMLAHudeSA...\n-----END RSA PRIVATE KEY-----';
|
|
134
|
+
const result = scanForCredentials(pem);
|
|
135
|
+
expect(result.leaked).toBe(true);
|
|
136
|
+
expect(result.findings.some(f => f.type === 'private_key')).toBe(true);
|
|
137
|
+
expect(result.findings.find(f => f.type === 'private_key')?.severity).toBe('critical');
|
|
138
|
+
});
|
|
139
|
+
it('should detect EC private keys', () => {
|
|
140
|
+
const pem = '-----BEGIN EC PRIVATE KEY-----\nMHQCAQEEIBLtbW...\n-----END EC PRIVATE KEY-----';
|
|
141
|
+
const result = scanForCredentials(pem);
|
|
142
|
+
expect(result.leaked).toBe(true);
|
|
143
|
+
expect(result.findings.some(f => f.type === 'private_key')).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
it('should detect SSH private keys', () => {
|
|
146
|
+
const pem = '-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1r...\n-----END OPENSSH PRIVATE KEY-----';
|
|
147
|
+
const result = scanForCredentials(pem);
|
|
148
|
+
expect(result.leaked).toBe(true);
|
|
149
|
+
expect(result.findings.some(f => f.type === 'private_key')).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
it('should detect generic private keys', () => {
|
|
152
|
+
const pem = '-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBg...\n-----END PRIVATE KEY-----';
|
|
153
|
+
const result = scanForCredentials(pem);
|
|
154
|
+
expect(result.leaked).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
// ── Connection Strings ──
|
|
158
|
+
describe('Connection Strings', () => {
|
|
159
|
+
it('should detect PostgreSQL connection strings with passwords', () => {
|
|
160
|
+
const result = scanForCredentials('DATABASE_URL=postgres://admin:s3cretP4ss@db.example.com:5432/mydb');
|
|
161
|
+
expect(result.leaked).toBe(true);
|
|
162
|
+
expect(result.findings.some(f => f.type === 'connection_string')).toBe(true);
|
|
163
|
+
expect(result.findings.find(f => f.type === 'connection_string')?.provider).toBe('postgres');
|
|
164
|
+
});
|
|
165
|
+
it('should detect MySQL connection strings', () => {
|
|
166
|
+
const result = scanForCredentials('mysql://root:password123@localhost:3306/app');
|
|
167
|
+
expect(result.leaked).toBe(true);
|
|
168
|
+
expect(result.findings.some(f => f.provider === 'mysql')).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
it('should detect MongoDB connection strings', () => {
|
|
171
|
+
const result = scanForCredentials('mongodb+srv://user:pass123@cluster0.example.mongodb.net/db');
|
|
172
|
+
expect(result.leaked).toBe(true);
|
|
173
|
+
expect(result.findings.some(f => f.provider === 'mongodb')).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
it('should detect Redis connection strings', () => {
|
|
176
|
+
const result = scanForCredentials('redis://default:mypassword@redis.example.com:6379');
|
|
177
|
+
expect(result.leaked).toBe(true);
|
|
178
|
+
expect(result.findings.some(f => f.provider === 'redis')).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
// ── Environment Variable Patterns ──
|
|
182
|
+
describe('Environment Variable Secrets', () => {
|
|
183
|
+
it('should detect PASSWORD= assignments', () => {
|
|
184
|
+
const result = scanForCredentials('DB_PASSWORD=super_secret_password_123');
|
|
185
|
+
expect(result.leaked).toBe(true);
|
|
186
|
+
expect(result.findings.some(f => f.type === 'env_secret')).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
it('should detect SECRET= assignments', () => {
|
|
189
|
+
const result = scanForCredentials('JWT_SECRET="my-super-secret-jwt-key-12345"');
|
|
190
|
+
expect(result.leaked).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
it('should detect TOKEN= assignments', () => {
|
|
193
|
+
const result = scanForCredentials('API_TOKEN=abcdef1234567890ghijklmn');
|
|
194
|
+
expect(result.leaked).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
it('should detect API_KEY= assignments', () => {
|
|
197
|
+
const result = scanForCredentials('API_KEY=some-long-api-key-value-here');
|
|
198
|
+
expect(result.leaked).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
it('should not trigger on short values', () => {
|
|
201
|
+
const result = scanForCredentials('PASSWORD=short');
|
|
202
|
+
// Value "short" is < 8 chars, should not trigger env_secret
|
|
203
|
+
const envFindings = result.findings.filter(f => f.type === 'env_secret');
|
|
204
|
+
expect(envFindings).toHaveLength(0);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
// ── Entropy Calculation ──
|
|
208
|
+
describe('Shannon Entropy', () => {
|
|
209
|
+
it('should calculate entropy of uniform distribution', () => {
|
|
210
|
+
// All unique chars in "abcdefgh" → log2(8) = 3.0
|
|
211
|
+
const e = shannonEntropy('abcdefgh');
|
|
212
|
+
expect(e).toBeCloseTo(3.0, 1);
|
|
213
|
+
});
|
|
214
|
+
it('should return 0 for empty string', () => {
|
|
215
|
+
expect(shannonEntropy('')).toBe(0);
|
|
216
|
+
});
|
|
217
|
+
it('should return 0 for single repeated char', () => {
|
|
218
|
+
expect(shannonEntropy('aaaaaaa')).toBe(0);
|
|
219
|
+
});
|
|
220
|
+
it('should have higher entropy for random-looking strings', () => {
|
|
221
|
+
const lowEntropy = shannonEntropy('aaabbbccc');
|
|
222
|
+
const highEntropy = shannonEntropy('Kx9$mQ2!pL7@nR4^');
|
|
223
|
+
expect(highEntropy).toBeGreaterThan(lowEntropy);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
// ── High Entropy Detection ──
|
|
227
|
+
describe('High Entropy Detection', () => {
|
|
228
|
+
it('should flag high-entropy strings 20+ chars as potential secrets', () => {
|
|
229
|
+
// This is a high-entropy random string
|
|
230
|
+
const result = scanForCredentials('token: aB3xK9mQ2pL7nR4cT8vY1wZ5fH6jD0s');
|
|
231
|
+
// Should detect either via pattern or entropy
|
|
232
|
+
expect(result.leaked).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
it('should not flag normal English text', () => {
|
|
235
|
+
const result = scanForCredentials('The quick brown fox jumps over the lazy dog');
|
|
236
|
+
// No API key patterns, low entropy for English
|
|
237
|
+
const highEntropyFindings = result.findings.filter(f => f.type === 'high_entropy');
|
|
238
|
+
expect(highEntropyFindings).toHaveLength(0);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
// ── Redaction ──
|
|
242
|
+
describe('Redaction', () => {
|
|
243
|
+
it('should redact detected secrets in content', () => {
|
|
244
|
+
const content = 'My key is sk-abcdefghijklmnopqrstuvwxyz1234';
|
|
245
|
+
const redacted = redactCredentials(content);
|
|
246
|
+
expect(redacted).not.toContain('sk-abcdefghijklmnopqrstuvwxyz1234');
|
|
247
|
+
expect(redacted).toContain('[REDACTED-');
|
|
248
|
+
});
|
|
249
|
+
it('should redact multiple secrets', () => {
|
|
250
|
+
const content = 'OpenAI: sk-abcdefghijklmnopqrstuvwxyz1234\nGitHub: ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij';
|
|
251
|
+
const redacted = redactCredentials(content);
|
|
252
|
+
expect(redacted).not.toContain('sk-abcdefghijklmnopqrstuvwx');
|
|
253
|
+
expect(redacted).not.toContain('ghp_ABCDEFGHIJKLMNOPQR');
|
|
254
|
+
expect(redacted).toContain('[REDACTED-');
|
|
255
|
+
});
|
|
256
|
+
it('should return original content if no secrets found', () => {
|
|
257
|
+
const content = 'Just some normal text with no secrets';
|
|
258
|
+
const redacted = redactCredentials(content);
|
|
259
|
+
expect(redacted).toBe(content);
|
|
260
|
+
});
|
|
261
|
+
it('should show first/last 4 chars in match field for long secrets', () => {
|
|
262
|
+
const result = scanForCredentials('key is sk-abcdefghijklmnopqrstuvwxyz1234');
|
|
263
|
+
expect(result.findings.length).toBeGreaterThan(0);
|
|
264
|
+
const match = result.findings[0].match;
|
|
265
|
+
// Should look like "sk-a...1234" (first 4 + ... + last 4)
|
|
266
|
+
expect(match).toContain('...');
|
|
267
|
+
});
|
|
268
|
+
it('should redact private keys', () => {
|
|
269
|
+
const content = 'Here is my key:\n-----BEGIN RSA PRIVATE KEY-----\nMIIBogIBAAJBALRiMLAH\n-----END RSA PRIVATE KEY-----\nDone';
|
|
270
|
+
const redacted = redactCredentials(content);
|
|
271
|
+
expect(redacted).toContain('[REDACTED-private_key');
|
|
272
|
+
expect(redacted).not.toContain('MIIBogIBAAJBALRiMLAH');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
// ── False Positive Handling ──
|
|
276
|
+
describe('False Positive Handling', () => {
|
|
277
|
+
it('should not trigger on common words starting with "sk-"', () => {
|
|
278
|
+
const texts = [
|
|
279
|
+
'I want to sk-ip this',
|
|
280
|
+
'Let me sk-etch it out',
|
|
281
|
+
'The sk-ill level is high',
|
|
282
|
+
];
|
|
283
|
+
for (const text of texts) {
|
|
284
|
+
const result = scanForCredentials(text);
|
|
285
|
+
const apiKeyFindings = result.findings.filter(f => f.type === 'api_key' && f.provider === 'openai');
|
|
286
|
+
expect(apiKeyFindings).toHaveLength(0);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
it('should not trigger on example/placeholder API keys in documentation', () => {
|
|
290
|
+
// Allow "sk-..." only when actually long enough to be a real key
|
|
291
|
+
const result = scanForCredentials('Example: sk-your-key-here');
|
|
292
|
+
// "sk-your-key-here" is only 16 chars; the minLength is 24
|
|
293
|
+
const openaiFindings = result.findings.filter(f => f.provider === 'openai');
|
|
294
|
+
expect(openaiFindings).toHaveLength(0);
|
|
295
|
+
});
|
|
296
|
+
it('should not flag CSS class names as high-entropy', () => {
|
|
297
|
+
const result = scanForCredentials('class="flex-items-center-justify-between-px-4"');
|
|
298
|
+
const entropyFindings = result.findings.filter(f => f.type === 'high_entropy');
|
|
299
|
+
expect(entropyFindings).toHaveLength(0);
|
|
300
|
+
});
|
|
301
|
+
it('should respect allowlist', () => {
|
|
302
|
+
const result = scanForCredentials('sk-abcdefghijklmnopqrstuvwxyz1234', { allowlist: ['sk-abcdefg'] });
|
|
303
|
+
expect(result.findings.filter(f => f.provider === 'openai')).toHaveLength(0);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
// ── Config Options ──
|
|
307
|
+
describe('Configuration', () => {
|
|
308
|
+
it('should not scan when disabled', () => {
|
|
309
|
+
const result = scanForCredentials('sk-abcdefghijklmnopqrstuvwxyz1234', { enabled: false });
|
|
310
|
+
expect(result.leaked).toBe(false);
|
|
311
|
+
expect(result.findings).toHaveLength(0);
|
|
312
|
+
});
|
|
313
|
+
it('should warn instead of block when blockOnCritical is false', () => {
|
|
314
|
+
const result = scanForCredentials('sk-abcdefghijklmnopqrstuvwxyz1234', {
|
|
315
|
+
blockOnCritical: false,
|
|
316
|
+
blockOnHigh: false,
|
|
317
|
+
});
|
|
318
|
+
expect(result.leaked).toBe(true);
|
|
319
|
+
expect(result.findings[0].action).not.toBe('blocked');
|
|
320
|
+
});
|
|
321
|
+
it('should support custom patterns', () => {
|
|
322
|
+
const result = scanForCredentials('my-custom-secret-XYZ123ABC', {
|
|
323
|
+
customPatterns: [{
|
|
324
|
+
name: 'Custom Secret',
|
|
325
|
+
type: 'api_key',
|
|
326
|
+
provider: 'custom',
|
|
327
|
+
regex: /my-custom-secret-[A-Z0-9]+/g,
|
|
328
|
+
severity: 'high',
|
|
329
|
+
confidence: 0.90,
|
|
330
|
+
}],
|
|
331
|
+
});
|
|
332
|
+
expect(result.leaked).toBe(true);
|
|
333
|
+
expect(result.findings.some(f => f.provider === 'custom')).toBe(true);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
// ── Multiple Credentials in Single Content ──
|
|
337
|
+
describe('Multiple Credentials', () => {
|
|
338
|
+
it('should detect multiple different credential types', () => {
|
|
339
|
+
const content = `
|
|
340
|
+
OPENAI_API_KEY=sk-abcdefghijklmnopqrstuvwxyz1234
|
|
341
|
+
DATABASE_URL=postgres://admin:password@db.example.com:5432/mydb
|
|
342
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
343
|
+
MIIBogIBAAJBALRiMLAHudeSA
|
|
344
|
+
-----END RSA PRIVATE KEY-----
|
|
345
|
+
`;
|
|
346
|
+
const result = scanForCredentials(content);
|
|
347
|
+
expect(result.leaked).toBe(true);
|
|
348
|
+
expect(result.findings.length).toBeGreaterThanOrEqual(3);
|
|
349
|
+
const types = result.findings.map(f => f.type);
|
|
350
|
+
expect(types).toContain('api_key');
|
|
351
|
+
expect(types).toContain('connection_string');
|
|
352
|
+
expect(types).toContain('private_key');
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
// ── Position Tracking ──
|
|
356
|
+
describe('Position Tracking', () => {
|
|
357
|
+
it('should report correct character offset positions', () => {
|
|
358
|
+
const prefix = 'prefix text ';
|
|
359
|
+
const content = `${prefix}sk-abcdefghijklmnopqrstuvwxyz1234`;
|
|
360
|
+
const result = scanForCredentials(content);
|
|
361
|
+
expect(result.findings.length).toBeGreaterThan(0);
|
|
362
|
+
expect(result.findings[0].position).toBe(prefix.length);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
// ── Pipeline Integration ──
|
|
367
|
+
describe('Pipeline Integration', () => {
|
|
368
|
+
beforeAll(() => {
|
|
369
|
+
initDatabase(':memory:');
|
|
370
|
+
});
|
|
371
|
+
afterAll(() => {
|
|
372
|
+
closeDatabase();
|
|
373
|
+
});
|
|
374
|
+
const testConfig = {
|
|
375
|
+
mode: 'balanced',
|
|
376
|
+
enableFragmentationDetection: false,
|
|
377
|
+
fragmentationWindowHours: 24,
|
|
378
|
+
trustThresholdForActions: 0.7,
|
|
379
|
+
autoQuarantineThreshold: 0.3,
|
|
380
|
+
flagThreshold: 0.5,
|
|
381
|
+
strictSourceMode: false,
|
|
382
|
+
};
|
|
383
|
+
it('should block content with critical credentials via pipeline', async () => {
|
|
384
|
+
const { runDefencePipeline } = await import('../pipeline.js');
|
|
385
|
+
const result = runDefencePipeline('Save this: my API key is sk-abcdefghijklmnopqrstuvwxyz1234', 'API key note', { type: 'agent', identifier: 'test-agent' }, testConfig);
|
|
386
|
+
expect(result.allowed).toBe(false);
|
|
387
|
+
expect(result.firewall.result).toBe('BLOCK');
|
|
388
|
+
expect(result.firewall.threatIndicators).toContain('credential_leak');
|
|
389
|
+
});
|
|
390
|
+
it('should include credentialScan in pipeline result when leaked', async () => {
|
|
391
|
+
const { runDefencePipeline } = await import('../pipeline.js');
|
|
392
|
+
const result = runDefencePipeline('postgres://admin:secret@db.host.com:5432/mydb', 'Database config', { type: 'cli', identifier: 'test' }, testConfig);
|
|
393
|
+
expect(result.credentialScan).toBeDefined();
|
|
394
|
+
expect(result.credentialScan.leaked).toBe(true);
|
|
395
|
+
expect(result.credentialScan.findings.length).toBeGreaterThan(0);
|
|
396
|
+
});
|
|
397
|
+
it('should not include credentialScan when no leaks found', async () => {
|
|
398
|
+
const { runDefencePipeline } = await import('../pipeline.js');
|
|
399
|
+
const result = runDefencePipeline('Just a normal note about architecture decisions', 'Meeting notes', { type: 'user', identifier: 'direct' }, testConfig);
|
|
400
|
+
expect(result.credentialScan).toBeUndefined();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
//# sourceMappingURL=credential-leak.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-leak.test.js","sourceRoot":"","sources":["../../../src/defence/__tests__/credential-leak.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Leak Detection — Entropy Analysis
|
|
3
|
+
*
|
|
4
|
+
* Shannon entropy calculation for detecting high-entropy strings
|
|
5
|
+
* that look like secrets (random tokens, keys, passwords).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Calculate Shannon entropy of a string (bits per character).
|
|
9
|
+
* Higher entropy = more random/unpredictable.
|
|
10
|
+
*
|
|
11
|
+
* Reference values:
|
|
12
|
+
* - English text: ~3.5-4.0
|
|
13
|
+
* - Base64 encoded: ~5.0-5.5
|
|
14
|
+
* - Random hex: ~3.5-4.0
|
|
15
|
+
* - Random alphanumeric: ~5.5-5.9
|
|
16
|
+
* - Crypto keys: ~5.5-6.0+
|
|
17
|
+
*/
|
|
18
|
+
export declare function shannonEntropy(str: string): number;
|
|
19
|
+
/** Minimum string length for entropy-based detection */
|
|
20
|
+
export declare const MIN_ENTROPY_LENGTH = 20;
|
|
21
|
+
/** Entropy threshold — strings above this are flagged */
|
|
22
|
+
export declare const ENTROPY_THRESHOLD = 4.5;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a string looks like a high-entropy secret.
|
|
25
|
+
* Returns confidence score (0-1) or null if not suspicious.
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkHighEntropy(str: string): {
|
|
28
|
+
entropy: number;
|
|
29
|
+
confidence: number;
|
|
30
|
+
} | null;
|
|
31
|
+
/**
|
|
32
|
+
* Extract candidate high-entropy tokens from content.
|
|
33
|
+
* Looks for long contiguous alphanumeric+special strings
|
|
34
|
+
* that could be secrets.
|
|
35
|
+
*/
|
|
36
|
+
export declare function extractHighEntropyTokens(content: string): Array<{
|
|
37
|
+
token: string;
|
|
38
|
+
position: number;
|
|
39
|
+
entropy: number;
|
|
40
|
+
confidence: number;
|
|
41
|
+
}>;
|
|
42
|
+
//# sourceMappingURL=entropy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entropy.d.ts","sourceRoot":"","sources":["../../../src/defence/credential-leak/entropy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgBlD;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAW5F;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,GACd,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CA2BjF"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Leak Detection — Entropy Analysis
|
|
3
|
+
*
|
|
4
|
+
* Shannon entropy calculation for detecting high-entropy strings
|
|
5
|
+
* that look like secrets (random tokens, keys, passwords).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Calculate Shannon entropy of a string (bits per character).
|
|
9
|
+
* Higher entropy = more random/unpredictable.
|
|
10
|
+
*
|
|
11
|
+
* Reference values:
|
|
12
|
+
* - English text: ~3.5-4.0
|
|
13
|
+
* - Base64 encoded: ~5.0-5.5
|
|
14
|
+
* - Random hex: ~3.5-4.0
|
|
15
|
+
* - Random alphanumeric: ~5.5-5.9
|
|
16
|
+
* - Crypto keys: ~5.5-6.0+
|
|
17
|
+
*/
|
|
18
|
+
export function shannonEntropy(str) {
|
|
19
|
+
if (str.length === 0)
|
|
20
|
+
return 0;
|
|
21
|
+
const freq = new Map();
|
|
22
|
+
for (const ch of str) {
|
|
23
|
+
freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
24
|
+
}
|
|
25
|
+
let entropy = 0;
|
|
26
|
+
const len = str.length;
|
|
27
|
+
for (const count of freq.values()) {
|
|
28
|
+
const p = count / len;
|
|
29
|
+
entropy -= p * Math.log2(p);
|
|
30
|
+
}
|
|
31
|
+
return entropy;
|
|
32
|
+
}
|
|
33
|
+
/** Minimum string length for entropy-based detection */
|
|
34
|
+
export const MIN_ENTROPY_LENGTH = 20;
|
|
35
|
+
/** Entropy threshold — strings above this are flagged */
|
|
36
|
+
export const ENTROPY_THRESHOLD = 4.5;
|
|
37
|
+
/**
|
|
38
|
+
* Check if a string looks like a high-entropy secret.
|
|
39
|
+
* Returns confidence score (0-1) or null if not suspicious.
|
|
40
|
+
*/
|
|
41
|
+
export function checkHighEntropy(str) {
|
|
42
|
+
if (str.length < MIN_ENTROPY_LENGTH)
|
|
43
|
+
return null;
|
|
44
|
+
const entropy = shannonEntropy(str);
|
|
45
|
+
if (entropy < ENTROPY_THRESHOLD)
|
|
46
|
+
return null;
|
|
47
|
+
// Confidence scales with entropy above threshold
|
|
48
|
+
// 4.5 → 0.50, 5.0 → 0.65, 5.5 → 0.80, 6.0 → 0.95
|
|
49
|
+
const confidence = Math.min(0.95, 0.50 + (entropy - ENTROPY_THRESHOLD) * 0.30);
|
|
50
|
+
return { entropy, confidence };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract candidate high-entropy tokens from content.
|
|
54
|
+
* Looks for long contiguous alphanumeric+special strings
|
|
55
|
+
* that could be secrets.
|
|
56
|
+
*/
|
|
57
|
+
export function extractHighEntropyTokens(content) {
|
|
58
|
+
// Match long strings of alphanumeric + common secret chars
|
|
59
|
+
const tokenRegex = /[A-Za-z0-9\-_./+=]{20,}/g;
|
|
60
|
+
const results = [];
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
let match;
|
|
63
|
+
while ((match = tokenRegex.exec(content)) !== null) {
|
|
64
|
+
const token = match[0];
|
|
65
|
+
if (seen.has(token))
|
|
66
|
+
continue;
|
|
67
|
+
seen.add(token);
|
|
68
|
+
// Skip common false positives
|
|
69
|
+
if (isLikelyFalsePositive(token))
|
|
70
|
+
continue;
|
|
71
|
+
const result = checkHighEntropy(token);
|
|
72
|
+
if (result) {
|
|
73
|
+
results.push({
|
|
74
|
+
token,
|
|
75
|
+
position: match.index,
|
|
76
|
+
entropy: result.entropy,
|
|
77
|
+
confidence: result.confidence,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Heuristic filter to reduce false positives from entropy detection.
|
|
85
|
+
*/
|
|
86
|
+
function isLikelyFalsePositive(token) {
|
|
87
|
+
// Pure lowercase with dashes — likely a slug or CSS class
|
|
88
|
+
if (/^[a-z\-]+$/.test(token))
|
|
89
|
+
return true;
|
|
90
|
+
// Looks like a file path
|
|
91
|
+
if (token.includes('/') && !token.includes('//') && /\.[a-z]{2,4}$/i.test(token))
|
|
92
|
+
return true;
|
|
93
|
+
// Repeated character sequences (aaaaaaa...)
|
|
94
|
+
if (/^(.)\1{10,}$/.test(token))
|
|
95
|
+
return true;
|
|
96
|
+
// Common base64 padding pattern (just padding)
|
|
97
|
+
if (/^=+$/.test(token) || /^[A-Za-z0-9+/]*={3,}$/.test(token))
|
|
98
|
+
return true;
|
|
99
|
+
// Long runs of a single character class with low variety
|
|
100
|
+
const uniqueChars = new Set(token).size;
|
|
101
|
+
if (uniqueChars < 6 && token.length > 20)
|
|
102
|
+
return true;
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=entropy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entropy.js","sourceRoot":"","sources":["../../../src/defence/credential-leak/entropy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Leak Detection — Layer 6
|
|
3
|
+
*
|
|
4
|
+
* Detects credentials, secrets, and sensitive tokens accidentally
|
|
5
|
+
* persisted in AI agent memory writes. Supports known API key formats,
|
|
6
|
+
* generic secrets, private keys, connection strings, environment
|
|
7
|
+
* variable patterns, and high-entropy string heuristics.
|
|
8
|
+
*/
|
|
9
|
+
import { type CredentialPattern, type CredentialType, type CredentialSeverity } from './patterns.js';
|
|
10
|
+
export interface CredentialFinding {
|
|
11
|
+
type: CredentialType;
|
|
12
|
+
provider?: string;
|
|
13
|
+
confidence: number;
|
|
14
|
+
severity: CredentialSeverity;
|
|
15
|
+
/** Redacted version showing first/last 4 chars */
|
|
16
|
+
match: string;
|
|
17
|
+
/** Char offset in content */
|
|
18
|
+
position: number;
|
|
19
|
+
action: 'blocked' | 'warned' | 'logged';
|
|
20
|
+
}
|
|
21
|
+
export interface CredentialScanResult {
|
|
22
|
+
leaked: boolean;
|
|
23
|
+
findings: CredentialFinding[];
|
|
24
|
+
redactedContent?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface CredentialDetectionConfig {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
blockOnCritical: boolean;
|
|
29
|
+
blockOnHigh: boolean;
|
|
30
|
+
warnOnMedium: boolean;
|
|
31
|
+
customPatterns: CredentialPattern[];
|
|
32
|
+
allowlist: string[];
|
|
33
|
+
}
|
|
34
|
+
export declare const DEFAULT_CREDENTIAL_CONFIG: CredentialDetectionConfig;
|
|
35
|
+
/**
|
|
36
|
+
* Scan content for credential leaks.
|
|
37
|
+
*
|
|
38
|
+
* Checks known API key formats, generic secrets, private keys,
|
|
39
|
+
* connection strings, env variable patterns, and high-entropy strings.
|
|
40
|
+
*
|
|
41
|
+
* @param content - The text content to scan
|
|
42
|
+
* @param config - Optional credential detection configuration
|
|
43
|
+
* @returns Scan result with findings and optional redacted content
|
|
44
|
+
*/
|
|
45
|
+
export declare function scanForCredentials(content: string, config?: Partial<CredentialDetectionConfig>): CredentialScanResult;
|
|
46
|
+
/**
|
|
47
|
+
* Replace all detected secrets in content with [REDACTED-{type}] placeholders.
|
|
48
|
+
* Useful for agents that want to store memory but strip the secrets.
|
|
49
|
+
*/
|
|
50
|
+
export declare function redactCredentials(content: string, config?: Partial<CredentialDetectionConfig>): string;
|
|
51
|
+
export type { CredentialPattern, CredentialType, CredentialSeverity } from './patterns.js';
|
|
52
|
+
export { shannonEntropy, checkHighEntropy } from './entropy.js';
|
|
53
|
+
export { ALL_CREDENTIAL_PATTERNS } from './patterns.js';
|
|
54
|
+
//# sourceMappingURL=index.d.ts.map
|