codehere 0.8.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +37 -184
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +329 -0
  5. package/dist/index.js +74 -1004
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
  7. package/dist/presentation/cli/commands/check-command.js +251 -0
  8. package/dist/presentation/cli/commands/help-command.js +22 -286
  9. package/dist/ui.js +19 -438
  10. package/dist/utils/version.js +0 -12
  11. package/package.json +18 -27
  12. package/dist/application/use-cases/domain-trust.js +0 -110
  13. package/dist/application/use-cases/generate-final-report.js +0 -134
  14. package/dist/application/use-cases/generate-narration.js +0 -128
  15. package/dist/application/use-cases/generate-thread-title.js +0 -178
  16. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  17. package/dist/application/use-cases/thread-export.js +0 -155
  18. package/dist/application/use-cases/verify-claim.js +0 -274
  19. package/dist/application/use-cases/write-pass.js +0 -277
  20. package/dist/domain/entities/task.js +0 -61
  21. package/dist/domain/services/codebase-detector.js +0 -91
  22. package/dist/error-handler.js +0 -251
  23. package/dist/infrastructure/completion/bash-completion.js +0 -25
  24. package/dist/infrastructure/completion/fish-completion.js +0 -22
  25. package/dist/infrastructure/completion/zsh-completion.js +0 -30
  26. package/dist/infrastructure/config/config-manager.js +0 -173
  27. package/dist/infrastructure/config/product-context.js +0 -67
  28. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  29. package/dist/infrastructure/context/adapters/figma-source.js +0 -163
  30. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
  31. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  32. package/dist/infrastructure/context/context-resolver.js +0 -144
  33. package/dist/infrastructure/context/context-source.js +0 -114
  34. package/dist/infrastructure/context/oauth-flow.js +0 -207
  35. package/dist/infrastructure/context/oauth-store.js +0 -119
  36. package/dist/infrastructure/context/registry-bootstrap.js +0 -99
  37. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  38. package/dist/infrastructure/cost/budget-config.js +0 -113
  39. package/dist/infrastructure/cost/cost-router.js +0 -104
  40. package/dist/infrastructure/cost/spend-aggregator.js +0 -147
  41. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
  42. package/dist/infrastructure/hooks/hook-mode.js +0 -71
  43. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
  44. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  45. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  46. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  47. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  48. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  49. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
  50. package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
  51. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  52. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
  53. package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
  54. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
  55. package/dist/infrastructure/orchestration/context-handoff.js +0 -144
  56. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  57. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  58. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  59. package/dist/infrastructure/orchestration/failover-config.js +0 -77
  60. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  61. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
  62. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
  63. package/dist/infrastructure/orchestration/graphify-context.js +0 -254
  64. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  65. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
  66. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  67. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  68. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  69. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
  70. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
  71. package/dist/infrastructure/orchestration/readiness.js +0 -103
  72. package/dist/infrastructure/orchestration/session-manager.js +0 -1091
  73. package/dist/infrastructure/orchestration/session-persistence.js +0 -258
  74. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  75. package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
  76. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  77. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  78. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
  79. package/dist/infrastructure/protocols/probe-port.js +0 -36
  80. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  81. package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
  82. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  83. package/dist/infrastructure/protocols/verify-page.js +0 -983
  84. package/dist/infrastructure/security/file-collection.js +0 -118
  85. package/dist/infrastructure/security/input-validator.js +0 -152
  86. package/dist/infrastructure/security/permissions-config.js +0 -234
  87. package/dist/infrastructure/security/trust-scorer.js +0 -657
  88. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  89. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  90. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  91. package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
  92. package/dist/infrastructure/storage/project-thread-reader.js +0 -172
  93. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  94. package/dist/infrastructure/storage/task-repository.js +0 -206
  95. package/dist/monitoring.js +0 -212
  96. package/dist/presentation/cli/commands/audit-command.js +0 -126
  97. package/dist/presentation/cli/commands/budget-command.js +0 -79
  98. package/dist/presentation/cli/commands/config-command.js +0 -125
  99. package/dist/presentation/cli/commands/context-source-command.js +0 -198
  100. package/dist/presentation/cli/commands/hook-command.js +0 -265
  101. package/dist/presentation/cli/commands/init-command.js +0 -578
  102. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  103. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  104. package/dist/presentation/cli/commands/permission-command.js +0 -116
  105. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  106. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  107. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  108. package/dist/presentation/cli/commands/report-command.js +0 -387
  109. package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
  110. package/dist/presentation/cli/commands/setup-command.js +0 -307
  111. package/dist/presentation/cli/commands/status-command.js +0 -661
  112. package/dist/presentation/cli/commands/thread-command.js +0 -288
  113. package/dist/presentation/cli/commands/trust-command.js +0 -306
  114. package/dist/presentation/cli/commands/verify-command.js +0 -233
  115. package/dist/presentation/cli/error-display.js +0 -336
  116. package/dist/presentation/cli/repl.js +0 -797
  117. package/dist/presentation/cli/task-deliverable.js +0 -127
  118. package/dist/testing/cassette.js +0 -125
  119. package/dist/testing/replay-adapter.js +0 -166
