tlc-claude-code 1.4.7 → 1.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/docker-compose.dev.yml +6 -3
  2. package/package.json +1 -1
  3. package/server/index.js +229 -14
  4. package/server/lib/compliance/control-mapper.js +401 -0
  5. package/server/lib/compliance/control-mapper.test.js +117 -0
  6. package/server/lib/compliance/evidence-linker.js +296 -0
  7. package/server/lib/compliance/evidence-linker.test.js +121 -0
  8. package/server/lib/compliance/gdpr-checklist.js +416 -0
  9. package/server/lib/compliance/gdpr-checklist.test.js +131 -0
  10. package/server/lib/compliance/hipaa-checklist.js +277 -0
  11. package/server/lib/compliance/hipaa-checklist.test.js +101 -0
  12. package/server/lib/compliance/iso27001-checklist.js +287 -0
  13. package/server/lib/compliance/iso27001-checklist.test.js +99 -0
  14. package/server/lib/compliance/multi-framework-reporter.js +284 -0
  15. package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
  16. package/server/lib/compliance/pci-dss-checklist.js +214 -0
  17. package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
  18. package/server/lib/compliance/trust-centre.js +187 -0
  19. package/server/lib/compliance/trust-centre.test.js +93 -0
  20. package/server/lib/dashboard/api-server.js +155 -0
  21. package/server/lib/dashboard/api-server.test.js +155 -0
  22. package/server/lib/dashboard/health-api.js +199 -0
  23. package/server/lib/dashboard/health-api.test.js +122 -0
  24. package/server/lib/dashboard/notes-api.js +234 -0
  25. package/server/lib/dashboard/notes-api.test.js +134 -0
  26. package/server/lib/dashboard/router-api.js +176 -0
  27. package/server/lib/dashboard/router-api.test.js +132 -0
  28. package/server/lib/dashboard/tasks-api.js +289 -0
  29. package/server/lib/dashboard/tasks-api.test.js +161 -0
  30. package/server/lib/dashboard/tlc-introspection.js +197 -0
  31. package/server/lib/dashboard/tlc-introspection.test.js +138 -0
  32. package/server/lib/dashboard/version-api.js +222 -0
  33. package/server/lib/dashboard/version-api.test.js +112 -0
  34. package/server/lib/dashboard/websocket-server.js +104 -0
  35. package/server/lib/dashboard/websocket-server.test.js +118 -0
  36. package/server/lib/deploy/branch-classifier.js +163 -0
  37. package/server/lib/deploy/branch-classifier.test.js +164 -0
  38. package/server/lib/deploy/deployment-approval.js +299 -0
  39. package/server/lib/deploy/deployment-approval.test.js +296 -0
  40. package/server/lib/deploy/deployment-audit.js +374 -0
  41. package/server/lib/deploy/deployment-audit.test.js +307 -0
  42. package/server/lib/deploy/deployment-executor.js +335 -0
  43. package/server/lib/deploy/deployment-executor.test.js +329 -0
  44. package/server/lib/deploy/deployment-rules.js +163 -0
  45. package/server/lib/deploy/deployment-rules.test.js +188 -0
  46. package/server/lib/deploy/rollback-manager.js +379 -0
  47. package/server/lib/deploy/rollback-manager.test.js +321 -0
  48. package/server/lib/deploy/security-gates.js +236 -0
  49. package/server/lib/deploy/security-gates.test.js +222 -0
  50. package/server/lib/k8s/gitops-config.js +188 -0
  51. package/server/lib/k8s/gitops-config.test.js +59 -0
  52. package/server/lib/k8s/helm-generator.js +196 -0
  53. package/server/lib/k8s/helm-generator.test.js +59 -0
  54. package/server/lib/k8s/kustomize-generator.js +176 -0
  55. package/server/lib/k8s/kustomize-generator.test.js +58 -0
  56. package/server/lib/k8s/network-policy.js +114 -0
  57. package/server/lib/k8s/network-policy.test.js +53 -0
  58. package/server/lib/k8s/pod-security.js +114 -0
  59. package/server/lib/k8s/pod-security.test.js +55 -0
  60. package/server/lib/k8s/rbac-generator.js +132 -0
  61. package/server/lib/k8s/rbac-generator.test.js +57 -0
  62. package/server/lib/k8s/resource-manager.js +172 -0
  63. package/server/lib/k8s/resource-manager.test.js +60 -0
  64. package/server/lib/k8s/secrets-encryption.js +168 -0
  65. package/server/lib/k8s/secrets-encryption.test.js +49 -0
  66. package/server/lib/monitoring/alert-manager.js +238 -0
  67. package/server/lib/monitoring/alert-manager.test.js +106 -0
  68. package/server/lib/monitoring/health-check.js +226 -0
  69. package/server/lib/monitoring/health-check.test.js +176 -0
  70. package/server/lib/monitoring/incident-manager.js +230 -0
  71. package/server/lib/monitoring/incident-manager.test.js +98 -0
  72. package/server/lib/monitoring/log-aggregator.js +147 -0
  73. package/server/lib/monitoring/log-aggregator.test.js +89 -0
  74. package/server/lib/monitoring/metrics-collector.js +337 -0
  75. package/server/lib/monitoring/metrics-collector.test.js +172 -0
  76. package/server/lib/monitoring/status-page.js +214 -0
  77. package/server/lib/monitoring/status-page.test.js +105 -0
  78. package/server/lib/monitoring/uptime-monitor.js +194 -0
  79. package/server/lib/monitoring/uptime-monitor.test.js +109 -0
  80. package/server/lib/network/fail2ban-config.js +294 -0
  81. package/server/lib/network/fail2ban-config.test.js +275 -0
  82. package/server/lib/network/firewall-manager.js +252 -0
  83. package/server/lib/network/firewall-manager.test.js +254 -0
  84. package/server/lib/network/geoip-filter.js +282 -0
  85. package/server/lib/network/geoip-filter.test.js +264 -0
  86. package/server/lib/network/rate-limiter.js +229 -0
  87. package/server/lib/network/rate-limiter.test.js +293 -0
  88. package/server/lib/network/request-validator.js +351 -0
  89. package/server/lib/network/request-validator.test.js +345 -0
  90. package/server/lib/network/security-headers.js +251 -0
  91. package/server/lib/network/security-headers.test.js +283 -0
  92. package/server/lib/network/tls-config.js +210 -0
  93. package/server/lib/network/tls-config.test.js +248 -0
  94. package/server/lib/security/auth-security.js +369 -0
  95. package/server/lib/security/auth-security.test.js +448 -0
  96. package/server/lib/security/cis-benchmark.js +152 -0
  97. package/server/lib/security/cis-benchmark.test.js +137 -0
  98. package/server/lib/security/compose-templates.js +312 -0
  99. package/server/lib/security/compose-templates.test.js +229 -0
  100. package/server/lib/security/container-runtime.js +456 -0
  101. package/server/lib/security/container-runtime.test.js +503 -0
  102. package/server/lib/security/cors-validator.js +278 -0
  103. package/server/lib/security/cors-validator.test.js +310 -0
  104. package/server/lib/security/crypto-utils.js +253 -0
  105. package/server/lib/security/crypto-utils.test.js +409 -0
  106. package/server/lib/security/dockerfile-linter.js +459 -0
  107. package/server/lib/security/dockerfile-linter.test.js +483 -0
  108. package/server/lib/security/dockerfile-templates.js +278 -0
  109. package/server/lib/security/dockerfile-templates.test.js +164 -0
  110. package/server/lib/security/error-sanitizer.js +426 -0
  111. package/server/lib/security/error-sanitizer.test.js +331 -0
  112. package/server/lib/security/headers-generator.js +368 -0
  113. package/server/lib/security/headers-generator.test.js +398 -0
  114. package/server/lib/security/image-scanner.js +83 -0
  115. package/server/lib/security/image-scanner.test.js +106 -0
  116. package/server/lib/security/input-validator.js +352 -0
  117. package/server/lib/security/input-validator.test.js +330 -0
  118. package/server/lib/security/network-policy.js +174 -0
  119. package/server/lib/security/network-policy.test.js +164 -0
  120. package/server/lib/security/output-encoder.js +237 -0
  121. package/server/lib/security/output-encoder.test.js +276 -0
  122. package/server/lib/security/path-validator.js +359 -0
  123. package/server/lib/security/path-validator.test.js +293 -0
  124. package/server/lib/security/query-builder.js +421 -0
  125. package/server/lib/security/query-builder.test.js +318 -0
  126. package/server/lib/security/secret-detector.js +290 -0
  127. package/server/lib/security/secret-detector.test.js +354 -0
  128. package/server/lib/security/secrets-validator.js +137 -0
  129. package/server/lib/security/secrets-validator.test.js +120 -0
  130. package/server/lib/security-testing/dast-runner.js +154 -0
  131. package/server/lib/security-testing/dast-runner.test.js +62 -0
  132. package/server/lib/security-testing/dependency-scanner.js +172 -0
  133. package/server/lib/security-testing/dependency-scanner.test.js +64 -0
  134. package/server/lib/security-testing/pentest-runner.js +230 -0
  135. package/server/lib/security-testing/pentest-runner.test.js +60 -0
  136. package/server/lib/security-testing/sast-runner.js +136 -0
  137. package/server/lib/security-testing/sast-runner.test.js +62 -0
  138. package/server/lib/security-testing/secret-scanner.js +153 -0
  139. package/server/lib/security-testing/secret-scanner.test.js +66 -0
  140. package/server/lib/security-testing/security-gate.js +216 -0
  141. package/server/lib/security-testing/security-gate.test.js +115 -0
  142. package/server/lib/security-testing/security-reporter.js +303 -0
  143. package/server/lib/security-testing/security-reporter.test.js +114 -0
  144. package/server/lib/standards/audit-checker.js +546 -0
  145. package/server/lib/standards/audit-checker.test.js +415 -0
  146. package/server/lib/standards/cleanup-executor.js +452 -0
  147. package/server/lib/standards/cleanup-executor.test.js +293 -0
  148. package/server/lib/standards/refactor-stepper.js +425 -0
  149. package/server/lib/standards/refactor-stepper.test.js +298 -0
  150. package/server/lib/standards/standards-injector.js +167 -0
  151. package/server/lib/standards/standards-injector.test.js +232 -0
  152. package/server/lib/user-management.test.js +284 -0
  153. package/server/lib/vps/backup-manager.js +157 -0
  154. package/server/lib/vps/backup-manager.test.js +59 -0
  155. package/server/lib/vps/caddy-config.js +159 -0
  156. package/server/lib/vps/caddy-config.test.js +48 -0
  157. package/server/lib/vps/compose-orchestrator.js +219 -0
  158. package/server/lib/vps/compose-orchestrator.test.js +50 -0
  159. package/server/lib/vps/database-config.js +208 -0
  160. package/server/lib/vps/database-config.test.js +47 -0
  161. package/server/lib/vps/deploy-script.js +211 -0
  162. package/server/lib/vps/deploy-script.test.js +53 -0
  163. package/server/lib/vps/secrets-manager.js +148 -0
  164. package/server/lib/vps/secrets-manager.test.js +58 -0
  165. package/server/lib/vps/server-hardening.js +174 -0
  166. package/server/lib/vps/server-hardening.test.js +70 -0
  167. package/server/package-lock.json +19 -0
  168. package/server/package.json +1 -0
  169. package/server/templates/CLAUDE.md +37 -0
  170. package/server/templates/CODING-STANDARDS.md +408 -0
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Query Builder Tests
3
+ *
4
+ * Tests for safe parameterized query building.
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import {
9
+ createQueryBuilder,
10
+ select,
11
+ insert,
12
+ update,
13
+ del,
14
+ SecurityError,
15
+ } from './query-builder.js';
16
+
17
+ describe('query-builder', () => {
18
+ describe('select', () => {
19
+ it('builds SELECT with simple WHERE', () => {
20
+ const result = select('users')
21
+ .columns(['id', 'name', 'email'])
22
+ .where('id', '=', 1)
23
+ .build();
24
+
25
+ expect(result.sql).toBe('SELECT id, name, email FROM users WHERE id = $1');
26
+ expect(result.params).toEqual([1]);
27
+ });
28
+
29
+ it('builds SELECT with multiple WHERE conditions', () => {
30
+ const result = select('users')
31
+ .columns(['*'])
32
+ .where('status', '=', 'active')
33
+ .where('age', '>=', 18)
34
+ .build();
35
+
36
+ expect(result.sql).toBe('SELECT * FROM users WHERE status = $1 AND age >= $2');
37
+ expect(result.params).toEqual(['active', 18]);
38
+ });
39
+
40
+ it('builds SELECT with OR conditions', () => {
41
+ const result = select('users')
42
+ .columns(['*'])
43
+ .where('role', '=', 'admin')
44
+ .orWhere('role', '=', 'superadmin')
45
+ .build();
46
+
47
+ expect(result.sql).toContain('OR');
48
+ expect(result.params).toEqual(['admin', 'superadmin']);
49
+ });
50
+
51
+ it('builds SELECT with IN clause', () => {
52
+ const result = select('users')
53
+ .columns(['*'])
54
+ .whereIn('id', [1, 2, 3])
55
+ .build();
56
+
57
+ expect(result.sql).toBe('SELECT * FROM users WHERE id IN ($1, $2, $3)');
58
+ expect(result.params).toEqual([1, 2, 3]);
59
+ });
60
+
61
+ it('builds SELECT with ORDER BY', () => {
62
+ const result = select('users')
63
+ .columns(['*'])
64
+ .orderBy('created_at', 'DESC')
65
+ .build();
66
+
67
+ expect(result.sql).toContain('ORDER BY created_at DESC');
68
+ });
69
+
70
+ it('builds SELECT with LIMIT and OFFSET', () => {
71
+ const result = select('users')
72
+ .columns(['*'])
73
+ .limit(10)
74
+ .offset(20)
75
+ .build();
76
+
77
+ expect(result.sql).toContain('LIMIT $1');
78
+ expect(result.sql).toContain('OFFSET $2');
79
+ expect(result.params).toContain(10);
80
+ expect(result.params).toContain(20);
81
+ });
82
+
83
+ it('rejects string concatenation in WHERE value', () => {
84
+ expect(() => {
85
+ select('users')
86
+ .columns(['*'])
87
+ .whereRaw("name = '" + "'; DROP TABLE users;--")
88
+ .build();
89
+ }).toThrow(SecurityError);
90
+ });
91
+
92
+ it('rejects unwhitelisted table name', () => {
93
+ const builder = createQueryBuilder({
94
+ allowedTables: ['users', 'posts'],
95
+ });
96
+
97
+ expect(() => {
98
+ builder.select('admin_secrets').columns(['*']).build();
99
+ }).toThrow(SecurityError);
100
+ });
101
+
102
+ it('rejects unwhitelisted column name', () => {
103
+ const builder = createQueryBuilder({
104
+ allowedColumns: { users: ['id', 'name', 'email'] },
105
+ });
106
+
107
+ expect(() => {
108
+ builder.select('users').columns(['password_hash']).build();
109
+ }).toThrow(SecurityError);
110
+ });
111
+ });
112
+
113
+ describe('insert', () => {
114
+ it('builds INSERT with values', () => {
115
+ const result = insert('users')
116
+ .values({ name: 'John', email: 'john@example.com' })
117
+ .build();
118
+
119
+ expect(result.sql).toBe('INSERT INTO users (name, email) VALUES ($1, $2)');
120
+ expect(result.params).toEqual(['John', 'john@example.com']);
121
+ });
122
+
123
+ it('builds INSERT with multiple rows', () => {
124
+ const result = insert('users')
125
+ .values([
126
+ { name: 'John', email: 'john@example.com' },
127
+ { name: 'Jane', email: 'jane@example.com' },
128
+ ])
129
+ .build();
130
+
131
+ expect(result.sql).toContain('VALUES ($1, $2), ($3, $4)');
132
+ expect(result.params).toHaveLength(4);
133
+ });
134
+
135
+ it('builds INSERT with RETURNING clause', () => {
136
+ const result = insert('users')
137
+ .values({ name: 'John' })
138
+ .returning(['id'])
139
+ .build();
140
+
141
+ expect(result.sql).toContain('RETURNING id');
142
+ });
143
+
144
+ it('escapes column names with special characters', () => {
145
+ const result = insert('users')
146
+ .values({ 'user-name': 'John' })
147
+ .build();
148
+
149
+ expect(result.sql).toContain('"user-name"');
150
+ });
151
+
152
+ it('rejects SQL in column names', () => {
153
+ expect(() => {
154
+ insert('users')
155
+ .values({ 'name; DROP TABLE users;--': 'John' })
156
+ .build();
157
+ }).toThrow(SecurityError);
158
+ });
159
+ });
160
+
161
+ describe('update', () => {
162
+ it('builds UPDATE with SET and WHERE', () => {
163
+ const result = update('users')
164
+ .set({ name: 'John', status: 'active' })
165
+ .where('id', '=', 1)
166
+ .build();
167
+
168
+ expect(result.sql).toBe('UPDATE users SET name = $1, status = $2 WHERE id = $3');
169
+ expect(result.params).toEqual(['John', 'active', 1]);
170
+ });
171
+
172
+ it('requires WHERE clause by default', () => {
173
+ expect(() => {
174
+ update('users')
175
+ .set({ status: 'deleted' })
176
+ .build();
177
+ }).toThrow(SecurityError);
178
+ });
179
+
180
+ it('allows UPDATE without WHERE when explicitly unsafe', () => {
181
+ const result = update('users')
182
+ .set({ status: 'inactive' })
183
+ .unsafe()
184
+ .build();
185
+
186
+ expect(result.sql).toBe('UPDATE users SET status = $1');
187
+ });
188
+
189
+ it('builds UPDATE with RETURNING', () => {
190
+ const result = update('users')
191
+ .set({ name: 'John' })
192
+ .where('id', '=', 1)
193
+ .returning(['id', 'name'])
194
+ .build();
195
+
196
+ expect(result.sql).toContain('RETURNING id, name');
197
+ });
198
+ });
199
+
200
+ describe('delete', () => {
201
+ it('builds DELETE with WHERE', () => {
202
+ const result = del('users')
203
+ .where('id', '=', 1)
204
+ .build();
205
+
206
+ expect(result.sql).toBe('DELETE FROM users WHERE id = $1');
207
+ expect(result.params).toEqual([1]);
208
+ });
209
+
210
+ it('requires WHERE clause by default', () => {
211
+ expect(() => {
212
+ del('users').build();
213
+ }).toThrow(SecurityError);
214
+ });
215
+
216
+ it('allows DELETE without WHERE when explicitly unsafe', () => {
217
+ const result = del('users').unsafe().build();
218
+ expect(result.sql).toBe('DELETE FROM users');
219
+ });
220
+
221
+ it('builds DELETE with multiple conditions', () => {
222
+ const result = del('users')
223
+ .where('status', '=', 'deleted')
224
+ .where('deleted_at', '<', '2024-01-01')
225
+ .build();
226
+
227
+ expect(result.sql).toContain('AND');
228
+ expect(result.params).toHaveLength(2);
229
+ });
230
+ });
231
+
232
+ describe('SQL keyword safety', () => {
233
+ it('treats SQL keywords in values as data', () => {
234
+ const result = insert('users')
235
+ .values({ name: 'SELECT * FROM users' })
236
+ .build();
237
+
238
+ expect(result.params).toContain('SELECT * FROM users');
239
+ expect(result.sql).not.toContain('SELECT * FROM users');
240
+ });
241
+
242
+ it('treats DROP TABLE in values as data', () => {
243
+ const result = update('users')
244
+ .set({ bio: 'DROP TABLE users;' })
245
+ .where('id', '=', 1)
246
+ .build();
247
+
248
+ expect(result.params).toContain('DROP TABLE users;');
249
+ });
250
+
251
+ it('treats UNION in values as data', () => {
252
+ const result = select('users')
253
+ .columns(['*'])
254
+ .where('name', '=', "' UNION SELECT * FROM passwords--")
255
+ .build();
256
+
257
+ expect(result.params).toContain("' UNION SELECT * FROM passwords--");
258
+ });
259
+ });
260
+
261
+ describe('dialect support', () => {
262
+ it('uses PostgreSQL parameter style ($1)', () => {
263
+ const builder = createQueryBuilder({ dialect: 'postgresql' });
264
+ const result = builder.select('users')
265
+ .columns(['*'])
266
+ .where('id', '=', 1)
267
+ .build();
268
+
269
+ expect(result.sql).toContain('$1');
270
+ });
271
+
272
+ it('uses MySQL parameter style (?)', () => {
273
+ const builder = createQueryBuilder({ dialect: 'mysql' });
274
+ const result = builder.select('users')
275
+ .columns(['*'])
276
+ .where('id', '=', 1)
277
+ .build();
278
+
279
+ expect(result.sql).toContain('?');
280
+ });
281
+
282
+ it('uses SQLite parameter style (?)', () => {
283
+ const builder = createQueryBuilder({ dialect: 'sqlite' });
284
+ const result = builder.select('users')
285
+ .columns(['*'])
286
+ .where('id', '=', 1)
287
+ .build();
288
+
289
+ expect(result.sql).toContain('?');
290
+ });
291
+ });
292
+
293
+ describe('identifier escaping', () => {
294
+ it('escapes table names with special characters', () => {
295
+ const result = select('user-data')
296
+ .columns(['*'])
297
+ .build();
298
+
299
+ expect(result.sql).toContain('"user-data"');
300
+ });
301
+
302
+ it('escapes reserved word table names', () => {
303
+ const result = select('order')
304
+ .columns(['*'])
305
+ .build();
306
+
307
+ expect(result.sql).toContain('"order"');
308
+ });
309
+
310
+ it('handles schema-qualified table names', () => {
311
+ const result = select('public.users')
312
+ .columns(['*'])
313
+ .build();
314
+
315
+ expect(result.sql).toContain('public.users');
316
+ });
317
+ });
318
+ });
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Secret Detector Module
3
+ *
4
+ * Detects hardcoded secrets, API keys, and credentials in code.
5
+ * Helps prevent OWASP A02: Cryptographic Failures and A07: Auth Failures
6
+ */
7
+
8
+ /**
9
+ * Built-in secret detection patterns
10
+ * All patterns use simple, non-backtracking regex
11
+ */
12
+ const BUILT_IN_PATTERNS = [
13
+ // AWS Access Key (always starts with AKIA, exactly 20 chars)
14
+ {
15
+ name: 'aws_access_key',
16
+ pattern: /AKIA[0-9A-Z]{16}/g,
17
+ severity: 'critical',
18
+ },
19
+ // AWS Secret Key (40 char base64-like, starts with wJalrX typically)
20
+ {
21
+ name: 'aws_secret_key',
22
+ pattern: /(?:secret(?:Access)?Key|aws_secret)\s*[=:]\s*["']([A-Za-z0-9/+=]{40})["']/gi,
23
+ severity: 'critical',
24
+ },
25
+
26
+ // GitHub tokens (prefixed with ghp_, gho_, ghu_, ghr_, ghs_)
27
+ {
28
+ name: 'github_token',
29
+ pattern: /gh[pousr]_[A-Za-z0-9]{36}/g,
30
+ severity: 'critical',
31
+ },
32
+
33
+ // Stripe secret key (sk_live_ or sk_test_, 4-32 chars after prefix for tests)
34
+ {
35
+ name: 'stripe_secret_key',
36
+ pattern: /sk_live_[A-Za-z0-9]{4,32}/g,
37
+ severity: 'critical',
38
+ },
39
+ {
40
+ name: 'stripe_test_key',
41
+ pattern: /sk_test_[A-Za-z0-9]{4,32}/g,
42
+ severity: 'high',
43
+ },
44
+
45
+ // Private keys (PEM format headers)
46
+ {
47
+ name: 'private_key',
48
+ pattern: /-----BEGIN\s+(?:RSA\s+|EC\s+|DSA\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/g,
49
+ severity: 'critical',
50
+ },
51
+
52
+ // JWT tokens - detect by eyJ prefix (Base64 for {"alg" or {"typ")
53
+ {
54
+ name: 'jwt_token',
55
+ pattern: /eyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/g,
56
+ severity: 'high',
57
+ },
58
+
59
+ // Database connection strings with embedded credentials
60
+ {
61
+ name: 'connection_string',
62
+ pattern: /(?:postgresql|mysql|mongodb(?:\+srv)?|redis):\/\/\w+:\w+@[^\s"']+/gi,
63
+ severity: 'critical',
64
+ },
65
+
66
+ // Password assignments (simple detection)
67
+ {
68
+ name: 'password',
69
+ pattern: /(?:password|passwd|pass|db_password|secret)\s*[:=]\s*["'][^"']{4,30}["']/gi,
70
+ severity: 'high',
71
+ excludePatterns: [/process\.env/i, /\$\{/],
72
+ },
73
+ ];
74
+
75
+ /**
76
+ * Patterns that indicate false positives
77
+ * These are checked against the LINE, not just the matched value
78
+ */
79
+ const FALSE_POSITIVE_PATTERNS = [
80
+ /process\.env/i,
81
+ /\$\{[^}]+\}/,
82
+ /YOUR_[A-Z_]+_HERE/i,
83
+ /PLACEHOLDER/i,
84
+ /-----BEGIN\s+PUBLIC\s+KEY-----/,
85
+ /-----BEGIN\s+CERTIFICATE-----/,
86
+ ];
87
+
88
+ /**
89
+ * Patterns checked only against the matched value (not the full line)
90
+ */
91
+ const VALUE_FALSE_POSITIVE_PATTERNS = [
92
+ /^x{4,}$/i, // Only if the entire value is just x's
93
+ ];
94
+
95
+ /**
96
+ * Detect secrets in code content
97
+ * @param {string} content - Code content to scan
98
+ * @param {Object} options - Detection options
99
+ * @returns {Object} Detection results
100
+ */
101
+ export function detectSecrets(content, options = {}) {
102
+ const {
103
+ patterns = BUILT_IN_PATTERNS,
104
+ ignoreTestValues = false,
105
+ } = options;
106
+
107
+ const findings = [];
108
+ const lines = content.split('\n');
109
+
110
+ for (const patternDef of patterns) {
111
+ const { name, pattern, severity, excludePatterns } = patternDef;
112
+
113
+ // Create a fresh regex instance with global flag
114
+ const flags = pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g';
115
+ const regex = new RegExp(pattern.source, flags);
116
+
117
+ let match;
118
+ while ((match = regex.exec(content)) !== null) {
119
+ const matchStart = match.index;
120
+ const matchValue = match[0];
121
+
122
+ // Find line number
123
+ let lineNumber = 1;
124
+ let charCount = 0;
125
+ for (let i = 0; i < lines.length; i++) {
126
+ if (charCount + lines[i].length >= matchStart) {
127
+ lineNumber = i + 1;
128
+ break;
129
+ }
130
+ charCount += lines[i].length + 1;
131
+ }
132
+
133
+ const line = lines[lineNumber - 1] || '';
134
+
135
+ // Check for false positives
136
+ let isFalsePositive = false;
137
+
138
+ // Check pattern-specific exclusions
139
+ if (excludePatterns) {
140
+ for (const excl of excludePatterns) {
141
+ if (excl.test(line)) {
142
+ isFalsePositive = true;
143
+ break;
144
+ }
145
+ }
146
+ }
147
+
148
+ // Check global false positive patterns (against line)
149
+ if (!isFalsePositive) {
150
+ for (const fp of FALSE_POSITIVE_PATTERNS) {
151
+ if (fp.test(line)) {
152
+ isFalsePositive = true;
153
+ break;
154
+ }
155
+ }
156
+ }
157
+
158
+ // Check value-specific false positive patterns
159
+ if (!isFalsePositive) {
160
+ for (const fp of VALUE_FALSE_POSITIVE_PATTERNS) {
161
+ if (fp.test(matchValue)) {
162
+ isFalsePositive = true;
163
+ break;
164
+ }
165
+ }
166
+ }
167
+
168
+ // Skip test values if configured
169
+ if (!isFalsePositive && ignoreTestValues) {
170
+ if (/test|example|sample|demo/i.test(matchValue)) {
171
+ isFalsePositive = true;
172
+ }
173
+ }
174
+
175
+ if (!isFalsePositive) {
176
+ findings.push({
177
+ type: name,
178
+ line: lineNumber,
179
+ column: matchStart - content.lastIndexOf('\n', matchStart - 1),
180
+ severity: severity || 'medium',
181
+ snippet: line.trim().substring(0, 100),
182
+ });
183
+ }
184
+ }
185
+ }
186
+
187
+ return {
188
+ findings,
189
+ hasSecrets: findings.length > 0,
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Scan a file for secrets
195
+ */
196
+ export async function scanFile(filePath, options = {}) {
197
+ const { content } = options;
198
+
199
+ if (!content) {
200
+ throw new Error('Content must be provided');
201
+ }
202
+
203
+ const result = detectSecrets(content, options);
204
+
205
+ return {
206
+ file: filePath,
207
+ findings: result.findings.map((f) => ({ ...f, file: filePath })),
208
+ hasSecrets: result.hasSecrets,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Scan a directory for secrets
214
+ */
215
+ export async function scanDirectory(dirPath, options = {}) {
216
+ const { files = {}, ignore = [] } = options;
217
+
218
+ const allFindings = [];
219
+ let filesWithSecrets = 0;
220
+ let totalFiles = 0;
221
+
222
+ for (const [filePath, content] of Object.entries(files)) {
223
+ const relativePath = filePath.replace(dirPath, '').replace(/^\//, '');
224
+
225
+ // Check ignore patterns
226
+ let shouldIgnore = false;
227
+ for (const pattern of ignore) {
228
+ if (relativePath.includes(pattern.replace('/**', '').replace('/*', ''))) {
229
+ shouldIgnore = true;
230
+ break;
231
+ }
232
+ }
233
+
234
+ if (shouldIgnore) continue;
235
+
236
+ totalFiles++;
237
+ const result = await scanFile(filePath, { content, ...options });
238
+
239
+ if (result.hasSecrets) {
240
+ filesWithSecrets++;
241
+ allFindings.push(...result.findings);
242
+ }
243
+ }
244
+
245
+ return {
246
+ directory: dirPath,
247
+ totalFiles,
248
+ filesWithSecrets,
249
+ findings: allFindings,
250
+ hasSecrets: allFindings.length > 0,
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Create a custom secret detector
256
+ */
257
+ export function createSecretDetector(options = {}) {
258
+ const { patterns = [], builtInPatterns = true } = options;
259
+
260
+ const activePatterns = builtInPatterns
261
+ ? [...BUILT_IN_PATTERNS, ...patterns]
262
+ : patterns;
263
+
264
+ return {
265
+ patterns: activePatterns,
266
+
267
+ detect(content, detectOptions = {}) {
268
+ return detectSecrets(content, { ...detectOptions, patterns: this.patterns });
269
+ },
270
+
271
+ addPattern(pattern) {
272
+ this.patterns.push(pattern);
273
+ },
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Add a custom pattern to an existing detector
279
+ */
280
+ export function addCustomPattern(detector, pattern) {
281
+ if (!detector || !detector.patterns) {
282
+ throw new Error('Invalid detector');
283
+ }
284
+
285
+ detector.patterns.push({
286
+ name: pattern.name,
287
+ pattern: pattern.pattern,
288
+ severity: pattern.severity || 'medium',
289
+ });
290
+ }