codehere 1.0.1 → 1.0.3

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