@@ -1,657 +0,0 @@
1
- /**
2
- * Shared trust scoring engine — used by CLI, MCP server, and hook handler.
3
- *
4
- * Three scanning passes:
5
- * Pass 1 (comments+regex stripped): Secrets & API keys — string literals visible, comments/regex stripped
6
- * Pass 2 (fully stripped): Structural patterns — eval, execSync, innerHTML, etc.
7
- * Stripped to avoid false positives from pattern definitions
8
- * Pass 3 (instruction scan): Hook-layer patterns on fully stripped code
9
- *
10
- * All passes run locally, zero API calls.
11
- */
12
- /**
13
- * Extract the child_process bindings actually imported in a file, so the
14
- * exec() command-injection pattern can tell a real `cp.exec(cmd)` from a
15
- * `regex.exec(str)` that happens to live in a file which also uses
16
- * child_process (e.g. our own artifacts-extractor: spawns git AND runs a
17
- * regex loop). namespaceAliases = the `cp` in `import * as cp` / `const cp =
18
- * require(...)`; namedExec = whether `exec`/`execSync` was imported by name so
19
- * a bare `exec(cmd)` is callable.
20
- */
21
- export function childProcessBindings(src) {
22
- const namespaceAliases = new Set();
23
- let namedExec = false;
24
- for (const m of src.matchAll(/import\s+(?:\*\s+as\s+)?(\w+)\s+from\s+['"](?:node:)?child_process['"]/g)) {
25
- namespaceAliases.add(m[1]);
26
- }
27
- for (const m of src.matchAll(/(?:const|let|var|import)\s+(\w+)\s*=\s*require\(\s*['"](?:node:)?child_process['"]/g)) {
28
- namespaceAliases.add(m[1]);
29
- }
30
- for (const m of src.matchAll(/(?:import|const|let|var)\s*\{([^}]*)\}\s*(?:from\s*|=\s*require\(\s*)['"](?:node:)?child_process['"]/g)) {
31
- if (/\b(?:exec|execSync)\b/.test(m[1]))
32
- namedExec = true;
33
- }
34
- return { namespaceAliases, namedExec };
35
- }
36
- export const CODE_PATTERNS = [
37
- // Secrets (scan with strings visible — must see inside string literals)
38
- {
39
- regex: /(?:password|passwd|pass|pwd|secret|api_?key|auth_?token)\s*[:=]\s*['"`][^'"`\s]{4,}['"`]/gi,
40
- severity: 'high',
41
- category: 'Secrets',
42
- description: 'Hardcoded credential',
43
- cwe: 'CWE-798',
44
- scanRaw: true,
45
- },
46
- {
47
- regex: /(?:password|passwd|secret|api.?key|auth.?token)[^\n]*\|\|\s*['"`][^'"`\s]{3,}['"`]/gi,
48
- severity: 'high',
49
- category: 'Secrets',
50
- description: 'Hardcoded fallback credential (env var || literal)',
51
- cwe: 'CWE-798',
52
- scanRaw: true,
53
- },
54
- {
55
- regex: /sk-[a-zA-Z0-9]{32,}|xoxb-[a-zA-Z0-9-]+|ghp_[a-zA-Z0-9]{36,}/i,
56
- severity: 'critical',
57
- category: 'Secrets',
58
- description: 'API key pattern (OpenAI/Slack/GitHub)',
59
- cwe: 'CWE-798',
60
- scanRaw: true,
61
- },
62
- // SQL Injection (scan with strings visible — SQL lives inside string literals)
63
- // Per-quote-type to allow inner quotes: "SELECT...WHERE name='" + user
64
- {
65
- regex: /"[^"]*SELECT[^"]*FROM[^"]*WHERE[^"]*"\s*\+/gi,
66
- severity: 'critical',
67
- category: 'SQL Injection',
68
- description: 'SQL query built with string concatenation (double-quoted)',
69
- cwe: 'CWE-89',
70
- scanRaw: true,
71
- },
72
- {
73
- regex: /'[^']*SELECT[^']*FROM[^']*WHERE[^']*'\s*\+/gi,
74
- severity: 'critical',
75
- category: 'SQL Injection',
76
- description: 'SQL query built with string concatenation (single-quoted)',
77
- cwe: 'CWE-89',
78
- scanRaw: true,
79
- },
80
- // Require a SQL keyword inside the interpolated string — otherwise this
81
- // matches any `query("prompt with ${x}")` (handleQuery, graphql.query, etc.)
82
- {
83
- regex: /query\s*\(\s*['"`][^'"`]*\b(?:SELECT|INSERT|UPDATE|DELETE|WHERE|FROM)\b[^'"`]*\$\{|execute\s*\([`'"]\s*SELECT.*\$\{/gi,
84
- severity: 'critical',
85
- category: 'SQL Injection',
86
- description: 'String interpolation in SQL query',
87
- cwe: 'CWE-89',
88
- scanRaw: true,
89
- },
90
- {
91
- regex: /pool\.query\s*\([^,)]*\+[^,)]*\)/g,
92
- severity: 'high',
93
- category: 'SQL Injection',
94
- description: 'String concatenation in SQL query call',
95
- cwe: 'CWE-89',
96
- },
97
- // Command injection with user input (scan with strings visible)
98
- // Catches require('child_process').execSync(req.body.cmd) — child_process is in a string
99
- // but the method call and req. reference are in plain code
100
- {
101
- regex: /\.exec(?:Sync)?\s*\(\s*req\./gi,
102
- severity: 'critical',
103
- category: 'Command Injection',
104
- description: 'exec/execSync called directly with request input',
105
- cwe: 'CWE-78',
106
- scanRaw: true,
107
- },
108
- // Injection (scan fully stripped — structural, must not trigger on pattern defs or comments)
109
- {
110
- regex: /\beval\s*\(/g,
111
- severity: 'critical',
112
- category: 'Code Injection',
113
- description: 'eval() allows arbitrary code execution',
114
- cwe: 'CWE-95',
115
- },
116
- {
117
- regex: /new\s+Function\s*\(/g,
118
- severity: 'critical',
119
- category: 'Code Injection',
120
- description: 'Function() constructor execution',
121
- cwe: 'CWE-95',
122
- },
123
- {
124
- regex: /child_process\.exec(?:Sync)?\s*\(\s*[^,)'"]*(?:req\.|body\.|params\.|query\.)/g,
125
- severity: 'critical',
126
- category: 'Command Injection',
127
- description: 'execSync/exec with user-controlled input',
128
- cwe: 'CWE-78',
129
- },
130
- {
131
- regex: /child_process\.exec\s*\(\s*[^,)]*\+/g,
132
- severity: 'critical',
133
- category: 'Command Injection',
134
- description: 'Command injection via string concat',
135
- cwe: 'CWE-78',
136
- },
137
- // Destructured exec/execSync with string concat containing user input
138
- {
139
- regex: /\bexec(?:Sync)?\s*\([^)]*\+\s*req\./g,
140
- severity: 'critical',
141
- category: 'Command Injection',
142
- description: 'exec() with string concatenation of user input',
143
- cwe: 'CWE-78',
144
- scanRaw: true,
145
- },
146
- {
147
- regex: /\.innerHTML\s*=\s*[^=]/g,
148
- severity: 'high',
149
- category: 'XSS',
150
- description: 'innerHTML assignment (potential XSS)',
151
- cwe: 'CWE-79',
152
- },
153
- {
154
- regex: /document\.write\s*\(/g,
155
- severity: 'high',
156
- category: 'XSS',
157
- description: 'document.write can lead to XSS',
158
- cwe: 'CWE-79',
159
- },
160
- // Insecure deserialization
161
- {
162
- regex: /JSON\.parse\s*\([^)]*req\.|JSON\.parse\s*\([^)]*input/gi,
163
- severity: 'medium',
164
- category: 'Insecure Input',
165
- description: 'JSON.parse on unvalidated user input',
166
- cwe: 'CWE-502',
167
- },
168
- {
169
- regex: /yaml\.load\s*\([^)]*\)/g,
170
- severity: 'high',
171
- category: 'Insecure Deserialization',
172
- description: 'yaml.load without safeLoad',
173
- cwe: 'CWE-502',
174
- },
175
- {
176
- regex: /pickle\.loads\s*\(/g,
177
- severity: 'critical',
178
- category: 'Insecure Deserialization',
179
- description: 'pickle.loads on arbitrary data',
180
- cwe: 'CWE-502',
181
- },
182
- // Auth issues
183
- {
184
- regex: /jwt\.verify\s*\([^,)]+,\s*[^,)]+,\s*\{\s*algorithms\s*:\s*\[\s*['"`]none['"`]/i,
185
- severity: 'critical',
186
- category: 'Auth Bypass',
187
- description: 'JWT algorithm:none vulnerability',
188
- cwe: 'CWE-347',
189
- },
190
- {
191
- regex: /password\s*===?\s*['"`]|password\s*==\s*req\./gi,
192
- severity: 'high',
193
- category: 'Auth Bypass',
194
- description: 'Plaintext password comparison',
195
- cwe: 'CWE-256',
196
- },
197
- // Weak crypto
198
- {
199
- regex: /Math\.random\s*\(\s*\).*(?:token|key|secret|password|nonce|salt)/gi,
200
- severity: 'high',
201
- category: 'Weak Crypto',
202
- description: 'Math.random() for security-sensitive value',
203
- cwe: 'CWE-338',
204
- },
205
- {
206
- regex: /createCipheriv\s*\([^,]*['"`](?:des|rc4|md5)[^'"]*['"`]/gi,
207
- severity: 'high',
208
- category: 'Weak Crypto',
209
- description: 'Weak/deprecated cipher algorithm',
210
- cwe: 'CWE-327',
211
- },
212
- // Path traversal
213
- {
214
- regex: /readFile\s*\([^)]*req\.|readFileSync\s*\([^)]*user/gi,
215
- severity: 'high',
216
- category: 'Path Traversal',
217
- description: 'File read with user-controlled path',
218
- cwe: 'CWE-22',
219
- },
220
- {
221
- regex: /__dirname\s*\+\s*['"`/]*\s*\+?\s*req\.(params|query|body)/gi,
222
- severity: 'high',
223
- category: 'Path Traversal',
224
- description: 'Path built from user input without sanitization',
225
- cwe: 'CWE-22',
226
- },
227
- {
228
- regex: /sendFile\s*\([^)]*req\.(params|query|body)/gi,
229
- severity: 'high',
230
- category: 'Path Traversal',
231
- description: 'sendFile with user-controlled path',
232
- cwe: 'CWE-22',
233
- },
234
- // Open redirect
235
- {
236
- regex: /res\.redirect\s*\([^)]*req\.(query|body|params)/gi,
237
- severity: 'medium',
238
- category: 'Open Redirect',
239
- description: 'Redirect to user-controlled URL',
240
- cwe: 'CWE-601',
241
- },
242
- // Server-side XSS (template injection in response)
243
- {
244
- regex: /res\.send\s*\(\s*`[^`]*\$\{[^}]*req\.(query|body|params)/gi,
245
- severity: 'high',
246
- category: 'XSS',
247
- description: 'User input interpolated directly into response HTML',
248
- cwe: 'CWE-79',
249
- scanRaw: true,
250
- },
251
- {
252
- regex: /res\.send\s*\([^)]*\+\s*req\.(query|body|params)/gi,
253
- severity: 'high',
254
- category: 'XSS',
255
- description: 'User input concatenated into response',
256
- cwe: 'CWE-79',
257
- scanRaw: true,
258
- },
259
- // Command injection via variable indirection (exec with non-literal arg).
260
- // Gated: only fires if file references child_process — otherwise regex.exec(str)
261
- // lights up every bundled highlight.js / parser in the ecosystem.
262
- // requiresContext must match an actual import, not the literal string
263
- // child_process (which this scanner file itself contains as pattern data).
264
- {
265
- // Capture an optional receiver (group 1) so matchFilter can check it:
266
- // cp.exec(cmd) → receiver "cp" | exec(cmd) → no receiver | g.exec(s) → "g"
267
- regex: /(?:\b(\w+)\s*\.\s*)?\bexec(?:Sync)?\s*\(\s*(?!['"`])(\w+)\s*[,)]/g,
268
- severity: 'high',
269
- category: 'Command Injection',
270
- description: 'exec() called with variable argument — verify input is not user-controlled',
271
- cwe: 'CWE-78',
272
- requiresContext: /(?:from|require\()\s*['"](?:node:)?child_process['"]/,
273
- // Only a real child_process call counts: a method call on the imported
274
- // child_process binding, or a bare exec() when exec was named-imported.
275
- // A method call on anything else (a RegExp, a parser, …) is not.
276
- matchFilter: (m, src) => {
277
- const receiver = m[1];
278
- const b = childProcessBindings(src);
279
- return receiver === undefined ? b.namedExec : b.namespaceAliases.has(receiver);
280
- },
281
- },
282
- // SSRF
283
- {
284
- regex: /fetch\s*\(\s*req\.(body|query|params)/gi,
285
- severity: 'high',
286
- category: 'SSRF',
287
- description: 'Server-side request to user-controlled URL',
288
- cwe: 'CWE-918',
289
- },
290
- {
291
- regex: /axios\s*\.\s*(?:get|post|put|delete)\s*\(\s*req\.(body|query|params)/gi,
292
- severity: 'high',
293
- category: 'SSRF',
294
- description: 'HTTP request to user-controlled URL',
295
- cwe: 'CWE-918',
296
- },
297
- // Weak crypto (MD5, SHA1 for passwords)
298
- {
299
- regex: /createHash\s*\(\s*['"`]md5['"`]\s*\)/gi,
300
- severity: 'high',
301
- category: 'Weak Crypto',
302
- description: 'MD5 hash is cryptographically broken — use bcrypt/argon2 for passwords',
303
- cwe: 'CWE-328',
304
- scanRaw: true,
305
- },
306
- {
307
- regex: /createHash\s*\(\s*['"`]sha1['"`]\s*\)/gi,
308
- severity: 'medium',
309
- category: 'Weak Crypto',
310
- description: 'SHA1 is deprecated for security use — use SHA-256+ or bcrypt',
311
- cwe: 'CWE-328',
312
- scanRaw: true,
313
- },
314
- // JWT algorithm:none (both sign and verify — scan raw to catch string content)
315
- {
316
- regex: /jwt\.(?:sign|verify)\s*\([^)]*algorithm\s*:\s*['"`]none['"`]/gi,
317
- severity: 'critical',
318
- category: 'Auth Bypass',
319
- description: 'JWT with algorithm "none" disables signature verification',
320
- cwe: 'CWE-347',
321
- scanRaw: true,
322
- },
323
- // Secret-file read / exfiltration. Code that touches a private key, cloud
324
- // credential, or system password file is almost always exfil or theft.
325
- // dogfood 2026-05-27 Phase F: reading ~/.ssh/id_rsa was a false-negative on a
326
- // product that claims a trust score on every file. scanRaw — the path lives
327
- // inside a string literal. Tight path set keeps false positives near zero.
328
- {
329
- regex: /(?:\.ssh\/id_(?:rsa|dsa|ecdsa|ed25519)|(?:^|[^\w])id_(?:rsa|ed25519)\b|\.aws\/credentials|\.config\/gcloud\/|\.kube\/config|\.docker\/config\.json|\/etc\/shadow|\.npmrc\b|\.pem['"`])/gi,
330
- severity: 'critical',
331
- category: 'Secret Exfiltration',
332
- description: 'Reads a private key or credential file — likely secret exfiltration',
333
- cwe: 'CWE-522',
334
- scanRaw: true,
335
- },
336
- // Destructive shell command. `rm -rf` in agent-written code is worth flagging
337
- // even when it looks scoped — a stray variable or trailing slash turns it into
338
- // a filesystem wipe. dogfood 2026-05-27 Phase F named this a false-negative.
339
- // The runtime PreToolUse hook also blocks the worst forms at execution time;
340
- // this is the static signal on the file itself. scanRaw — the command is a
341
- // string literal.
342
- {
343
- regex: /rm\s+-[a-z]*r[a-z]*f|rm\s+-[a-z]*f[a-z]*r/gi,
344
- severity: 'high',
345
- category: 'Destructive Operation',
346
- description: 'Recursive force-delete (rm -rf) — verify the target can never be empty or a parent path',
347
- cwe: 'CWE-78',
348
- scanRaw: true,
349
- },
350
- {
351
- // rm -rf targeting root, a home glob, or a variable/concatenation → wipe risk.
352
- regex: /rm\s+-[a-z]*[rf][a-z]*\s+(?:\/(?:\s|$|["'`])|~|\$\{?\w|["'`][^"'`]*["'`]\s*\+|\.\.)/gi,
353
- severity: 'critical',
354
- category: 'Destructive Operation',
355
- description: 'rm -rf targeting root, home, or a dynamic path — can wipe unintended files',
356
- cwe: 'CWE-78',
357
- scanRaw: true,
358
- },
359
- ];
360
- export function measureComplexity(code) {
361
- const lines = code.split('\n').length;
362
- const functionMatches = code.match(/\b(function\s+\w+|=>\s*\{|\basync\s+function|\bclass\s+\w+)/g);
363
- const functions = functionMatches?.length ?? 0;
364
- const branchKeywords = code.match(/\b(if|else\s+if|while|for|switch|case|catch|\?\s*:)/g);
365
- const cyclomaticEstimate = (branchKeywords?.length ?? 0) + 1;
366
- let depth = 0, maxDepth = 0;
367
- for (const char of code) {
368
- if (char === '{') {
369
- depth++;
370
- maxDepth = Math.max(maxDepth, depth);
371
- }
372
- if (char === '}')
373
- depth = Math.max(0, depth - 1);
374
- }
375
- const todos = (code.match(/\/\/\s*(TODO|FIXME|HACK|XXX)/gi) ?? []).length;
376
- return { lines, functions, nestingDepth: maxDepth, cyclomaticEstimate, todos };
377
- }
378
- // ── Scoring ───────────────────────────────────────────────────────────────────
379
- const SEVERITY_PENALTY = {
380
- critical: 25,
381
- high: 12,
382
- medium: 5,
383
- low: 2,
384
- };
385
- /**
386
- * Returns true if the character at position `pos` follows an expression-start
387
- * context (i.e., `/` here opens a regex literal, not a division operator).
388
- */
389
- function isRegexStart(code, pos) {
390
- let j = pos - 1;
391
- while (j >= 0 && (code[j] === ' ' || code[j] === '\t'))
392
- j--;
393
- if (j < 0)
394
- return true;
395
- const prev = code[j];
396
- return ('=([{,;!&|?:~^%+->'.includes(prev) ||
397
- /\b(return|typeof|instanceof|in|new|delete|void|throw|case)\s*$/.test(code.slice(0, pos).trimEnd()));
398
- }
399
- /**
400
- * Strip ONLY comments and regex literals — string literals remain visible.
401
- * Used for pass 1 (raw pattern scanning): we need to see inside string literals to find
402
- * hardcoded secrets and SQL injection, but comments/regex literals that contain
403
- * example patterns would otherwise cause false positives.
404
- */
405
- function stripCommentsAndRegex(code) {
406
- const out = [];
407
- let i = 0;
408
- const len = code.length;
409
- while (i < len) {
410
- const ch = code[i];
411
- const next = code[i + 1];
412
- // Single-line comment: // ...
413
- if (ch === '/' && next === '/') {
414
- const start = i;
415
- while (i < len && code[i] !== '\n')
416
- i++;
417
- out.push(' '.repeat(i - start));
418
- continue;
419
- }
420
- // Block comment: /* ... */
421
- if (ch === '/' && next === '*') {
422
- const start = i;
423
- i += 2;
424
- while (i < len && !(code[i - 1] === '*' && code[i] === '/'))
425
- i++;
426
- i++;
427
- out.push(' '.repeat(i - start));
428
- continue;
429
- }
430
- // Regex literal: /.../ — strip to avoid pattern-def false positives
431
- if (ch === '/' && next !== '/' && next !== '*' && isRegexStart(code, i)) {
432
- const start = i;
433
- i++;
434
- while (i < len && code[i] !== '/' && code[i] !== '\n') {
435
- if (code[i] === '\\')
436
- i++;
437
- if (code[i] === '[') {
438
- i++;
439
- while (i < len && code[i] !== ']') {
440
- if (code[i] === '\\')
441
- i++;
442
- i++;
443
- }
444
- }
445
- i++;
446
- }
447
- i++;
448
- while (i < len && 'gimsuy'.includes(code[i]))
449
- i++;
450
- out.push(' '.repeat(i - start));
451
- continue;
452
- }
453
- // String literals — pass through (needed to detect secrets/SQL inside them)
454
- out.push(ch);
455
- i++;
456
- }
457
- return out.join('');
458
- }
459
- /**
460
- * Fully strip all string literals, regex literals, and comments.
461
- * Used for pass 2 (structural pattern scanning) to avoid false positives
462
- * from pattern definitions, example code in strings, etc.
463
- */
464
- function stripLiterals(code) {
465
- const out = [];
466
- let i = 0;
467
- const len = code.length;
468
- while (i < len) {
469
- const ch = code[i];
470
- const next = code[i + 1];
471
- if (ch === '/' && next === '/') {
472
- const start = i;
473
- while (i < len && code[i] !== '\n')
474
- i++;
475
- out.push(' '.repeat(i - start));
476
- continue;
477
- }
478
- if (ch === '/' && next === '*') {
479
- const start = i;
480
- i += 2;
481
- while (i < len && !(code[i - 1] === '*' && code[i] === '/'))
482
- i++;
483
- i++;
484
- out.push(' '.repeat(i - start));
485
- continue;
486
- }
487
- if (ch === '/' && next !== '/' && next !== '*' && isRegexStart(code, i)) {
488
- const start = i;
489
- i++;
490
- while (i < len && code[i] !== '/' && code[i] !== '\n') {
491
- if (code[i] === '\\')
492
- i++;
493
- if (code[i] === '[') {
494
- i++;
495
- while (i < len && code[i] !== ']') {
496
- if (code[i] === '\\')
497
- i++;
498
- i++;
499
- }
500
- }
501
- i++;
502
- }
503
- i++;
504
- while (i < len && 'gimsuy'.includes(code[i]))
505
- i++;
506
- out.push(' '.repeat(i - start));
507
- continue;
508
- }
509
- if (ch === '`') {
510
- const start = i;
511
- i++;
512
- while (i < len && code[i] !== '`') {
513
- if (code[i] === '\\')
514
- i++;
515
- i++;
516
- }
517
- i++;
518
- out.push(' '.repeat(i - start));
519
- continue;
520
- }
521
- if (ch === '"') {
522
- const start = i;
523
- i++;
524
- while (i < len && code[i] !== '"' && code[i] !== '\n') {
525
- if (code[i] === '\\')
526
- i++;
527
- i++;
528
- }
529
- i++;
530
- out.push(' '.repeat(i - start));
531
- continue;
532
- }
533
- if (ch === "'") {
534
- const start = i;
535
- i++;
536
- while (i < len && code[i] !== "'" && code[i] !== '\n') {
537
- if (code[i] === '\\')
538
- i++;
539
- i++;
540
- }
541
- i++;
542
- out.push(' '.repeat(i - start));
543
- continue;
544
- }
545
- out.push(ch);
546
- i++;
547
- }
548
- return out.join('');
549
- }
550
- /**
551
- * Public-by-design credential patterns — not secrets, should not flag.
552
- * Firebase web API keys: Google documents these as safe to expose (security is via Firebase Rules).
553
- * Stripe publishable keys: meant to be used in client code (pk_test_, pk_live_).
554
- * Supabase anon keys: public by design when used with row-level security.
555
- */
556
- const PUBLIC_KEY_SIGNATURES = [
557
- /AIzaSy[A-Za-z0-9_-]{33}/, // Firebase / Google Cloud web API key
558
- /pk_(?:test|live)_[A-Za-z0-9]{20,}/, // Stripe publishable key
559
- ];
560
- function isKnownPublicKey(matched) {
561
- if (!matched)
562
- return false;
563
- return PUBLIC_KEY_SIGNATURES.some((re) => re.test(matched));
564
- }
565
- export function scoreFile(filepath, code) {
566
- const allFindings = [];
567
- // Pass 1: Comments+regex stripped but strings visible — catches secrets/SQL inside literals
568
- const rawForSecrets = stripCommentsAndRegex(code);
569
- for (const pattern of CODE_PATTERNS.filter((p) => p.scanRaw)) {
570
- let match;
571
- const re = new RegExp(pattern.regex.source, pattern.regex.flags);
572
- while ((match = re.exec(rawForSecrets)) !== null) {
573
- const matched = match[0].substring(0, 80);
574
- // Skip known public-by-design keys (Firebase, Stripe publishable, etc.)
575
- if (pattern.category === 'Secrets' && isKnownPublicKey(matched)) {
576
- if (!re.global)
577
- break;
578
- continue;
579
- }
580
- const line = code.substring(0, match.index).split('\n').length;
581
- allFindings.push({
582
- severity: pattern.severity,
583
- category: pattern.category,
584
- description: pattern.description,
585
- line,
586
- cwe: pattern.cwe,
587
- matched,
588
- });
589
- if (!re.global)
590
- break;
591
- }
592
- }
593
- // Pass 2: Fully stripped code — structural patterns (no false positives from literals)
594
- const scannable = stripLiterals(code);
595
- // Context gate test: use comment-stripped source so `// require('child_process')`
596
- // in a comment doesn't count as real usage. Strings still visible so actual
597
- // imports like `from 'child_process'` still match.
598
- const contextTarget = rawForSecrets;
599
- for (const pattern of CODE_PATTERNS.filter((p) => !p.scanRaw)) {
600
- // Context gate: skip pattern if the file doesn't have the required context
601
- // (e.g., .exec(var) only fires if the file uses child_process).
602
- if (pattern.requiresContext && !pattern.requiresContext.test(contextTarget)) {
603
- continue;
604
- }
605
- let match;
606
- const re = new RegExp(pattern.regex.source, pattern.regex.flags);
607
- while ((match = re.exec(scannable)) !== null) {
608
- if (pattern.matchFilter && !pattern.matchFilter(match, contextTarget)) {
609
- if (!re.global)
610
- break;
611
- continue;
612
- }
613
- const line = code.substring(0, match.index).split('\n').length;
614
- allFindings.push({
615
- severity: pattern.severity,
616
- category: pattern.category,
617
- description: pattern.description,
618
- line,
619
- cwe: pattern.cwe,
620
- });
621
- if (!re.global)
622
- break;
623
- }
624
- }
625
- // Pass 3 (removed): hook-layer instruction patterns are designed for runtime
626
- // tool_input JSON, not source code. Firing them on source lit up every docstring
627
- // containing phrases like "act as a flag". The hook handler still uses scanPayload
628
- // directly; source scanning stays with Pass 1 + Pass 2 only.
629
- const complexity = measureComplexity(code);
630
- let penalty = 0;
631
- for (const f of allFindings)
632
- penalty += SEVERITY_PENALTY[f.severity] ?? 2;
633
- if (complexity.cyclomaticEstimate > 20)
634
- penalty += 5;
635
- if (complexity.cyclomaticEstimate > 50)
636
- penalty += 10;
637
- if (complexity.nestingDepth > 6)
638
- penalty += 5;
639
- if (complexity.todos > 5)
640
- penalty += 3;
641
- const score = Math.max(0, Math.min(100, 100 - penalty));
642
- const grade = score >= 90 ? 'A' : score >= 75 ? 'B' : score >= 60 ? 'C' : score >= 40 ? 'D' : 'F';
643
- const critical = allFindings.filter((f) => f.severity === 'critical').length;
644
- const high = allFindings.filter((f) => f.severity === 'high').length;
645
- const summary = critical > 0
646
- ? `${critical} critical issue${critical > 1 ? 's' : ''} require immediate attention`
647
- : high > 0
648
- ? `${high} high-severity issue${high > 1 ? 's' : ''} should be addressed before merge`
649
- : allFindings.length === 0
650
- ? 'No issues found — clean scan'
651
- : `${allFindings.length} minor issue${allFindings.length > 1 ? 's' : ''} found`;
652
- return { filepath, score, grade, findings: allFindings, complexity, summary };
653
- }
654
- export function gradeFromScore(score) {
655
- return score >= 90 ? 'A' : score >= 75 ? 'B' : score >= 60 ? 'C' : score >= 40 ? 'D' : 'F';
656
- }
657
- //# sourceMappingURL=trust-scorer.js.map