vibecheck-ai 2.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/bin/.generated +25 -0
  2. package/bin/_deprecations.js +463 -0
  3. package/bin/_router.js +46 -0
  4. package/bin/cli-hygiene.js +241 -0
  5. package/bin/dev/run-v2-torture.js +30 -0
  6. package/bin/registry.js +656 -0
  7. package/bin/runners/CLI_REFACTOR_SUMMARY.md +229 -0
  8. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -0
  9. package/bin/runners/REPORT_AUDIT.md +64 -0
  10. package/bin/runners/cli-utils.js +1070 -0
  11. package/bin/runners/context/ai-task-decomposer.js +337 -0
  12. package/bin/runners/context/analyzer.js +513 -0
  13. package/bin/runners/context/api-contracts.js +427 -0
  14. package/bin/runners/context/context-diff.js +342 -0
  15. package/bin/runners/context/context-pruner.js +291 -0
  16. package/bin/runners/context/dependency-graph.js +414 -0
  17. package/bin/runners/context/generators/claude.js +107 -0
  18. package/bin/runners/context/generators/codex.js +108 -0
  19. package/bin/runners/context/generators/copilot.js +119 -0
  20. package/bin/runners/context/generators/cursor-enhanced.js +2525 -0
  21. package/bin/runners/context/generators/cursor.js +514 -0
  22. package/bin/runners/context/generators/mcp.js +169 -0
  23. package/bin/runners/context/generators/windsurf.js +180 -0
  24. package/bin/runners/context/git-context.js +304 -0
  25. package/bin/runners/context/index.js +1110 -0
  26. package/bin/runners/context/insights.js +173 -0
  27. package/bin/runners/context/mcp-server/generate-rules.js +337 -0
  28. package/bin/runners/context/mcp-server/index.js +1176 -0
  29. package/bin/runners/context/mcp-server/package.json +24 -0
  30. package/bin/runners/context/memory.js +200 -0
  31. package/bin/runners/context/monorepo.js +215 -0
  32. package/bin/runners/context/multi-repo-federation.js +404 -0
  33. package/bin/runners/context/patterns.js +253 -0
  34. package/bin/runners/context/proof-context.js +1264 -0
  35. package/bin/runners/context/security-scanner.js +541 -0
  36. package/bin/runners/context/semantic-search.js +350 -0
  37. package/bin/runners/context/shared.js +264 -0
  38. package/bin/runners/context/team-conventions.js +336 -0
  39. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -0
  40. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  41. package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
  42. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  43. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  44. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  45. package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
  46. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  47. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  48. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  49. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  50. package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
  51. package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
  52. package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
  53. package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
  54. package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
  55. package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
  56. package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
  57. package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
  58. package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
  59. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  60. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  61. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
  62. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  63. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
  64. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  65. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  66. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  67. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  68. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  69. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  70. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  71. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  72. package/bin/runners/lib/agent-firewall/index.js +200 -0
  73. package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
  74. package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
  75. package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -0
  76. package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
  77. package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
  78. package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
  79. package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
  80. package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
  81. package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
  82. package/bin/runners/lib/agent-firewall/interceptor/base.js +308 -0
  83. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  84. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  85. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  86. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  87. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  88. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  89. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  90. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  91. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  92. package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
  93. package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
  94. package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
  95. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  96. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  97. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +79 -0
  98. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +227 -0
  99. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +191 -0
  100. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  101. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  102. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  103. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  104. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  105. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  106. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  107. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  108. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  109. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  110. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  111. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  112. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  113. package/bin/runners/lib/agent-firewall/risk/thresholds.js +322 -0
  114. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  115. package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
  116. package/bin/runners/lib/agent-firewall/session/index.js +26 -0
  117. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  118. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  119. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  120. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  121. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  122. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  123. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  124. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  125. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  126. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  127. package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
  128. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  129. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  130. package/bin/runners/lib/ai-bridge.js +416 -0
  131. package/bin/runners/lib/analysis-core.js +309 -0
  132. package/bin/runners/lib/analyzers.js +2500 -0
  133. package/bin/runners/lib/api-client.js +269 -0
  134. package/bin/runners/lib/approve-output.js +235 -0
  135. package/bin/runners/lib/artifact-envelope.js +540 -0
  136. package/bin/runners/lib/assets/vibecheck-logo.png +0 -0
  137. package/bin/runners/lib/audit-bridge.js +391 -0
  138. package/bin/runners/lib/auth-shared.js +977 -0
  139. package/bin/runners/lib/auth-truth.js +193 -0
  140. package/bin/runners/lib/auth.js +215 -0
  141. package/bin/runners/lib/authority-badge.js +425 -0
  142. package/bin/runners/lib/backup.js +62 -0
  143. package/bin/runners/lib/billing.js +107 -0
  144. package/bin/runners/lib/checkpoint.js +941 -0
  145. package/bin/runners/lib/claims.js +118 -0
  146. package/bin/runners/lib/classify-output.js +204 -0
  147. package/bin/runners/lib/cleanup/engine.js +571 -0
  148. package/bin/runners/lib/cleanup/index.js +53 -0
  149. package/bin/runners/lib/cleanup/output.js +375 -0
  150. package/bin/runners/lib/cleanup/rules.js +1060 -0
  151. package/bin/runners/lib/cli-output.js +400 -0
  152. package/bin/runners/lib/cli-ui.js +540 -0
  153. package/bin/runners/lib/compliance-bridge-new.js +0 -0
  154. package/bin/runners/lib/compliance-bridge.js +165 -0
  155. package/bin/runners/lib/contracts/auth-contract.js +202 -0
  156. package/bin/runners/lib/contracts/env-contract.js +181 -0
  157. package/bin/runners/lib/contracts/external-contract.js +206 -0
  158. package/bin/runners/lib/contracts/guard.js +168 -0
  159. package/bin/runners/lib/contracts/index.js +89 -0
  160. package/bin/runners/lib/contracts/plan-validator.js +311 -0
  161. package/bin/runners/lib/contracts/route-contract.js +199 -0
  162. package/bin/runners/lib/contracts.js +804 -0
  163. package/bin/runners/lib/default-config.js +127 -0
  164. package/bin/runners/lib/detect.js +89 -0
  165. package/bin/runners/lib/detectors-v2.js +622 -0
  166. package/bin/runners/lib/doctor/autofix.js +254 -0
  167. package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
  168. package/bin/runners/lib/doctor/failure-signatures.js +526 -0
  169. package/bin/runners/lib/doctor/fix-script.js +336 -0
  170. package/bin/runners/lib/doctor/index.js +37 -0
  171. package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
  172. package/bin/runners/lib/doctor/modules/dependencies.js +325 -0
  173. package/bin/runners/lib/doctor/modules/index.js +105 -0
  174. package/bin/runners/lib/doctor/modules/network.js +250 -0
  175. package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
  176. package/bin/runners/lib/doctor/modules/project.js +312 -0
  177. package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
  178. package/bin/runners/lib/doctor/modules/runtime.js +224 -0
  179. package/bin/runners/lib/doctor/modules/security.js +350 -0
  180. package/bin/runners/lib/doctor/modules/system.js +213 -0
  181. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -0
  182. package/bin/runners/lib/doctor/reporter.js +262 -0
  183. package/bin/runners/lib/doctor/safe-repair.js +384 -0
  184. package/bin/runners/lib/doctor/service.js +262 -0
  185. package/bin/runners/lib/doctor/types.js +113 -0
  186. package/bin/runners/lib/doctor/ui.js +263 -0
  187. package/bin/runners/lib/doctor-enhanced.js +233 -0
  188. package/bin/runners/lib/doctor-output.js +226 -0
  189. package/bin/runners/lib/doctor-v2.js +608 -0
  190. package/bin/runners/lib/drift.js +425 -0
  191. package/bin/runners/lib/enforcement.js +72 -0
  192. package/bin/runners/lib/engine/ast-cache.js +210 -0
  193. package/bin/runners/lib/engine/auth-extractor.js +211 -0
  194. package/bin/runners/lib/engine/billing-extractor.js +112 -0
  195. package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
  196. package/bin/runners/lib/engine/env-extractor.js +207 -0
  197. package/bin/runners/lib/engine/express-extractor.js +208 -0
  198. package/bin/runners/lib/engine/extractors.js +849 -0
  199. package/bin/runners/lib/engine/index.js +207 -0
  200. package/bin/runners/lib/engine/repo-index.js +514 -0
  201. package/bin/runners/lib/engine/types.js +124 -0
  202. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  203. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  204. package/bin/runners/lib/engines/ast-cache.js +99 -0
  205. package/bin/runners/lib/engines/attack-detector.js +1192 -0
  206. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  207. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  208. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  209. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  210. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  211. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  212. package/bin/runners/lib/engines/file-filter.js +131 -0
  213. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  214. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  215. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  216. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  217. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  218. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  219. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  220. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  221. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  222. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  223. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  224. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  225. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  226. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  227. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  228. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  229. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  230. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  231. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  232. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  233. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  234. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  235. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  236. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  237. package/bin/runners/lib/enterprise-detect.js +603 -0
  238. package/bin/runners/lib/enterprise-init.js +942 -0
  239. package/bin/runners/lib/entitlements-v2.js +265 -0
  240. package/bin/runners/lib/entitlements.generated.js +0 -0
  241. package/bin/runners/lib/entitlements.js +340 -0
  242. package/bin/runners/lib/env-resolver.js +417 -0
  243. package/bin/runners/lib/env-template.js +66 -0
  244. package/bin/runners/lib/env.js +189 -0
  245. package/bin/runners/lib/error-handler.js +368 -0
  246. package/bin/runners/lib/error-messages.js +289 -0
  247. package/bin/runners/lib/evidence-pack.js +684 -0
  248. package/bin/runners/lib/exit-codes.js +275 -0
  249. package/bin/runners/lib/extractors/client-calls.js +990 -0
  250. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -0
  251. package/bin/runners/lib/extractors/fastify-routes.js +426 -0
  252. package/bin/runners/lib/extractors/index.js +363 -0
  253. package/bin/runners/lib/extractors/next-routes.js +524 -0
  254. package/bin/runners/lib/extractors/proof-graph.js +431 -0
  255. package/bin/runners/lib/extractors/route-matcher.js +451 -0
  256. package/bin/runners/lib/extractors/truthpack-v2.js +377 -0
  257. package/bin/runners/lib/extractors/ui-bindings.js +547 -0
  258. package/bin/runners/lib/finding-id.js +69 -0
  259. package/bin/runners/lib/finding-sorter.js +89 -0
  260. package/bin/runners/lib/findings-schema.js +281 -0
  261. package/bin/runners/lib/fingerprint.js +377 -0
  262. package/bin/runners/lib/firewall-prompt.js +50 -0
  263. package/bin/runners/lib/fix-output.js +228 -0
  264. package/bin/runners/lib/global-flags.js +250 -0
  265. package/bin/runners/lib/graph/graph-builder.js +265 -0
  266. package/bin/runners/lib/graph/html-renderer.js +413 -0
  267. package/bin/runners/lib/graph/index.js +32 -0
  268. package/bin/runners/lib/graph/runtime-collector.js +215 -0
  269. package/bin/runners/lib/graph/static-extractor.js +518 -0
  270. package/bin/runners/lib/help-formatter.js +413 -0
  271. package/bin/runners/lib/html-proof-report.js +913 -0
  272. package/bin/runners/lib/html-report.js +650 -0
  273. package/bin/runners/lib/init-wizard.js +601 -0
  274. package/bin/runners/lib/interactive-menu.js +1496 -0
  275. package/bin/runners/lib/json-output.js +76 -0
  276. package/bin/runners/lib/llm.js +75 -0
  277. package/bin/runners/lib/logger.js +38 -0
  278. package/bin/runners/lib/meter.js +61 -0
  279. package/bin/runners/lib/missions/briefing.js +427 -0
  280. package/bin/runners/lib/missions/checkpoint.js +753 -0
  281. package/bin/runners/lib/missions/evidence.js +126 -0
  282. package/bin/runners/lib/missions/hardening.js +851 -0
  283. package/bin/runners/lib/missions/plan.js +648 -0
  284. package/bin/runners/lib/missions/safety-gates.js +645 -0
  285. package/bin/runners/lib/missions/schema.js +478 -0
  286. package/bin/runners/lib/missions/templates.js +317 -0
  287. package/bin/runners/lib/next-action.js +560 -0
  288. package/bin/runners/lib/packs/bundle.js +675 -0
  289. package/bin/runners/lib/packs/evidence-pack.js +671 -0
  290. package/bin/runners/lib/packs/pack-factory.js +837 -0
  291. package/bin/runners/lib/packs/permissions-pack.js +686 -0
  292. package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
  293. package/bin/runners/lib/patch.js +40 -0
  294. package/bin/runners/lib/permissions/auth-model.js +213 -0
  295. package/bin/runners/lib/permissions/idor-prover.js +205 -0
  296. package/bin/runners/lib/permissions/index.js +45 -0
  297. package/bin/runners/lib/permissions/matrix-builder.js +198 -0
  298. package/bin/runners/lib/pkgjson.js +28 -0
  299. package/bin/runners/lib/policy.js +295 -0
  300. package/bin/runners/lib/polish/accessibility.js +62 -0
  301. package/bin/runners/lib/polish/analyzer.js +93 -0
  302. package/bin/runners/lib/polish/backend.js +87 -0
  303. package/bin/runners/lib/polish/configuration.js +83 -0
  304. package/bin/runners/lib/polish/documentation.js +83 -0
  305. package/bin/runners/lib/polish/frontend.js +817 -0
  306. package/bin/runners/lib/polish/index.js +27 -0
  307. package/bin/runners/lib/polish/infrastructure.js +80 -0
  308. package/bin/runners/lib/polish/internationalization.js +85 -0
  309. package/bin/runners/lib/polish/libraries.js +180 -0
  310. package/bin/runners/lib/polish/observability.js +75 -0
  311. package/bin/runners/lib/polish/performance.js +64 -0
  312. package/bin/runners/lib/polish/privacy.js +110 -0
  313. package/bin/runners/lib/polish/resilience.js +92 -0
  314. package/bin/runners/lib/polish/security.js +78 -0
  315. package/bin/runners/lib/polish/seo.js +71 -0
  316. package/bin/runners/lib/polish/styles.js +62 -0
  317. package/bin/runners/lib/polish/utils.js +104 -0
  318. package/bin/runners/lib/preflight.js +142 -0
  319. package/bin/runners/lib/prerequisites.js +149 -0
  320. package/bin/runners/lib/prove-output.js +220 -0
  321. package/bin/runners/lib/reality/correlation-detectors.js +359 -0
  322. package/bin/runners/lib/reality/index.js +318 -0
  323. package/bin/runners/lib/reality/request-hashing.js +416 -0
  324. package/bin/runners/lib/reality/request-mapper.js +453 -0
  325. package/bin/runners/lib/reality/safety-rails.js +463 -0
  326. package/bin/runners/lib/reality/semantic-snapshot.js +408 -0
  327. package/bin/runners/lib/reality/toast-detector.js +393 -0
  328. package/bin/runners/lib/reality-findings.js +84 -0
  329. package/bin/runners/lib/reality-output.js +231 -0
  330. package/bin/runners/lib/receipts.js +179 -0
  331. package/bin/runners/lib/redact.js +29 -0
  332. package/bin/runners/lib/replay/capsule-manager.js +154 -0
  333. package/bin/runners/lib/replay/index.js +263 -0
  334. package/bin/runners/lib/replay/player.js +348 -0
  335. package/bin/runners/lib/replay/recorder.js +331 -0
  336. package/bin/runners/lib/report-engine.js +626 -0
  337. package/bin/runners/lib/report-html.js +1233 -0
  338. package/bin/runners/lib/report-output.js +366 -0
  339. package/bin/runners/lib/report-templates.js +967 -0
  340. package/bin/runners/lib/report.js +135 -0
  341. package/bin/runners/lib/route-detection.js +1209 -0
  342. package/bin/runners/lib/route-truth.js +1322 -0
  343. package/bin/runners/lib/safelist/index.js +96 -0
  344. package/bin/runners/lib/safelist/integration.js +334 -0
  345. package/bin/runners/lib/safelist/matcher.js +696 -0
  346. package/bin/runners/lib/safelist/schema.js +948 -0
  347. package/bin/runners/lib/safelist/store.js +438 -0
  348. package/bin/runners/lib/sandbox/index.js +59 -0
  349. package/bin/runners/lib/sandbox/proof-chain.js +399 -0
  350. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -0
  351. package/bin/runners/lib/sandbox/worktree.js +174 -0
  352. package/bin/runners/lib/scan-cache.js +330 -0
  353. package/bin/runners/lib/scan-output-schema.js +344 -0
  354. package/bin/runners/lib/scan-output.js +631 -0
  355. package/bin/runners/lib/scan-runner.js +135 -0
  356. package/bin/runners/lib/schema-validator.js +350 -0
  357. package/bin/runners/lib/schemas/ajv-validator.js +464 -0
  358. package/bin/runners/lib/schemas/contracts.schema.json +160 -0
  359. package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
  360. package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
  361. package/bin/runners/lib/schemas/finding.schema.json +100 -0
  362. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -0
  363. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -0
  364. package/bin/runners/lib/schemas/reality-report.schema.json +162 -0
  365. package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
  366. package/bin/runners/lib/schemas/run-request.schema.json +108 -0
  367. package/bin/runners/lib/schemas/share-pack.schema.json +180 -0
  368. package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
  369. package/bin/runners/lib/schemas/ship-report.schema.json +117 -0
  370. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -0
  371. package/bin/runners/lib/schemas/validator.js +465 -0
  372. package/bin/runners/lib/schemas/verdict.schema.json +140 -0
  373. package/bin/runners/lib/score-history.js +282 -0
  374. package/bin/runners/lib/security-bridge.js +249 -0
  375. package/bin/runners/lib/server-usage.js +513 -0
  376. package/bin/runners/lib/share-pack.js +239 -0
  377. package/bin/runners/lib/ship-gate.js +832 -0
  378. package/bin/runners/lib/ship-manifest.js +1153 -0
  379. package/bin/runners/lib/ship-output-enterprise.js +239 -0
  380. package/bin/runners/lib/ship-output.js +1128 -0
  381. package/bin/runners/lib/snippets.js +67 -0
  382. package/bin/runners/lib/status-output.js +340 -0
  383. package/bin/runners/lib/terminal-ui.js +356 -0
  384. package/bin/runners/lib/truth.js +1691 -0
  385. package/bin/runners/lib/ui.js +562 -0
  386. package/bin/runners/lib/unified-cli-output.js +947 -0
  387. package/bin/runners/lib/unified-output.js +197 -0
  388. package/bin/runners/lib/upsell.js +410 -0
  389. package/bin/runners/lib/usage.js +153 -0
  390. package/bin/runners/lib/validate-patch.js +156 -0
  391. package/bin/runners/lib/verdict-engine.js +628 -0
  392. package/bin/runners/lib/verification.js +345 -0
  393. package/bin/runners/lib/why-tree.js +650 -0
  394. package/bin/runners/reality/engine.js +917 -0
  395. package/bin/runners/reality/flows.js +122 -0
  396. package/bin/runners/reality/report.js +378 -0
  397. package/bin/runners/reality/session.js +193 -0
  398. package/bin/runners/runAIAgent.js +229 -0
  399. package/bin/runners/runAgent.d.ts +5 -0
  400. package/bin/runners/runAgent.js +161 -0
  401. package/bin/runners/runAllowlist.js +418 -0
  402. package/bin/runners/runApprove.js +320 -0
  403. package/bin/runners/runAudit.js +692 -0
  404. package/bin/runners/runAuth.js +731 -0
  405. package/bin/runners/runCI.js +353 -0
  406. package/bin/runners/runCheckpoint.js +530 -0
  407. package/bin/runners/runClassify.js +928 -0
  408. package/bin/runners/runCleanup.js +343 -0
  409. package/bin/runners/runContext.d.ts +4 -0
  410. package/bin/runners/runContext.js +175 -0
  411. package/bin/runners/runDoctor.js +877 -0
  412. package/bin/runners/runEvidencePack.js +362 -0
  413. package/bin/runners/runFirewall.d.ts +5 -0
  414. package/bin/runners/runFirewall.js +134 -0
  415. package/bin/runners/runFirewallHook.d.ts +5 -0
  416. package/bin/runners/runFirewallHook.js +56 -0
  417. package/bin/runners/runFix.js +1355 -0
  418. package/bin/runners/runForge.js +451 -0
  419. package/bin/runners/runGuard.js +262 -0
  420. package/bin/runners/runInit.js +1927 -0
  421. package/bin/runners/runIntent.js +906 -0
  422. package/bin/runners/runKickoff.js +878 -0
  423. package/bin/runners/runLabs.js +424 -0
  424. package/bin/runners/runLaunch.js +2000 -0
  425. package/bin/runners/runLink.js +785 -0
  426. package/bin/runners/runMcp.js +1875 -0
  427. package/bin/runners/runPacks.js +2089 -0
  428. package/bin/runners/runPolish.d.ts +4 -0
  429. package/bin/runners/runPolish.js +390 -0
  430. package/bin/runners/runPromptFirewall.js +211 -0
  431. package/bin/runners/runProve.js +1411 -0
  432. package/bin/runners/runQuickstart.js +531 -0
  433. package/bin/runners/runReality.js +2260 -0
  434. package/bin/runners/runReport.js +726 -0
  435. package/bin/runners/runRuntime.js +110 -0
  436. package/bin/runners/runSafelist.js +1190 -0
  437. package/bin/runners/runScan.js +688 -0
  438. package/bin/runners/runShield.js +1282 -0
  439. package/bin/runners/runShip.js +1660 -0
  440. package/bin/runners/runTruth.d.ts +5 -0
  441. package/bin/runners/runTruth.js +101 -0
  442. package/bin/runners/runValidate.js +179 -0
  443. package/bin/runners/runWatch.js +478 -0
  444. package/bin/runners/utils.js +360 -0
  445. package/bin/scan.js +617 -0
  446. package/bin/vibecheck.js +1617 -0
  447. package/dist/guardrail/index.d.ts +2405 -0
  448. package/dist/guardrail/index.js +9747 -0
  449. package/dist/guardrail/index.js.map +1 -0
  450. package/dist/scanner/index.d.ts +282 -0
  451. package/dist/scanner/index.js +3395 -0
  452. package/dist/scanner/index.js.map +1 -0
  453. package/package.json +123 -104
  454. package/README.md +0 -491
  455. package/dist/index.js +0 -99711
  456. package/dist/index.js.map +0 -1
@@ -0,0 +1,3395 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/scanner/index.ts
21
+ var scanner_exports = {};
22
+ __export(scanner_exports, {
23
+ ALL_ENGINES: () => ALL_ENGINES,
24
+ RULE_CATALOG: () => RULE_CATALOG,
25
+ applyFixes: () => applyFixes,
26
+ classifyPath: () => classifyPath,
27
+ fix: () => fix,
28
+ getRuleOrDefault: () => getRuleOrDefault,
29
+ scan: () => scan
30
+ });
31
+ module.exports = __toCommonJS(scanner_exports);
32
+ var import_fs3 = require("fs");
33
+ var import_path4 = require("path");
34
+ var import_crypto = require("crypto");
35
+
36
+ // src/scanner/classifiers/path-classifier.ts
37
+ var import_path = require("path");
38
+ var CATEGORY_PATTERNS = [
39
+ {
40
+ category: "third_party",
41
+ excludeByDefault: true,
42
+ patterns: [
43
+ /node_modules\//,
44
+ /vendor\//,
45
+ /\.yarn\//,
46
+ /\.pnpm\//,
47
+ /bower_components\//,
48
+ /packages\/.*\/node_modules/
49
+ ]
50
+ },
51
+ {
52
+ category: "build_output",
53
+ excludeByDefault: true,
54
+ patterns: [
55
+ /\/dist\//,
56
+ /\/build\//,
57
+ /\/.next\//,
58
+ /\/.nuxt\//,
59
+ /\/\.output\//,
60
+ /\/out\//,
61
+ /\/coverage\//,
62
+ /\/__generated__\//,
63
+ /\.min\.(js|css)$/,
64
+ /\.bundle\.(js|css)$/,
65
+ /\.chunk\.(js|css)$/
66
+ ]
67
+ },
68
+ {
69
+ category: "generated",
70
+ excludeByDefault: true,
71
+ patterns: [
72
+ /\.d\.ts$/,
73
+ /generated\//,
74
+ /\.gen\.(ts|js)$/,
75
+ /prisma\/generated\//,
76
+ /graphql\/generated\//,
77
+ /swagger-output\./,
78
+ /openapi-generated\//,
79
+ /\.lock$/,
80
+ /lock\.json$/
81
+ ]
82
+ },
83
+ {
84
+ category: "test",
85
+ excludeByDefault: false,
86
+ // We scan tests separately
87
+ patterns: [
88
+ /\.(test|spec)\.(ts|tsx|js|jsx)$/,
89
+ /__tests__\//,
90
+ /__mocks__\//,
91
+ /\.stories\.(ts|tsx|js|jsx)$/,
92
+ /\.storybook\//,
93
+ /test-utils?\.(ts|tsx|js|jsx)$/,
94
+ /fixtures?\//,
95
+ /cypress\//,
96
+ /playwright\//,
97
+ /e2e\//,
98
+ /setupTests\.(ts|js)$/,
99
+ /jest\.config\./,
100
+ /vitest\.config\./
101
+ ]
102
+ },
103
+ {
104
+ category: "config",
105
+ excludeByDefault: false,
106
+ patterns: [
107
+ /\.(config|rc)\.(ts|js|mjs|cjs|json|yaml|yml)$/,
108
+ /\.eslintrc/,
109
+ /\.prettierrc/,
110
+ /tsconfig.*\.json$/,
111
+ /\.babelrc/,
112
+ /tailwind\.config/,
113
+ /next\.config/,
114
+ /vite\.config/,
115
+ /webpack\.config/
116
+ ]
117
+ },
118
+ {
119
+ category: "documentation",
120
+ excludeByDefault: true,
121
+ patterns: [
122
+ /\.(md|mdx|rst|txt)$/,
123
+ /\/docs?\//,
124
+ /\/documentation\//,
125
+ /README/i,
126
+ /CHANGELOG/i,
127
+ /LICENSE/i,
128
+ /CONTRIBUTING/i
129
+ ]
130
+ }
131
+ ];
132
+ var CRITICAL_PATH_PATTERNS = [
133
+ /\/api\//,
134
+ /\/auth\//,
135
+ /\/payment/,
136
+ /\/billing/,
137
+ /\/admin/,
138
+ /\/checkout/,
139
+ /\/users\//,
140
+ /\/account/,
141
+ /\/webhook/,
142
+ /\/security/,
143
+ /middleware\.(ts|js)/,
144
+ /\/lib\/auth/,
145
+ /\/lib\/db/,
146
+ /\/lib\/stripe/
147
+ ];
148
+ var ALWAYS_EXCLUDE = [
149
+ /\.git\//,
150
+ /\.DS_Store$/,
151
+ /thumbs\.db$/i,
152
+ /\.(png|jpg|jpeg|gif|webp|svg|ico|bmp|tiff?)$/i,
153
+ /\.(woff2?|ttf|eot|otf)$/i,
154
+ /\.(mp3|mp4|wav|ogg|webm|avi|mov)$/i,
155
+ /\.(pdf|doc|docx|xls|xlsx|ppt|pptx)$/i,
156
+ /\.(zip|tar|gz|bz2|7z|rar)$/i,
157
+ /\.(exe|dll|so|dylib|bin)$/i,
158
+ /\.(sqlite|db)$/i
159
+ ];
160
+ function classifyPath(relativePath) {
161
+ const normalizedPath = relativePath.replace(/\\/g, "/");
162
+ for (const pattern of ALWAYS_EXCLUDE) {
163
+ if (pattern.test(normalizedPath)) {
164
+ return {
165
+ category: "build_output",
166
+ reason: `Binary/irrelevant file: ${pattern.source}`,
167
+ excludeByDefault: true,
168
+ isCriticalPath: false
169
+ };
170
+ }
171
+ }
172
+ for (const def of CATEGORY_PATTERNS) {
173
+ for (const pattern of def.patterns) {
174
+ if (pattern.test(normalizedPath)) {
175
+ return {
176
+ category: def.category,
177
+ reason: `Matched pattern: ${pattern.source}`,
178
+ excludeByDefault: def.excludeByDefault,
179
+ isCriticalPath: false
180
+ };
181
+ }
182
+ }
183
+ }
184
+ const isCritical = CRITICAL_PATH_PATTERNS.some((p) => p.test(normalizedPath));
185
+ return {
186
+ category: "user_code",
187
+ reason: "Application source code",
188
+ excludeByDefault: false,
189
+ isCriticalPath: isCritical
190
+ };
191
+ }
192
+ function isCodeFile(filePath) {
193
+ const ext = (0, import_path.extname)(filePath).toLowerCase();
194
+ return [
195
+ ".ts",
196
+ ".tsx",
197
+ ".js",
198
+ ".jsx",
199
+ ".mjs",
200
+ ".cjs",
201
+ ".py",
202
+ ".java",
203
+ ".go",
204
+ ".rs",
205
+ ".rb",
206
+ ".php",
207
+ ".vue",
208
+ ".svelte"
209
+ ].includes(ext);
210
+ }
211
+ function isUIFile(filePath) {
212
+ const ext = (0, import_path.extname)(filePath).toLowerCase();
213
+ return [".tsx", ".jsx", ".vue", ".svelte"].includes(ext);
214
+ }
215
+ function escalateSeverity(severity, isCriticalPath) {
216
+ if (!isCriticalPath) return severity;
217
+ const escalation = {
218
+ low: "medium",
219
+ medium: "high",
220
+ high: "critical",
221
+ critical: "critical"
222
+ };
223
+ return escalation[severity];
224
+ }
225
+
226
+ // src/scanner/utils/dedup.ts
227
+ var SEVERITY_ORDER = {
228
+ critical: 4,
229
+ high: 3,
230
+ medium: 2,
231
+ low: 1
232
+ };
233
+ function deduplicateFindings(findings) {
234
+ const groups = /* @__PURE__ */ new Map();
235
+ for (const finding of findings) {
236
+ const key = `${finding.file}:${finding.line}`;
237
+ if (!groups.has(key)) {
238
+ groups.set(key, []);
239
+ }
240
+ groups.get(key).push(finding);
241
+ }
242
+ const deduplicated = [];
243
+ let suppressedCount = 0;
244
+ for (const [, group] of groups) {
245
+ if (group.length === 1) {
246
+ deduplicated.push(group[0]);
247
+ continue;
248
+ }
249
+ const subGroups = /* @__PURE__ */ new Map();
250
+ for (const f of group) {
251
+ const subKey = f.category;
252
+ if (!subGroups.has(subKey)) subGroups.set(subKey, []);
253
+ subGroups.get(subKey).push(f);
254
+ }
255
+ for (const [, subGroup] of subGroups) {
256
+ if (subGroup.length === 1) {
257
+ deduplicated.push(subGroup[0]);
258
+ continue;
259
+ }
260
+ subGroup.sort((a, b) => {
261
+ const confDiff = b.confidenceScore - a.confidenceScore;
262
+ if (confDiff !== 0) return confDiff;
263
+ return (SEVERITY_ORDER[b.severity] ?? 0) - (SEVERITY_ORDER[a.severity] ?? 0);
264
+ });
265
+ const best = { ...subGroup[0] };
266
+ const allTags = /* @__PURE__ */ new Set();
267
+ for (const f of subGroup) {
268
+ for (const tag of f.tags) allTags.add(tag);
269
+ }
270
+ best.tags = Array.from(allTags);
271
+ const uniqueEngines = new Set(subGroup.map((f) => f.engine));
272
+ if (uniqueEngines.size >= 2) {
273
+ best.verified = true;
274
+ if (best.severity !== "critical") {
275
+ const maxSeverity = subGroup.reduce(
276
+ (max, f) => SEVERITY_ORDER[f.severity] > SEVERITY_ORDER[max] ? f.severity : max,
277
+ best.severity
278
+ );
279
+ best.severity = maxSeverity;
280
+ }
281
+ best.confidenceScore = Math.min(99, best.confidenceScore + 5);
282
+ }
283
+ deduplicated.push(best);
284
+ suppressedCount += subGroup.length - 1;
285
+ }
286
+ }
287
+ deduplicated.sort((a, b) => {
288
+ const sevDiff = (SEVERITY_ORDER[b.severity] ?? 0) - (SEVERITY_ORDER[a.severity] ?? 0);
289
+ if (sevDiff !== 0) return sevDiff;
290
+ return b.confidenceScore - a.confidenceScore;
291
+ });
292
+ return { deduplicated, suppressedCount };
293
+ }
294
+
295
+ // src/scanner/rules/catalog.ts
296
+ var RULE_CATALOG = {
297
+ // ═══════════════════════════════════════════════════════════════════════════
298
+ // CREDENTIALS (CRED)
299
+ // ═══════════════════════════════════════════════════════════════════════════
300
+ CRED001: {
301
+ ruleId: "CRED001",
302
+ name: "Hardcoded API Key",
303
+ category: "credentials",
304
+ severity: "critical",
305
+ description: "Hardcoded API key (Stripe, AWS, OpenAI, etc.) in source code",
306
+ why: "If your code is ever exposed (git push, npm publish, source maps), these credentials will be compromised. Attackers automate scanning for leaked keys.",
307
+ fix: "Move to environment variable. Use process.env.API_KEY and add to .env (gitignored).",
308
+ tags: ["secrets", "api-key", "credentials"],
309
+ autoFixable: false,
310
+ cwe: "CWE-798"
311
+ },
312
+ CRED002: {
313
+ ruleId: "CRED002",
314
+ name: "Hardcoded Password",
315
+ category: "credentials",
316
+ severity: "critical",
317
+ description: "Hardcoded password or database credential in source code",
318
+ why: "Passwords in code can be extracted from builds, logs, or version history. A single leaked DB password can compromise all user data.",
319
+ fix: "Use environment variables or a secrets manager (Vault, AWS Secrets Manager).",
320
+ tags: ["secrets", "password", "database"],
321
+ autoFixable: false,
322
+ cwe: "CWE-798"
323
+ },
324
+ CRED003: {
325
+ ruleId: "CRED003",
326
+ name: "Hardcoded JWT Secret",
327
+ category: "credentials",
328
+ severity: "critical",
329
+ description: "JWT signing secret hardcoded in source",
330
+ why: "Anyone with the JWT secret can forge authentication tokens and impersonate any user, including admins.",
331
+ fix: "Store in JWT_SECRET environment variable. Rotate regularly.",
332
+ tags: ["secrets", "jwt", "auth"],
333
+ autoFixable: false,
334
+ cwe: "CWE-798"
335
+ },
336
+ CRED004: {
337
+ ruleId: "CRED004",
338
+ name: "Private Key in Source",
339
+ category: "credentials",
340
+ severity: "critical",
341
+ description: "Private key (RSA, PEM, SSH) embedded in source code",
342
+ why: "Private keys grant access to encrypted communications, server authentication, and code signing. A leaked key requires full rotation.",
343
+ fix: "Store in secure key management. Load from file path in env var.",
344
+ tags: ["secrets", "private-key", "cryptography"],
345
+ autoFixable: false,
346
+ cwe: "CWE-321"
347
+ },
348
+ CRED005: {
349
+ ruleId: "CRED005",
350
+ name: "Connection String Exposed",
351
+ category: "credentials",
352
+ severity: "critical",
353
+ description: "Database connection string with credentials in source",
354
+ why: "Connection strings contain host, username, password, and database name. One string = complete database access.",
355
+ fix: "Use DATABASE_URL environment variable. Never commit .env files.",
356
+ tags: ["secrets", "database", "connection-string"],
357
+ autoFixable: false,
358
+ cwe: "CWE-798"
359
+ },
360
+ // ═══════════════════════════════════════════════════════════════════════════
361
+ // SECURITY (SEC)
362
+ // ═══════════════════════════════════════════════════════════════════════════
363
+ SEC001: {
364
+ ruleId: "SEC001",
365
+ name: "SQL Injection Risk",
366
+ category: "security",
367
+ severity: "critical",
368
+ description: "User input concatenated into SQL query",
369
+ why: "SQL injection allows attackers to read, modify, or delete all database data. It remains the #1 web vulnerability.",
370
+ fix: "Use parameterized queries or an ORM. Never concatenate user input into SQL.",
371
+ tags: ["injection", "sql", "database"],
372
+ autoFixable: false,
373
+ cwe: "CWE-89"
374
+ },
375
+ SEC002: {
376
+ ruleId: "SEC002",
377
+ name: "Command Injection Risk",
378
+ category: "security",
379
+ severity: "critical",
380
+ description: "User input passed to shell execution (exec, execSync, spawn)",
381
+ why: "Command injection lets attackers run arbitrary OS commands on your server \u2014 install backdoors, exfiltrate data, destroy files.",
382
+ fix: "Use execFile() with args array. Never pass user input to exec().",
383
+ tags: ["injection", "command", "shell"],
384
+ autoFixable: false,
385
+ cwe: "CWE-78"
386
+ },
387
+ SEC003: {
388
+ ruleId: "SEC003",
389
+ name: "XSS Vulnerability",
390
+ category: "security",
391
+ severity: "high",
392
+ description: "Unescaped user input rendered in HTML (innerHTML, dangerouslySetInnerHTML)",
393
+ why: "XSS lets attackers execute JavaScript in other users' browsers \u2014 steal sessions, redirect to phishing, deface UI.",
394
+ fix: "Use textContent instead of innerHTML. Sanitize with DOMPurify if HTML is required.",
395
+ tags: ["xss", "injection", "html"],
396
+ autoFixable: false,
397
+ cwe: "CWE-79"
398
+ },
399
+ SEC004: {
400
+ ruleId: "SEC004",
401
+ name: "Path Traversal Risk",
402
+ category: "security",
403
+ severity: "high",
404
+ description: "User input used in file path without sanitization",
405
+ why: "Path traversal (../../etc/passwd) lets attackers read any file on the server, including configs and secrets.",
406
+ fix: "Use path.resolve() and verify the result is within allowed directory.",
407
+ tags: ["path-traversal", "file-access"],
408
+ autoFixable: false,
409
+ cwe: "CWE-22"
410
+ },
411
+ SEC005: {
412
+ ruleId: "SEC005",
413
+ name: "SSRF Risk",
414
+ category: "security",
415
+ severity: "high",
416
+ description: "User-controlled URL passed to server-side fetch/request",
417
+ why: "SSRF lets attackers make your server request internal services, cloud metadata endpoints (169.254.169.254), or scan your network.",
418
+ fix: "Validate URL against allowlist of domains. Block private IPs.",
419
+ tags: ["ssrf", "network"],
420
+ autoFixable: false,
421
+ cwe: "CWE-918"
422
+ },
423
+ SEC006: {
424
+ ruleId: "SEC006",
425
+ name: "Prototype Pollution",
426
+ category: "security",
427
+ severity: "high",
428
+ description: "__proto__ or prototype[] manipulation detected",
429
+ why: "Prototype pollution can lead to property injection across all objects, causing auth bypass or RCE.",
430
+ fix: "Use Object.create(null) for lookup objects. Validate property names.",
431
+ tags: ["prototype-pollution", "injection"],
432
+ autoFixable: false,
433
+ cwe: "CWE-1321"
434
+ },
435
+ SEC007: {
436
+ ruleId: "SEC007",
437
+ name: "eval() Usage",
438
+ category: "security",
439
+ severity: "critical",
440
+ description: "eval() or new Function() with potentially dynamic input",
441
+ why: "eval() executes arbitrary code. If any input is user-controlled, it's equivalent to giving the attacker a shell.",
442
+ fix: "Use JSON.parse() for data. Use a sandboxed interpreter for expressions.",
443
+ tags: ["eval", "code-execution"],
444
+ autoFixable: false,
445
+ cwe: "CWE-95"
446
+ },
447
+ SEC008: {
448
+ ruleId: "SEC008",
449
+ name: "Mass Assignment",
450
+ category: "security",
451
+ severity: "high",
452
+ description: "Request body spread directly into database/object update",
453
+ why: `Mass assignment lets attackers set fields they shouldn't (isAdmin: true, role: "admin") by adding extra fields to requests.`,
454
+ fix: "Whitelist allowed fields explicitly. Use DTOs or pick() to select safe properties.",
455
+ tags: ["mass-assignment", "injection"],
456
+ autoFixable: false,
457
+ cwe: "CWE-915"
458
+ },
459
+ SEC009: {
460
+ ruleId: "SEC009",
461
+ name: "Insecure Randomness",
462
+ category: "security",
463
+ severity: "high",
464
+ description: "Math.random() used for security-sensitive values (tokens, IDs, secrets)",
465
+ why: "Math.random() is not cryptographically secure. Tokens generated with it can be predicted.",
466
+ fix: "Use crypto.randomUUID() or crypto.randomBytes() for security-sensitive values.",
467
+ tags: ["randomness", "cryptography"],
468
+ autoFixable: true,
469
+ cwe: "CWE-330"
470
+ },
471
+ SEC010: {
472
+ ruleId: "SEC010",
473
+ name: "Regex DoS Risk",
474
+ category: "security",
475
+ severity: "medium",
476
+ description: "Regular expression with catastrophic backtracking potential",
477
+ why: "A crafted input string can make a vulnerable regex take minutes/hours, causing denial of service.",
478
+ fix: "Use linear-time regex engines (re2) or simplify the pattern.",
479
+ tags: ["redos", "regex", "dos"],
480
+ autoFixable: false,
481
+ cwe: "CWE-1333"
482
+ },
483
+ SEC011: {
484
+ ruleId: "SEC011",
485
+ name: "Unfiltered DELETE/DROP",
486
+ category: "security",
487
+ severity: "critical",
488
+ description: "DELETE FROM or DROP TABLE without WHERE clause",
489
+ why: "Unfiltered destructive SQL operations can wipe entire tables in a single request.",
490
+ fix: "Always include WHERE clause. Add confirmation logic for destructive operations.",
491
+ tags: ["sql", "destructive"],
492
+ autoFixable: false,
493
+ cwe: "CWE-89"
494
+ },
495
+ // ═══════════════════════════════════════════════════════════════════════════
496
+ // HALLUCINATIONS (HAL) — AI-specific detection
497
+ // ═══════════════════════════════════════════════════════════════════════════
498
+ HAL001: {
499
+ ruleId: "HAL001",
500
+ name: "Fake npm Package",
501
+ category: "hallucinations",
502
+ severity: "critical",
503
+ description: "Import from a package that doesn't exist on npm",
504
+ why: "AI models frequently hallucinate package names. Installing a non-existent package can pull in a typosquatting malware package instead.",
505
+ fix: "Verify package exists on npmjs.com before importing. Check package.json dependencies.",
506
+ tags: ["hallucination", "npm", "supply-chain"],
507
+ autoFixable: false,
508
+ cwe: "CWE-829"
509
+ },
510
+ HAL002: {
511
+ ruleId: "HAL002",
512
+ name: "Fake API Method",
513
+ category: "hallucinations",
514
+ severity: "high",
515
+ description: "Call to a method that doesn't exist on the referenced library/object",
516
+ why: "AI models invent plausible-sounding method names. Code compiles in TS only with 'any' types, then crashes at runtime.",
517
+ fix: "Check the library's actual API documentation. Use TypeScript strict mode.",
518
+ tags: ["hallucination", "api", "method"],
519
+ autoFixable: false
520
+ },
521
+ HAL003: {
522
+ ruleId: "HAL003",
523
+ name: "Placeholder URL",
524
+ category: "hallucinations",
525
+ severity: "high",
526
+ description: "URL containing example.com, placeholder, or clearly fake domain",
527
+ why: "AI generates placeholder URLs that look real. Shipping these means features silently fail or hit wrong endpoints.",
528
+ fix: "Replace with actual API endpoint from environment variable.",
529
+ tags: ["hallucination", "url", "placeholder"],
530
+ autoFixable: false
531
+ },
532
+ HAL004: {
533
+ ruleId: "HAL004",
534
+ name: "Invented Config Option",
535
+ category: "hallucinations",
536
+ severity: "medium",
537
+ description: "Configuration key that doesn't exist in the library's schema",
538
+ why: "AI invents plausible config options. The option is silently ignored, and the expected behavior never activates.",
539
+ fix: "Check the library docs for valid configuration options.",
540
+ tags: ["hallucination", "config"],
541
+ autoFixable: false
542
+ },
543
+ HAL005: {
544
+ ruleId: "HAL005",
545
+ name: "Ghost Route",
546
+ category: "hallucinations",
547
+ severity: "high",
548
+ description: "Frontend references an API route that doesn't exist in the backend",
549
+ why: "AI generates matching frontend + backend code, but sometimes only one side. The missing route means the feature silently 404s.",
550
+ fix: "Verify route exists in your API. Run vibecheck truth to generate route manifest.",
551
+ tags: ["hallucination", "route", "api"],
552
+ autoFixable: false
553
+ },
554
+ HAL006: {
555
+ ruleId: "HAL006",
556
+ name: "Ghost Env Variable",
557
+ category: "hallucinations",
558
+ severity: "high",
559
+ description: "process.env.X references a variable not declared in any .env file",
560
+ why: "AI adds env var references without updating .env. The code silently uses undefined, causing subtle runtime bugs.",
561
+ fix: "Add the variable to .env.example and .env. Use required() wrapper for critical vars.",
562
+ tags: ["hallucination", "env", "config"],
563
+ autoFixable: true
564
+ },
565
+ // ═══════════════════════════════════════════════════════════════════════════
566
+ // FAKE FEATURES (FAKE) — Stubs, placeholders, empty implementations
567
+ // ═══════════════════════════════════════════════════════════════════════════
568
+ FAKE001: {
569
+ ruleId: "FAKE001",
570
+ name: "Empty Function Body",
571
+ category: "fake-features",
572
+ severity: "high",
573
+ description: "Function declared but body is empty or returns nothing",
574
+ why: "AI generates function signatures that look complete but have empty bodies. The feature appears to exist but does nothing.",
575
+ fix: "Implement the function body or remove if not needed.",
576
+ tags: ["stub", "empty", "fake-feature"],
577
+ autoFixable: false
578
+ },
579
+ FAKE002: {
580
+ ruleId: "FAKE002",
581
+ name: "Hardcoded Success",
582
+ category: "fake-features",
583
+ severity: "high",
584
+ description: "Function always returns true/success without doing actual work",
585
+ why: "Auth checks, validation, and payment processing that always return success means no actual protection is in place.",
586
+ fix: "Implement actual logic. If intentionally stubbed, add @stub annotation.",
587
+ tags: ["fake-success", "stub", "auth"],
588
+ autoFixable: false
589
+ },
590
+ FAKE003: {
591
+ ruleId: "FAKE003",
592
+ name: "Silent Error Swallowing",
593
+ category: "fake-features",
594
+ severity: "high",
595
+ description: "Empty catch block or catch that returns null/undefined",
596
+ why: "Silent failures hide bugs. Users see no error but the operation didn't complete. Data loss and corruption follow.",
597
+ fix: "Log the error and either re-throw, return error state, or handle gracefully.",
598
+ tags: ["error-handling", "silent-failure"],
599
+ autoFixable: false
600
+ },
601
+ FAKE004: {
602
+ ruleId: "FAKE004",
603
+ name: "Placeholder/TODO in Production",
604
+ category: "fake-features",
605
+ severity: "medium",
606
+ description: "TODO, FIXME, HACK, WIP, CHANGEME markers in production code",
607
+ why: "Placeholder markers indicate incomplete implementation. Shipping them means features are partially broken.",
608
+ fix: "Complete the implementation or create a tracked issue.",
609
+ tags: ["placeholder", "todo", "incomplete"],
610
+ autoFixable: false
611
+ },
612
+ FAKE005: {
613
+ ruleId: "FAKE005",
614
+ name: "Auth Bypass Pattern",
615
+ category: "fake-features",
616
+ severity: "critical",
617
+ description: "Authentication check that can be bypassed (skipAuth, isAdmin=true)",
618
+ why: "Auth bypasses in code are the most common security vulnerability. Any user can access protected resources.",
619
+ fix: "Remove the bypass. Use proper role-based access control.",
620
+ tags: ["auth", "bypass", "security"],
621
+ autoFixable: false,
622
+ cwe: "CWE-287"
623
+ },
624
+ FAKE006: {
625
+ ruleId: "FAKE006",
626
+ name: "Dangerous Default Value",
627
+ category: "fake-features",
628
+ severity: "high",
629
+ description: "Secret/credential env var with insecure fallback default",
630
+ why: "If the env var is missing, the insecure default is used in production \u2014 often a test key or empty string.",
631
+ fix: "Throw an error if required env vars are missing. Never provide fallback defaults for secrets.",
632
+ tags: ["default", "secret", "env"],
633
+ autoFixable: false
634
+ },
635
+ // ═══════════════════════════════════════════════════════════════════════════
636
+ // DEAD UI (UI)
637
+ // ═══════════════════════════════════════════════════════════════════════════
638
+ UI001: {
639
+ ruleId: "UI001",
640
+ name: "Dead Link",
641
+ category: "dead-ui",
642
+ severity: "high",
643
+ description: 'href="#" or javascript:void(0) \u2014 link goes nowhere',
644
+ why: "Dead links frustrate users and indicate unfinished UI. Screen readers announce them as real links.",
645
+ fix: "Replace with actual route, use <button> for actions, or remove.",
646
+ tags: ["ui", "accessibility", "dead-link"],
647
+ autoFixable: false
648
+ },
649
+ UI002: {
650
+ ruleId: "UI002",
651
+ name: "Noop Click Handler",
652
+ category: "dead-ui",
653
+ severity: "high",
654
+ description: "onClick={() => {}} \u2014 button/element does nothing when clicked",
655
+ why: "Users click the button expecting something to happen. Nothing does. This erodes trust in the entire application.",
656
+ fix: "Implement the handler or remove the onClick.",
657
+ tags: ["ui", "noop", "handler"],
658
+ autoFixable: false
659
+ },
660
+ UI003: {
661
+ ruleId: "UI003",
662
+ name: "Coming Soon in Production",
663
+ category: "dead-ui",
664
+ severity: "medium",
665
+ description: '"Coming soon", "under construction", "not available" in production UI',
666
+ why: "Users see a broken product. Competitors see an incomplete product. Neither is a good look.",
667
+ fix: "Hide the feature until ready, or implement it.",
668
+ tags: ["ui", "placeholder", "coming-soon"],
669
+ autoFixable: false
670
+ },
671
+ UI004: {
672
+ ruleId: "UI004",
673
+ name: "Disabled Without Reason",
674
+ category: "dead-ui",
675
+ severity: "medium",
676
+ description: "Disabled button/input without tooltip or explanation",
677
+ why: "Users don't know why they can't click. This is a UX and accessibility failure.",
678
+ fix: "Add tooltip, aria-label, or visible text explaining why it's disabled.",
679
+ tags: ["ui", "accessibility", "disabled"],
680
+ autoFixable: false
681
+ },
682
+ UI005: {
683
+ ruleId: "UI005",
684
+ name: "Raw Fetch in Component",
685
+ category: "dead-ui",
686
+ severity: "low",
687
+ description: 'Direct fetch("/api/...") call in a UI component',
688
+ why: "Scattered fetch calls are hard to maintain, don't handle loading/error states consistently, and bypass caching.",
689
+ fix: "Use React Query, SWR, tRPC, or a centralized API client.",
690
+ tags: ["ui", "fetch", "architecture"],
691
+ autoFixable: false
692
+ },
693
+ // ═══════════════════════════════════════════════════════════════════════════
694
+ // CODE QUALITY (QLT)
695
+ // ═══════════════════════════════════════════════════════════════════════════
696
+ QLT001: {
697
+ ruleId: "QLT001",
698
+ name: "Console.log in Production",
699
+ category: "code-quality",
700
+ severity: "medium",
701
+ description: "console.log/debug/trace statement in production code",
702
+ why: "Console logs leak information in browser devtools, slow down rendering, and indicate debug code left behind.",
703
+ fix: "Remove or replace with a proper logging library (winston, pino).",
704
+ tags: ["console", "debug", "quality"],
705
+ autoFixable: true
706
+ },
707
+ QLT002: {
708
+ ruleId: "QLT002",
709
+ name: "Debugger Statement",
710
+ category: "code-quality",
711
+ severity: "high",
712
+ description: "debugger statement left in production code",
713
+ why: "Debugger statements freeze the browser for any user with devtools open. In production, this is a showstopper.",
714
+ fix: "Remove the debugger statement.",
715
+ tags: ["debugger", "debug", "quality"],
716
+ autoFixable: true
717
+ },
718
+ QLT003: {
719
+ ruleId: "QLT003",
720
+ name: "Hardcoded Mock Data",
721
+ category: "code-quality",
722
+ severity: "high",
723
+ description: "Mock/fake/dummy data in production source files",
724
+ why: "Mock data makes features look functional but returns stale/fake information to real users.",
725
+ fix: "Connect to actual data source. Move mock data to test files.",
726
+ tags: ["mock", "fake-data", "quality"],
727
+ autoFixable: false
728
+ },
729
+ QLT004: {
730
+ ruleId: "QLT004",
731
+ name: "Unused Import",
732
+ category: "code-quality",
733
+ severity: "low",
734
+ description: "Import that is never used in the file",
735
+ why: "Unused imports increase bundle size and confuse other developers about what the file depends on.",
736
+ fix: "Remove the unused import.",
737
+ tags: ["unused", "import", "quality"],
738
+ autoFixable: true
739
+ },
740
+ QLT005: {
741
+ ruleId: "QLT005",
742
+ name: "Type Assertion Abuse",
743
+ category: "code-quality",
744
+ severity: "medium",
745
+ description: '"as any" or type assertion bypassing type safety',
746
+ why: "Type assertions silence the compiler but don't fix the underlying type issue. Runtime errors follow.",
747
+ fix: "Fix the type properly. Use type guards or proper generic types.",
748
+ tags: ["typescript", "type-safety", "quality"],
749
+ autoFixable: false
750
+ },
751
+ // ═══════════════════════════════════════════════════════════════════════════
752
+ // IMPORT GRAPH (IG) — Cross-file structural analysis
753
+ // ═══════════════════════════════════════════════════════════════════════════
754
+ IG001: {
755
+ ruleId: "IG001",
756
+ name: "Circular Dependency",
757
+ category: "import-graph",
758
+ severity: "high",
759
+ description: "Circular import chain detected between modules",
760
+ why: "Circular dependencies cause unpredictable initialization order, undefined values at import time, and make refactoring dangerous.",
761
+ fix: "Extract shared types/interfaces into a separate module. Break the cycle with dependency inversion.",
762
+ tags: ["imports", "circular", "architecture"],
763
+ autoFixable: false
764
+ },
765
+ IG002: {
766
+ ruleId: "IG002",
767
+ name: "Orphan Module",
768
+ category: "import-graph",
769
+ severity: "medium",
770
+ description: "File is never imported by any other module in the project",
771
+ why: "Orphan modules are dead code that increases bundle size, confuses developers, and accumulates tech debt.",
772
+ fix: "Delete the file if unused, or add it to your entry points if it should be included.",
773
+ tags: ["imports", "dead-code", "orphan"],
774
+ autoFixable: false
775
+ },
776
+ IG003: {
777
+ ruleId: "IG003",
778
+ name: "Ghost Route",
779
+ category: "import-graph",
780
+ severity: "high",
781
+ description: "Frontend fetches an API route that has no corresponding backend handler",
782
+ why: "Ghost routes cause silent 404s in production. Users see loading spinners that never resolve or cryptic error messages.",
783
+ fix: "Create the missing API route handler, or remove the frontend fetch call.",
784
+ tags: ["imports", "route", "api", "ghost"],
785
+ autoFixable: false
786
+ },
787
+ IG004: {
788
+ ruleId: "IG004",
789
+ name: "Ghost Env Variable",
790
+ category: "import-graph",
791
+ severity: "high",
792
+ description: "process.env.X referenced in code but not declared in any .env file",
793
+ why: "Missing env vars silently evaluate to undefined, causing features to break in production with no error message.",
794
+ fix: "Add the variable to .env and .env.example with a placeholder value.",
795
+ tags: ["env", "config", "ghost"],
796
+ autoFixable: true
797
+ },
798
+ IG005: {
799
+ ruleId: "IG005",
800
+ name: "Barrel Re-export Bloat",
801
+ category: "import-graph",
802
+ severity: "low",
803
+ description: "Barrel index.ts re-exports everything, defeating tree-shaking",
804
+ why: "Barrel files that re-export entire modules prevent bundlers from tree-shaking unused code, increasing bundle size.",
805
+ fix: "Use direct imports instead of barrel imports for large modules.",
806
+ tags: ["imports", "barrel", "performance"],
807
+ autoFixable: false
808
+ },
809
+ // ═══════════════════════════════════════════════════════════════════════════
810
+ // RUNTIME VERIFY (RV) — Static analysis for runtime issues
811
+ // ═══════════════════════════════════════════════════════════════════════════
812
+ RV001: {
813
+ ruleId: "RV001",
814
+ name: "Unhandled Promise",
815
+ category: "runtime-verify",
816
+ severity: "high",
817
+ description: "Promise chain without .catch() or try/catch around await",
818
+ why: "Unhandled promise rejections crash Node.js processes and leave users with white screens in browsers.",
819
+ fix: "Add .catch() to the promise chain, or wrap await in try/catch.",
820
+ tags: ["async", "promise", "error-handling"],
821
+ autoFixable: false,
822
+ cwe: "CWE-755"
823
+ },
824
+ RV002: {
825
+ ruleId: "RV002",
826
+ name: "Async Without Await",
827
+ category: "runtime-verify",
828
+ severity: "medium",
829
+ description: "Async function declared but never uses await",
830
+ why: "An async function without await still returns a Promise, which can mask bugs if the caller doesn't await it.",
831
+ fix: "Remove async keyword if not needed, or add await for async operations inside.",
832
+ tags: ["async", "promise", "quality"],
833
+ autoFixable: false
834
+ },
835
+ RV003: {
836
+ ruleId: "RV003",
837
+ name: "Dead Export",
838
+ category: "runtime-verify",
839
+ severity: "medium",
840
+ description: "Exported function/variable is never imported by any other file",
841
+ why: "Dead exports increase API surface area, confuse developers about the public API, and prevent safe refactoring.",
842
+ fix: "Remove the export keyword if the symbol is only used locally, or delete if truly unused.",
843
+ tags: ["exports", "dead-code", "quality"],
844
+ autoFixable: false
845
+ },
846
+ RV004: {
847
+ ruleId: "RV004",
848
+ name: "Floating Promise",
849
+ category: "runtime-verify",
850
+ severity: "high",
851
+ description: "Async function called without await, .then(), or void operator",
852
+ why: "Floating promises run in the background with no error handling. Failures are silently swallowed.",
853
+ fix: "Add await, .then()/.catch(), or prefix with void if intentionally fire-and-forget.",
854
+ tags: ["async", "promise", "fire-and-forget"],
855
+ autoFixable: false,
856
+ cwe: "CWE-755"
857
+ },
858
+ RV005: {
859
+ ruleId: "RV005",
860
+ name: "Race Condition Risk",
861
+ category: "runtime-verify",
862
+ severity: "medium",
863
+ description: "Shared mutable state accessed in async context without synchronization",
864
+ why: "Concurrent async operations reading/writing the same variable cause intermittent bugs that are extremely hard to reproduce.",
865
+ fix: "Use a mutex/lock, or restructure to avoid shared mutable state.",
866
+ tags: ["async", "race-condition", "concurrency"],
867
+ autoFixable: false
868
+ }
869
+ };
870
+ function getRuleOrDefault(ruleId) {
871
+ return RULE_CATALOG[ruleId] ?? {
872
+ ruleId,
873
+ name: ruleId,
874
+ category: "code-quality",
875
+ severity: "medium",
876
+ description: `Rule ${ruleId}`,
877
+ why: "See rule documentation.",
878
+ fix: "Review and fix the flagged code.",
879
+ tags: [],
880
+ autoFixable: false
881
+ };
882
+ }
883
+
884
+ // src/scanner/engines/credentials.ts
885
+ var PATTERNS = [
886
+ // ── Live payment keys (always flag) ──
887
+ {
888
+ name: "stripe-live-secret",
889
+ ruleId: "CRED001",
890
+ regex: /['"`](sk_live_[a-zA-Z0-9]{20,})['"`]/,
891
+ severity: "critical",
892
+ message: "Stripe live secret key hardcoded",
893
+ fix: "Use process.env.STRIPE_SECRET_KEY",
894
+ confidence: 99,
895
+ flagInTests: true,
896
+ skipInExamples: false,
897
+ cwe: "CWE-798"
898
+ },
899
+ {
900
+ name: "stripe-live-publishable",
901
+ ruleId: "CRED001",
902
+ regex: /['"`](pk_live_[a-zA-Z0-9]{20,})['"`]/,
903
+ severity: "high",
904
+ message: "Stripe live publishable key hardcoded",
905
+ fix: "Use process.env.NEXT_PUBLIC_STRIPE_KEY",
906
+ confidence: 95,
907
+ flagInTests: false,
908
+ skipInExamples: true
909
+ },
910
+ {
911
+ name: "stripe-test-secret",
912
+ ruleId: "CRED001",
913
+ regex: /['"`](sk_test_[a-zA-Z0-9]{20,})['"`]/,
914
+ severity: "high",
915
+ message: "Stripe test key hardcoded \u2014 use env var even for test keys",
916
+ fix: "Use process.env.STRIPE_TEST_KEY",
917
+ confidence: 90,
918
+ flagInTests: false,
919
+ skipInExamples: true
920
+ },
921
+ // ── AWS ──
922
+ {
923
+ name: "aws-access-key",
924
+ ruleId: "CRED001",
925
+ regex: /['"`](AKIA[0-9A-Z]{16})['"`]/,
926
+ severity: "critical",
927
+ message: "AWS Access Key ID hardcoded",
928
+ fix: "Use AWS SDK credential chain or AWS_ACCESS_KEY_ID env var",
929
+ confidence: 99,
930
+ flagInTests: true,
931
+ skipInExamples: false,
932
+ cwe: "CWE-798"
933
+ },
934
+ {
935
+ name: "aws-secret-key",
936
+ ruleId: "CRED001",
937
+ regex: /aws_secret_access_key\s*[:=]\s*['"`]([^'"`]{20,})['"`]/i,
938
+ severity: "critical",
939
+ message: "AWS Secret Access Key hardcoded",
940
+ fix: "Use AWS_SECRET_ACCESS_KEY environment variable",
941
+ confidence: 98,
942
+ flagInTests: true,
943
+ skipInExamples: false,
944
+ cwe: "CWE-798"
945
+ },
946
+ // ── AI providers ──
947
+ {
948
+ name: "openai-key",
949
+ ruleId: "CRED001",
950
+ regex: /['"`](sk-[a-zA-Z0-9]{32,})['"`]/,
951
+ severity: "critical",
952
+ message: "OpenAI API key hardcoded",
953
+ fix: "Use process.env.OPENAI_API_KEY",
954
+ confidence: 92,
955
+ flagInTests: true,
956
+ skipInExamples: false
957
+ },
958
+ {
959
+ name: "anthropic-key",
960
+ ruleId: "CRED001",
961
+ regex: /['"`](sk-ant-[a-zA-Z0-9-]{20,})['"`]/,
962
+ severity: "critical",
963
+ message: "Anthropic API key hardcoded",
964
+ fix: "Use process.env.ANTHROPIC_API_KEY",
965
+ confidence: 98,
966
+ flagInTests: true,
967
+ skipInExamples: false
968
+ },
969
+ // ── GitHub / Git ──
970
+ {
971
+ name: "github-token",
972
+ ruleId: "CRED001",
973
+ regex: /['"`](ghp_[a-zA-Z0-9]{36,})['"`]/,
974
+ severity: "critical",
975
+ message: "GitHub personal access token hardcoded",
976
+ fix: "Use process.env.GITHUB_TOKEN",
977
+ confidence: 99,
978
+ flagInTests: true,
979
+ skipInExamples: false
980
+ },
981
+ {
982
+ name: "github-oauth",
983
+ ruleId: "CRED001",
984
+ regex: /['"`](gho_[a-zA-Z0-9]{36,})['"`]/,
985
+ severity: "critical",
986
+ message: "GitHub OAuth token hardcoded",
987
+ fix: "Use process.env.GITHUB_OAUTH_TOKEN",
988
+ confidence: 99,
989
+ flagInTests: true,
990
+ skipInExamples: false
991
+ },
992
+ // ── Google / Firebase ──
993
+ {
994
+ name: "google-api-key",
995
+ ruleId: "CRED001",
996
+ regex: /['"`](AIza[0-9A-Za-z_-]{35})['"`]/,
997
+ severity: "high",
998
+ message: "Google API key hardcoded",
999
+ fix: "Use process.env.GOOGLE_API_KEY",
1000
+ confidence: 95,
1001
+ flagInTests: false,
1002
+ skipInExamples: true
1003
+ },
1004
+ // ── Generic secrets ──
1005
+ {
1006
+ name: "jwt-secret",
1007
+ ruleId: "CRED003",
1008
+ regex: /(?:jwt[_-]?secret|JWT_SECRET)\s*[:=]\s*['"`]([^'"`]{8,})['"`]/i,
1009
+ severity: "critical",
1010
+ message: "JWT signing secret hardcoded",
1011
+ fix: "Use process.env.JWT_SECRET",
1012
+ confidence: 90,
1013
+ flagInTests: false,
1014
+ skipInExamples: true,
1015
+ cwe: "CWE-798"
1016
+ },
1017
+ {
1018
+ name: "generic-password",
1019
+ ruleId: "CRED002",
1020
+ regex: /(?:password|passwd|pwd)\s*[:=]\s*['"`]([^'"`]{4,})['"`]/i,
1021
+ severity: "high",
1022
+ message: "Hardcoded password detected",
1023
+ fix: "Use environment variable or secrets manager",
1024
+ confidence: 75,
1025
+ flagInTests: false,
1026
+ skipInExamples: true,
1027
+ cwe: "CWE-798"
1028
+ },
1029
+ {
1030
+ name: "generic-secret",
1031
+ ruleId: "CRED002",
1032
+ regex: /(?:secret|api[_-]?key|auth[_-]?token)\s*[:=]\s*['"`]([^'"`]{8,})['"`]/i,
1033
+ severity: "high",
1034
+ message: "Hardcoded secret/token detected",
1035
+ fix: "Use environment variable",
1036
+ confidence: 70,
1037
+ flagInTests: false,
1038
+ skipInExamples: true
1039
+ },
1040
+ {
1041
+ name: "private-key-pem",
1042
+ ruleId: "CRED004",
1043
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
1044
+ severity: "critical",
1045
+ message: "Private key embedded in source code",
1046
+ fix: "Store key in file, reference via env var path",
1047
+ confidence: 99,
1048
+ flagInTests: true,
1049
+ skipInExamples: false,
1050
+ cwe: "CWE-321"
1051
+ },
1052
+ {
1053
+ name: "connection-string",
1054
+ ruleId: "CRED005",
1055
+ regex: /['"`](?:mongodb(?:\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\/\/[^'"`\s]{10,}['"`]/i,
1056
+ severity: "critical",
1057
+ message: "Database connection string with credentials in source",
1058
+ fix: "Use DATABASE_URL environment variable",
1059
+ confidence: 92,
1060
+ flagInTests: false,
1061
+ skipInExamples: true,
1062
+ cwe: "CWE-798"
1063
+ },
1064
+ // ── Additional providers ──
1065
+ {
1066
+ name: "slack-token",
1067
+ ruleId: "CRED001",
1068
+ regex: /['"`](xox[bpoas]-[0-9a-zA-Z-]{10,})['"`]/,
1069
+ severity: "critical",
1070
+ message: "Slack token hardcoded",
1071
+ fix: "Use process.env.SLACK_TOKEN",
1072
+ confidence: 97,
1073
+ flagInTests: true,
1074
+ skipInExamples: false
1075
+ },
1076
+ {
1077
+ name: "sendgrid-key",
1078
+ ruleId: "CRED001",
1079
+ regex: /['"`](SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43})['"`]/,
1080
+ severity: "critical",
1081
+ message: "SendGrid API key hardcoded",
1082
+ fix: "Use process.env.SENDGRID_API_KEY",
1083
+ confidence: 99,
1084
+ flagInTests: true,
1085
+ skipInExamples: false
1086
+ },
1087
+ {
1088
+ name: "twilio-key",
1089
+ ruleId: "CRED001",
1090
+ regex: /['"`](SK[a-f0-9]{32})['"`]/,
1091
+ severity: "high",
1092
+ message: "Twilio API key hardcoded",
1093
+ fix: "Use process.env.TWILIO_API_KEY",
1094
+ confidence: 85,
1095
+ flagInTests: false,
1096
+ skipInExamples: true
1097
+ },
1098
+ {
1099
+ name: "npm-token",
1100
+ ruleId: "CRED001",
1101
+ regex: /['"`](npm_[a-zA-Z0-9]{36,})['"`]/,
1102
+ severity: "critical",
1103
+ message: "npm auth token hardcoded",
1104
+ fix: "Use .npmrc with env var interpolation",
1105
+ confidence: 98,
1106
+ flagInTests: true,
1107
+ skipInExamples: false
1108
+ }
1109
+ ];
1110
+ var credentialsEngine = {
1111
+ name: "credentials",
1112
+ description: "Detects hardcoded API keys, secrets, passwords, tokens",
1113
+ async scan(files, options) {
1114
+ const findings = [];
1115
+ for (const [, file] of files) {
1116
+ const isTest = file.classification.category === "test";
1117
+ const isExample = /\.(example|sample|template)\b/.test(file.path);
1118
+ const isCritical = file.classification.isCriticalPath;
1119
+ for (let i = 0; i < file.lines.length; i++) {
1120
+ const line = file.lines[i];
1121
+ const trimmed = line.trim();
1122
+ if (trimmed.startsWith("//") && !trimmed.includes("=")) continue;
1123
+ if (trimmed.startsWith("*")) continue;
1124
+ for (const pattern of PATTERNS) {
1125
+ if (isTest && !pattern.flagInTests) continue;
1126
+ if (isExample && pattern.skipInExamples) continue;
1127
+ const match = line.match(pattern.regex);
1128
+ if (!match) continue;
1129
+ if (pattern.name === "generic-password") {
1130
+ if (/type\s|interface\s|placeholder|example/i.test(line)) continue;
1131
+ const val = match[1];
1132
+ if (!val || val.length < 6) continue;
1133
+ }
1134
+ if (pattern.name === "generic-secret") {
1135
+ const val = match[1];
1136
+ if (!val || /^[a-z_]+$/i.test(val)) continue;
1137
+ if (/placeholder|example|your[_-]/i.test(val)) continue;
1138
+ }
1139
+ const rule = getRuleOrDefault(pattern.ruleId);
1140
+ const severity = escalateSeverity(pattern.severity, isCritical);
1141
+ findings.push({
1142
+ id: `${pattern.ruleId}-${file.path}-${i + 1}`,
1143
+ ruleId: pattern.ruleId,
1144
+ engine: "credentials",
1145
+ category: "credentials",
1146
+ severity,
1147
+ confidence: pattern.confidence >= 90 ? "certain" : pattern.confidence >= 70 ? "likely" : "possible",
1148
+ confidenceScore: pattern.confidence,
1149
+ file: file.path,
1150
+ line: i + 1,
1151
+ column: match.index,
1152
+ code: trimmed,
1153
+ message: pattern.message,
1154
+ why: rule.why,
1155
+ fix: pattern.fix,
1156
+ autoFixable: false,
1157
+ tags: rule.tags,
1158
+ cwe: pattern.cwe,
1159
+ verified: false,
1160
+ _dedup: `${pattern.name}:${file.path}:${i + 1}`
1161
+ });
1162
+ }
1163
+ }
1164
+ }
1165
+ return findings;
1166
+ }
1167
+ };
1168
+
1169
+ // src/scanner/engines/security.ts
1170
+ var PATTERNS2 = [
1171
+ // ── Injection ──
1172
+ {
1173
+ name: "sql-injection-concat",
1174
+ ruleId: "SEC001",
1175
+ regex: /(?:query|execute|raw)\s*\(\s*[`'"](?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\s[^`'"]*\$\{/i,
1176
+ severity: "critical",
1177
+ message: "SQL injection: template literal in query",
1178
+ fix: 'Use parameterized queries: db.query("SELECT * FROM users WHERE id = $1", [userId])',
1179
+ confidence: 92,
1180
+ excludeInTests: true,
1181
+ cwe: "CWE-89"
1182
+ },
1183
+ {
1184
+ name: "sql-injection-plus",
1185
+ ruleId: "SEC001",
1186
+ regex: /(?:query|execute|raw)\s*\(\s*['"](?:SELECT|INSERT|UPDATE|DELETE)\s[^'"]*['"]\s*\+/i,
1187
+ severity: "critical",
1188
+ message: "SQL injection: string concatenation in query",
1189
+ fix: "Use parameterized queries. Never concatenate user input into SQL.",
1190
+ confidence: 90,
1191
+ excludeInTests: true,
1192
+ cwe: "CWE-89"
1193
+ },
1194
+ {
1195
+ name: "unfiltered-delete",
1196
+ ruleId: "SEC011",
1197
+ regex: /DELETE\s+FROM\s+\w+\s*;?\s*$/im,
1198
+ severity: "critical",
1199
+ message: "Unfiltered DELETE statement (no WHERE clause)",
1200
+ fix: "Add WHERE clause. Add confirmation logic for destructive operations.",
1201
+ confidence: 85,
1202
+ excludeInTests: true,
1203
+ cwe: "CWE-89"
1204
+ },
1205
+ {
1206
+ name: "drop-table",
1207
+ ruleId: "SEC011",
1208
+ regex: /DROP\s+TABLE/i,
1209
+ severity: "critical",
1210
+ message: "DROP TABLE statement in application code",
1211
+ fix: "Use migrations for schema changes. Never DROP in application logic.",
1212
+ confidence: 88,
1213
+ excludeInTests: true
1214
+ },
1215
+ // ── Command Injection ──
1216
+ {
1217
+ name: "exec-template",
1218
+ ruleId: "SEC002",
1219
+ regex: /(?:exec|execSync|spawn|spawnSync)\s*\(\s*`[^`]*\$\{/,
1220
+ severity: "critical",
1221
+ message: "Command injection: template literal in shell command",
1222
+ fix: 'Use execFile() with args array: execFile("cmd", [arg1, arg2])',
1223
+ confidence: 95,
1224
+ excludeInTests: true,
1225
+ cwe: "CWE-78"
1226
+ },
1227
+ {
1228
+ name: "exec-concat",
1229
+ ruleId: "SEC002",
1230
+ regex: /(?:exec|execSync)\s*\(\s*['"][^'"]*['"]\s*\+/,
1231
+ severity: "critical",
1232
+ message: "Command injection: concatenation in shell command",
1233
+ fix: "Use execFile() with args array.",
1234
+ confidence: 93,
1235
+ excludeInTests: true,
1236
+ cwe: "CWE-78"
1237
+ },
1238
+ {
1239
+ name: "child-process-exec",
1240
+ ruleId: "SEC002",
1241
+ regex: /child_process\s*\.\s*exec\s*\(/,
1242
+ severity: "high",
1243
+ message: "child_process.exec() can run arbitrary shell commands",
1244
+ fix: "Prefer execFile() or spawn() with explicit args array.",
1245
+ confidence: 80,
1246
+ excludeInTests: true,
1247
+ cwe: "CWE-78"
1248
+ },
1249
+ // ── XSS ──
1250
+ {
1251
+ name: "innerhtml-assignment",
1252
+ ruleId: "SEC003",
1253
+ regex: /\.innerHTML\s*=/,
1254
+ severity: "high",
1255
+ message: "innerHTML assignment \u2014 potential XSS if content is user-controlled",
1256
+ fix: "Use textContent for text. Use DOMPurify.sanitize() if HTML is needed.",
1257
+ confidence: 75,
1258
+ excludeInTests: true,
1259
+ cwe: "CWE-79"
1260
+ },
1261
+ {
1262
+ name: "dangerously-set-innerhtml",
1263
+ ruleId: "SEC003",
1264
+ regex: /dangerouslySetInnerHTML/,
1265
+ severity: "high",
1266
+ message: "dangerouslySetInnerHTML \u2014 ensure content is sanitized",
1267
+ fix: "Sanitize with DOMPurify before passing to dangerouslySetInnerHTML.",
1268
+ confidence: 70,
1269
+ excludeInTests: true,
1270
+ cwe: "CWE-79"
1271
+ },
1272
+ {
1273
+ name: "document-write",
1274
+ ruleId: "SEC003",
1275
+ regex: /document\.write\s*\(/,
1276
+ severity: "high",
1277
+ message: "document.write() can overwrite entire page with unescaped content",
1278
+ fix: "Use DOM manipulation methods instead.",
1279
+ confidence: 80,
1280
+ excludeInTests: true,
1281
+ cwe: "CWE-79"
1282
+ },
1283
+ // ── Path Traversal ──
1284
+ {
1285
+ name: "path-traversal-join",
1286
+ ruleId: "SEC004",
1287
+ regex: /(?:path\.join|path\.resolve)\s*\([^)]*(?:req\.|params\.|query\.|body\.)/,
1288
+ severity: "high",
1289
+ message: "User input in file path \u2014 potential path traversal",
1290
+ fix: "Validate resolved path is within allowed directory: resolvedPath.startsWith(allowedDir)",
1291
+ confidence: 82,
1292
+ excludeInTests: true,
1293
+ cwe: "CWE-22"
1294
+ },
1295
+ {
1296
+ name: "fs-read-user-input",
1297
+ ruleId: "SEC004",
1298
+ regex: /fs\.(?:readFile|readFileSync|createReadStream)\s*\([^)]*(?:req\.|params\.|query\.)/,
1299
+ severity: "high",
1300
+ message: "User input passed directly to file read operation",
1301
+ fix: "Sanitize path and verify it's within allowed directory.",
1302
+ confidence: 85,
1303
+ excludeInTests: true,
1304
+ cwe: "CWE-22"
1305
+ },
1306
+ // ── SSRF ──
1307
+ {
1308
+ name: "ssrf-fetch",
1309
+ ruleId: "SEC005",
1310
+ regex: /fetch\s*\(\s*(?:req\.|params\.|query\.|body\.|url|href)/,
1311
+ severity: "high",
1312
+ message: "User-controlled URL in server-side fetch \u2014 SSRF risk",
1313
+ fix: "Validate URL against allowlist. Block private IPs (10.x, 172.16-31.x, 192.168.x, 169.254.x).",
1314
+ confidence: 78,
1315
+ excludeInTests: true,
1316
+ cwe: "CWE-918"
1317
+ },
1318
+ {
1319
+ name: "ssrf-axios",
1320
+ ruleId: "SEC005",
1321
+ regex: /axios\s*(?:\.get|\.post|\.put|\.delete|\.request)\s*\(\s*(?:req\.|params\.|query\.|body\.)/,
1322
+ severity: "high",
1323
+ message: "User-controlled URL in axios request \u2014 SSRF risk",
1324
+ fix: "Validate URL against allowlist of allowed domains.",
1325
+ confidence: 78,
1326
+ excludeInTests: true,
1327
+ cwe: "CWE-918"
1328
+ },
1329
+ // ── Code Execution ──
1330
+ {
1331
+ name: "eval-usage",
1332
+ ruleId: "SEC007",
1333
+ regex: /\beval\s*\(/,
1334
+ severity: "critical",
1335
+ message: "eval() can execute arbitrary code",
1336
+ fix: "Use JSON.parse() for data. Use a sandboxed interpreter for expressions.",
1337
+ confidence: 88,
1338
+ excludeInTests: true,
1339
+ cwe: "CWE-95",
1340
+ validate: (line) => !/\/\/.*eval|['"]eval['"]/.test(line)
1341
+ },
1342
+ {
1343
+ name: "new-function",
1344
+ ruleId: "SEC007",
1345
+ regex: /\bnew\s+Function\s*\(/,
1346
+ severity: "critical",
1347
+ message: "new Function() can execute arbitrary code",
1348
+ fix: "Avoid dynamic code generation. Use a template engine or parser.",
1349
+ confidence: 90,
1350
+ excludeInTests: true,
1351
+ cwe: "CWE-95"
1352
+ },
1353
+ // ── Prototype Pollution ──
1354
+ {
1355
+ name: "proto-access",
1356
+ ruleId: "SEC006",
1357
+ regex: /__proto__|prototype\s*\[/,
1358
+ severity: "high",
1359
+ message: "Prototype pollution risk \u2014 __proto__ or prototype[] access",
1360
+ fix: "Use Object.create(null) for lookup objects. Validate property names.",
1361
+ confidence: 82,
1362
+ excludeInTests: true,
1363
+ cwe: "CWE-1321"
1364
+ },
1365
+ {
1366
+ name: "mass-assignment",
1367
+ ruleId: "SEC008",
1368
+ regex: /Object\.assign\s*\([^,]+,\s*req\.body/,
1369
+ severity: "high",
1370
+ message: "Mass assignment \u2014 spreading request body into object",
1371
+ fix: "Whitelist allowed fields: const { name, email } = req.body",
1372
+ confidence: 85,
1373
+ excludeInTests: true,
1374
+ cwe: "CWE-915"
1375
+ },
1376
+ {
1377
+ name: "spread-req-body",
1378
+ ruleId: "SEC008",
1379
+ regex: /\{\s*\.\.\.req\.body\s*\}/,
1380
+ severity: "high",
1381
+ message: "Mass assignment \u2014 spreading request body directly",
1382
+ fix: "Destructure only needed fields from req.body.",
1383
+ confidence: 88,
1384
+ excludeInTests: true,
1385
+ cwe: "CWE-915"
1386
+ },
1387
+ // ── Insecure Randomness ──
1388
+ {
1389
+ name: "math-random-token",
1390
+ ruleId: "SEC009",
1391
+ regex: /Math\.random\s*\(\s*\).*(?:token|id|key|secret|session|nonce|csrf)/i,
1392
+ severity: "high",
1393
+ message: "Math.random() used for security-sensitive value",
1394
+ fix: "Use crypto.randomUUID() or crypto.randomBytes()",
1395
+ confidence: 85,
1396
+ excludeInTests: true,
1397
+ cwe: "CWE-330"
1398
+ },
1399
+ {
1400
+ name: "math-random-hex",
1401
+ ruleId: "SEC009",
1402
+ regex: /Math\.random\(\)\.toString\((?:16|36)\)/,
1403
+ severity: "medium",
1404
+ message: "Math.random() for ID generation \u2014 not cryptographically secure",
1405
+ fix: "Use crypto.randomUUID() for unique IDs",
1406
+ confidence: 80,
1407
+ excludeInTests: true,
1408
+ cwe: "CWE-330"
1409
+ },
1410
+ // ── Destructive Operations ──
1411
+ {
1412
+ name: "rm-rf-root",
1413
+ ruleId: "SEC002",
1414
+ regex: /rm\s+-rf\s+\//,
1415
+ severity: "critical",
1416
+ message: "Recursive delete from root directory",
1417
+ fix: "Use explicit, validated path. Never delete from root.",
1418
+ confidence: 98,
1419
+ excludeInTests: false
1420
+ },
1421
+ {
1422
+ name: "rm-rf-wildcard",
1423
+ ruleId: "SEC002",
1424
+ regex: /rm\s+-rf\s+\*/,
1425
+ severity: "critical",
1426
+ message: "Recursive delete with wildcard",
1427
+ fix: "Use explicit, validated path.",
1428
+ confidence: 95,
1429
+ excludeInTests: false
1430
+ },
1431
+ // ── Dynamic Require/Import ──
1432
+ {
1433
+ name: "dynamic-require",
1434
+ ruleId: "SEC007",
1435
+ regex: /require\s*\(\s*[^'"][a-zA-Z_$]/,
1436
+ severity: "medium",
1437
+ message: "Dynamic require() \u2014 path injection risk",
1438
+ fix: "Use static imports. Validate path against allowlist if dynamic is needed.",
1439
+ confidence: 72,
1440
+ excludeInTests: true
1441
+ },
1442
+ {
1443
+ name: "dynamic-import",
1444
+ ruleId: "SEC007",
1445
+ regex: /import\s*\(\s*[^'"][a-zA-Z_$]/,
1446
+ severity: "medium",
1447
+ message: "Dynamic import() \u2014 path injection risk",
1448
+ fix: "Use static imports when possible. Validate path.",
1449
+ confidence: 68,
1450
+ excludeInTests: true
1451
+ },
1452
+ // ── File Deletion ──
1453
+ {
1454
+ name: "fs-unlink",
1455
+ ruleId: "SEC002",
1456
+ regex: /fs\.(?:unlink|rmdir|rm)(?:Sync)?\s*\(/,
1457
+ severity: "medium",
1458
+ message: "File/directory deletion \u2014 ensure path is validated",
1459
+ fix: "Validate path is within allowed directory before deletion.",
1460
+ confidence: 65,
1461
+ excludeInTests: true,
1462
+ validate: (line) => !line.includes("temp") && !line.includes("tmp")
1463
+ },
1464
+ // ── Weak Crypto ──
1465
+ {
1466
+ name: "md5-usage",
1467
+ ruleId: "SEC009",
1468
+ regex: /createHash\s*\(\s*['"`]md5['"`]\s*\)/,
1469
+ severity: "medium",
1470
+ message: "MD5 hash \u2014 cryptographically broken",
1471
+ fix: 'Use SHA-256 or better: crypto.createHash("sha256")',
1472
+ confidence: 90,
1473
+ excludeInTests: true,
1474
+ cwe: "CWE-328"
1475
+ },
1476
+ {
1477
+ name: "sha1-usage",
1478
+ ruleId: "SEC009",
1479
+ regex: /createHash\s*\(\s*['"`]sha1['"`]\s*\)/,
1480
+ severity: "medium",
1481
+ message: "SHA-1 hash \u2014 deprecated for security use",
1482
+ fix: 'Use SHA-256: crypto.createHash("sha256")',
1483
+ confidence: 85,
1484
+ excludeInTests: true,
1485
+ cwe: "CWE-328"
1486
+ },
1487
+ // ── CORS Misconfiguration ──
1488
+ {
1489
+ name: "cors-wildcard",
1490
+ ruleId: "SEC005",
1491
+ regex: /(?:Access-Control-Allow-Origin|origin)\s*[:=]\s*['"`]\*['"`]/,
1492
+ severity: "high",
1493
+ message: "CORS wildcard allows any origin",
1494
+ fix: "Restrict to specific allowed origins.",
1495
+ confidence: 80,
1496
+ excludeInTests: true
1497
+ },
1498
+ // ── NoSQL Injection ──
1499
+ {
1500
+ name: "nosql-injection",
1501
+ ruleId: "SEC001",
1502
+ regex: /\$(?:where|regex|gt|lt|ne|or|and)\s*[:=]\s*(?:req\.|params\.|query\.|body\.)/,
1503
+ severity: "high",
1504
+ message: "NoSQL injection \u2014 MongoDB operator from user input",
1505
+ fix: "Sanitize input. Use mongoose-sanitize or explicit field selection.",
1506
+ confidence: 82,
1507
+ excludeInTests: true,
1508
+ cwe: "CWE-943"
1509
+ }
1510
+ ];
1511
+ var securityEngine = {
1512
+ name: "security",
1513
+ description: "Detects injection, XSS, SSRF, prototype pollution, and security anti-patterns",
1514
+ async scan(files, options) {
1515
+ const findings = [];
1516
+ for (const [, file] of files) {
1517
+ const isTest = file.classification.category === "test";
1518
+ const isCritical = file.classification.isCriticalPath;
1519
+ for (let i = 0; i < file.lines.length; i++) {
1520
+ const line = file.lines[i];
1521
+ const trimmed = line.trim();
1522
+ if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
1523
+ for (const pattern of PATTERNS2) {
1524
+ if (isTest && pattern.excludeInTests) continue;
1525
+ const match = line.match(pattern.regex);
1526
+ if (!match) continue;
1527
+ if (pattern.validate && !pattern.validate(line, file.lines, i)) continue;
1528
+ const rule = getRuleOrDefault(pattern.ruleId);
1529
+ const severity = escalateSeverity(pattern.severity, isCritical);
1530
+ findings.push({
1531
+ id: `${pattern.ruleId}-${file.path}-${i + 1}`,
1532
+ ruleId: pattern.ruleId,
1533
+ engine: "security",
1534
+ category: "security",
1535
+ severity,
1536
+ confidence: pattern.confidence >= 90 ? "certain" : pattern.confidence >= 70 ? "likely" : "possible",
1537
+ confidenceScore: pattern.confidence,
1538
+ file: file.path,
1539
+ line: i + 1,
1540
+ column: match.index,
1541
+ code: trimmed,
1542
+ message: pattern.message,
1543
+ why: rule.why,
1544
+ fix: pattern.fix,
1545
+ autoFixable: false,
1546
+ tags: rule.tags,
1547
+ cwe: pattern.cwe,
1548
+ verified: false,
1549
+ _dedup: `${pattern.name}:${file.path}:${i + 1}`
1550
+ });
1551
+ }
1552
+ }
1553
+ }
1554
+ return findings;
1555
+ }
1556
+ };
1557
+
1558
+ // src/scanner/engines/fake-features.ts
1559
+ var PLACEHOLDER_PATTERNS = [
1560
+ // Match 'placeholder' when used as value, not HTML attribute
1561
+ /[=:]\s*['"]?placeholder['"]?(?:\s*[,;}\]]|$)/i,
1562
+ // Match 'stub' in function/method context
1563
+ /\bstub(?:bed|bing)?\s*(?:function|method|implementation|out)/i,
1564
+ // Match 'dummy' in data contexts
1565
+ /\bdummy[-_]?(?:data|value|user|id|token|response)\b/i,
1566
+ // Match 'fake' in data contexts (not test files)
1567
+ /\bfake[-_]?(?:data|response|user|api|endpoint)\b/i,
1568
+ // Hardcoded credential placeholders
1569
+ /\bhardcoded\s*(?:secret|password|key|token|credential)/i,
1570
+ // TODO with action verbs
1571
+ /\bTODO\b.*(?:implement|fix|complete|replace|remove)/i,
1572
+ // Work in progress markers
1573
+ /\bWIP\b(?:\s*[-:]\s|\s+)/i,
1574
+ // TBD markers
1575
+ /\bTBD\b(?:\s*[-:]\s|\s+)/i,
1576
+ // Not yet implemented
1577
+ /\bNYI\b/i,
1578
+ /\bcoming\s+soon\b/i,
1579
+ // CHANGEME / REPLACEME
1580
+ /\bCHANGE[-_]?ME\b/i,
1581
+ /\bREPLACE[-_]?ME\b/i
1582
+ ];
1583
+ var FAKE_SUCCESS_PATTERNS = [
1584
+ // Return true with nothing else in the function
1585
+ /^\s*return\s+true\s*;?\s*$/i,
1586
+ // Hardcoded success without checking
1587
+ /return\s*{\s*success:\s*true\s*}/i,
1588
+ // Always returning ok
1589
+ /return\s*{\s*status:\s*["']ok["']\s*}/i,
1590
+ // Mock/fake success comments
1591
+ /\/\/\s*(?:fake|mock|stub)\s*success/i
1592
+ ];
1593
+ var SILENT_FAILURE_PATTERNS = [
1594
+ // Completely empty catch blocks
1595
+ /catch\s*\(\s*(?:_|e|err|error)?\s*\)\s*{\s*}/,
1596
+ // Catch that just returns undefined/null
1597
+ /catch\s*\([^)]*\)\s*{\s*return(?:\s+(?:undefined|null))?\s*;?\s*}/,
1598
+ // Catch with only a comment
1599
+ /catch\s*\([^)]*\)\s*{\s*\/\/[^\n]*\s*}/,
1600
+ // Catch with only console.log (no actual handling)
1601
+ /catch\s*\([^)]*\)\s*{\s*console\.log\([^)]+\)\s*;?\s*}/
1602
+ ];
1603
+ var AUTH_BYPASS_PATTERNS = [
1604
+ // Hardcoded admin bypasses
1605
+ /(?:if|&&|\|\|)\s*\(\s*(?:true|1)\s*\)\s*.*(?:admin|auth)/i,
1606
+ // Skip/disable auth flags
1607
+ /\b(?:skip|disable|bypass)Auth(?:entication)?\s*[=:]\s*true\b/i,
1608
+ // Hardcoded isAdmin = true
1609
+ /\b(?:const|let|var)\s+isAdmin\s*=\s*true\b/i,
1610
+ // Auth bypass comments
1611
+ /\/\/\s*(?:bypass|skip|disable)\s*auth/i,
1612
+ // UI-only gating
1613
+ /\/\/\s*(?:ui|client)[-\s]*only\s*(?:check|gating|validation)/i,
1614
+ // Wildcard permissions
1615
+ /permissions?\s*[=:]\s*\[\s*['"]?\*['"]?\s*\]/i,
1616
+ // Always-true auth functions
1617
+ /(?:isAuth|isAdmin|hasPermission|canAccess)\s*[=:]\s*\(\)\s*=>\s*true/i
1618
+ ];
1619
+ var DANGEROUS_DEFAULT_PATTERNS = [
1620
+ // Secret env vars with insecure defaults
1621
+ /process\.env\.(?:\w*(?:SECRET|KEY|TOKEN|PASSWORD|CREDENTIAL)\w*)\s*\|\|\s*["'][^'"]{0,50}["']/i,
1622
+ // Database URLs with placeholder passwords
1623
+ /(?:DATABASE|DB|MONGO|POSTGRES|MYSQL|REDIS)_URL\s*[=:]\s*["'][^'"]*(?:password|changeme|replace)[^'"]*["']/i,
1624
+ // Placeholder markers in credentials
1625
+ /(?:api[_-]?key|secret|token|password)\s*[=:]\s*["'](?:CHANGEME|REPLACE_ME|YOUR_[A-Z_]+|xxx+|TODO)["']/i,
1626
+ // Auth endpoints pointing to localhost
1627
+ /(?:AUTH|BILLING|PAYMENT|WEBHOOK)_(?:URL|ENDPOINT)\s*[=:]\s*["']https?:\/\/localhost/i,
1628
+ // Empty string defaults for required secrets
1629
+ /(?:SECRET|API_KEY|TOKEN|PASSWORD)\s*[=:]\s*process\.env\.\w+\s*\|\|\s*["']\s*["']/i
1630
+ ];
1631
+ var EMPTY_FUNCTION_PATTERNS = [
1632
+ // async function with empty body
1633
+ /async\s+(?:function\s+\w+|(?:const|let)\s+\w+\s*=\s*async)\s*\([^)]*\)\s*(?::\s*\w+[<>\[\]]*\s*)?\s*{\s*}/,
1634
+ // function with only return undefined
1635
+ /function\s+\w+\s*\([^)]*\)\s*{\s*return\s*;?\s*}/,
1636
+ // arrow function with empty body
1637
+ /=>\s*{\s*}\s*[;,)]/
1638
+ ];
1639
+ function isCommentLine(line) {
1640
+ return /^\s*(?:\/\/|\/\*|\*|#|<!--|"""|''')/.test(line);
1641
+ }
1642
+ function isDocContext(filePath, line) {
1643
+ const lp = filePath.toLowerCase();
1644
+ if (/\.(md|mdx|rst|txt)$/.test(lp)) return true;
1645
+ if (lp.includes("/docs/") || lp.includes("/documentation/")) return true;
1646
+ if (lp.includes("/examples/") || lp.includes("/example/")) return true;
1647
+ if (/^\s*\*\s*@(?:example|see|param|returns)/.test(line)) return true;
1648
+ return false;
1649
+ }
1650
+ function isInErrorContext(lines, lineIndex) {
1651
+ const before = lines.slice(Math.max(0, lineIndex - 5), lineIndex).join("\n");
1652
+ return /catch|onError|fallback|error|exception/i.test(before);
1653
+ }
1654
+ function validatePlaceholder(line, filePath) {
1655
+ const ll = line.toLowerCase();
1656
+ if (/placeholder\s*[=:]/.test(ll) && !ll.includes("=")) return false;
1657
+ if (ll.includes("type ") || ll.includes("interface ")) return false;
1658
+ return true;
1659
+ }
1660
+ function validateFakeSuccess(line, filePath) {
1661
+ const ll = line.toLowerCase();
1662
+ if (ll.includes("expect(") || ll.includes("assert")) return false;
1663
+ if (filePath.includes(".test.") || filePath.includes(".spec.")) return false;
1664
+ return true;
1665
+ }
1666
+ var fakeFeaturesEngine = {
1667
+ name: "fake-features",
1668
+ description: "Detects stubs, fake success, silent failures, auth bypasses, placeholders",
1669
+ async scan(files, options) {
1670
+ const findings = [];
1671
+ for (const [, file] of files) {
1672
+ if (file.classification.category === "test" && !options.includeTests) continue;
1673
+ if (file.classification.category === "documentation") continue;
1674
+ const isCritical = file.classification.isCriticalPath;
1675
+ for (let i = 0; i < file.lines.length; i++) {
1676
+ const line = file.lines[i];
1677
+ const trimmed = line.trim();
1678
+ if (!isCommentLine(trimmed) || /\b(?:TODO|FIXME|WIP|TBD)\b/i.test(trimmed)) {
1679
+ if (!isDocContext(file.path, trimmed)) {
1680
+ for (const pattern of PLACEHOLDER_PATTERNS) {
1681
+ if (pattern.test(line) && validatePlaceholder(line, file.path)) {
1682
+ findings.push(makeFinding("FAKE004", file, i, trimmed, "placeholder", "medium", 65, isCritical));
1683
+ break;
1684
+ }
1685
+ }
1686
+ }
1687
+ }
1688
+ if (isCommentLine(trimmed)) continue;
1689
+ const inErrorCtx = isInErrorContext(file.lines, i);
1690
+ for (const pattern of FAKE_SUCCESS_PATTERNS) {
1691
+ if (pattern.test(line) && validateFakeSuccess(line, file.path)) {
1692
+ const sev = inErrorCtx ? "high" : "medium";
1693
+ findings.push(makeFinding("FAKE002", file, i, trimmed, "fake-success", sev, inErrorCtx ? 80 : 65, isCritical));
1694
+ break;
1695
+ }
1696
+ }
1697
+ for (const pattern of SILENT_FAILURE_PATTERNS) {
1698
+ if (pattern.test(line)) {
1699
+ if (line.toLowerCase().includes("finally")) continue;
1700
+ findings.push(makeFinding("FAKE003", file, i, trimmed, "silent-failure", "high", 85, isCritical));
1701
+ break;
1702
+ }
1703
+ }
1704
+ for (const pattern of AUTH_BYPASS_PATTERNS) {
1705
+ if (pattern.test(line)) {
1706
+ if (file.path.includes(".config.") || file.path.includes(".env")) continue;
1707
+ findings.push(makeFinding("FAKE005", file, i, trimmed, "auth-bypass", "critical", 88, isCritical));
1708
+ break;
1709
+ }
1710
+ }
1711
+ for (const pattern of DANGEROUS_DEFAULT_PATTERNS) {
1712
+ if (pattern.test(line)) {
1713
+ if (file.path.includes(".example") || file.path.includes(".template")) continue;
1714
+ if (file.path.includes(".env.sample")) continue;
1715
+ findings.push(makeFinding("FAKE006", file, i, trimmed, "dangerous-default", "high", 82, isCritical));
1716
+ break;
1717
+ }
1718
+ }
1719
+ for (const pattern of EMPTY_FUNCTION_PATTERNS) {
1720
+ if (pattern.test(line)) {
1721
+ if (/noop|passthrough|abstract|interface|override/i.test(line)) continue;
1722
+ findings.push(makeFinding("FAKE001", file, i, trimmed, "empty-function", "high", 75, isCritical));
1723
+ break;
1724
+ }
1725
+ }
1726
+ }
1727
+ }
1728
+ return findings;
1729
+ }
1730
+ };
1731
+ function makeFinding(ruleId, file, lineIndex, code, tag, severity, confidence, isCritical) {
1732
+ const rule = getRuleOrDefault(ruleId);
1733
+ const finalSeverity = escalateSeverity(severity, isCritical);
1734
+ return {
1735
+ id: `${ruleId}-${file.path}-${lineIndex + 1}`,
1736
+ ruleId,
1737
+ engine: "fake-features",
1738
+ category: "fake-features",
1739
+ severity: finalSeverity,
1740
+ confidence: confidence >= 85 ? "certain" : confidence >= 65 ? "likely" : "possible",
1741
+ confidenceScore: confidence,
1742
+ file: file.path,
1743
+ line: lineIndex + 1,
1744
+ code,
1745
+ message: rule.description,
1746
+ why: rule.why,
1747
+ fix: rule.fix,
1748
+ autoFixable: rule.autoFixable,
1749
+ tags: [...rule.tags, tag],
1750
+ verified: false,
1751
+ _dedup: `${ruleId}:${file.path}:${lineIndex + 1}`
1752
+ };
1753
+ }
1754
+
1755
+ // src/scanner/engines/hallucinations.ts
1756
+ var PATTERNS3 = [
1757
+ // ── Fake npm packages (known hallucinated package names) ──
1758
+ {
1759
+ name: "fake-npm-react-auth",
1760
+ ruleId: "HAL001",
1761
+ regex: /from\s+['"](?:react-auth-provider|react-secure-auth|next-auth-helpers|react-auth-kit-v2)['"]|require\s*\(\s*['"](?:react-auth-provider|react-secure-auth|next-auth-helpers)['"]/,
1762
+ severity: "critical",
1763
+ message: "Import from frequently hallucinated npm package",
1764
+ fix: "Verify package exists on npmjs.com. AI often invents auth/utility packages.",
1765
+ confidence: 88,
1766
+ excludeInTests: false
1767
+ },
1768
+ {
1769
+ name: "fake-npm-util",
1770
+ ruleId: "HAL001",
1771
+ regex: /from\s+['"](?:string-utils-pro|array-helpers-ts|object-deep-merge|config-loader-pro|api-response-handler)['"]|require\s*\(\s*['"](?:string-utils-pro|array-helpers-ts|object-deep-merge)['"]/,
1772
+ severity: "critical",
1773
+ message: "Import from frequently hallucinated npm package",
1774
+ fix: "Check npmjs.com. These utility package names are commonly invented by AI.",
1775
+ confidence: 85,
1776
+ excludeInTests: false
1777
+ },
1778
+ // ── Placeholder URLs ──
1779
+ {
1780
+ name: "placeholder-url",
1781
+ ruleId: "HAL003",
1782
+ regex: /['"`]https?:\/\/(?:api\.)?example\.com[^'"`]*['"`]/i,
1783
+ severity: "high",
1784
+ message: "Placeholder URL (example.com) in source code",
1785
+ fix: "Replace with actual API endpoint from environment variable.",
1786
+ confidence: 92,
1787
+ excludeInTests: true
1788
+ },
1789
+ {
1790
+ name: "placeholder-url-yoursite",
1791
+ ruleId: "HAL003",
1792
+ regex: /['"`]https?:\/\/(?:your-?(?:site|app|domain|api)|my-?(?:api|app|site))\.[a-z]+[^'"`]*['"`]/i,
1793
+ severity: "high",
1794
+ message: "Placeholder URL (your-site/my-api) \u2014 AI-generated placeholder",
1795
+ fix: "Replace with actual URL from environment variable.",
1796
+ confidence: 95,
1797
+ excludeInTests: true
1798
+ },
1799
+ {
1800
+ name: "placeholder-url-todo",
1801
+ ruleId: "HAL003",
1802
+ regex: /['"`]https?:\/\/(?:todo|fixme|placeholder|changeme)\.[a-z]+[^'"`]*['"`]/i,
1803
+ severity: "high",
1804
+ message: "Placeholder URL with marker keyword",
1805
+ fix: "Replace with actual URL.",
1806
+ confidence: 98,
1807
+ excludeInTests: true
1808
+ },
1809
+ {
1810
+ name: "localhost-in-prod",
1811
+ ruleId: "HAL003",
1812
+ regex: /(?:API_URL|BASE_URL|BACKEND_URL|SERVER_URL)\s*[=:]\s*['"`]https?:\/\/localhost/i,
1813
+ severity: "high",
1814
+ message: "Localhost URL in production configuration",
1815
+ fix: "Use environment variable: process.env.API_URL",
1816
+ confidence: 80,
1817
+ excludeInTests: true,
1818
+ validate: (line, file) => !file.path.includes(".env") && !file.path.includes(".local")
1819
+ },
1820
+ // ── Invented API methods ──
1821
+ {
1822
+ name: "prisma-hallucinated",
1823
+ ruleId: "HAL002",
1824
+ regex: /prisma\.\w+\.(?:getAll|fetchAll|search|getOne|fetchOne|removeAll|updateAll|createOrUpdate|findFirst|getById)\s*\(/,
1825
+ severity: "high",
1826
+ message: "Possible hallucinated Prisma method \u2014 verify against Prisma docs",
1827
+ fix: "Prisma uses findMany, findUnique, create, update, delete, upsert. Check docs.",
1828
+ confidence: 65,
1829
+ excludeInTests: true,
1830
+ validate: (line) => {
1831
+ if (line.includes("findFirst")) return false;
1832
+ return true;
1833
+ }
1834
+ },
1835
+ {
1836
+ name: "express-hallucinated",
1837
+ ruleId: "HAL002",
1838
+ regex: /(?:app|router)\.(?:handle|serve|mount|listen)\s*\(\s*['"]\/\w/,
1839
+ severity: "medium",
1840
+ message: "Possible hallucinated Express method \u2014 verify against Express docs",
1841
+ fix: "Express uses .get(), .post(), .put(), .delete(), .use(), .all(). Check docs.",
1842
+ confidence: 60,
1843
+ excludeInTests: true
1844
+ },
1845
+ // ── Invented config options ──
1846
+ {
1847
+ name: "next-config-hallucinated",
1848
+ ruleId: "HAL004",
1849
+ regex: /(?:next\.config|nextConfig)\s*(?:=|\.)\s*\{[^}]*(?:enableSSR|enableCSR|autoOptimize|smartBundling|lazyHydration|autoCache)/,
1850
+ severity: "medium",
1851
+ message: "Possible hallucinated Next.js config option",
1852
+ fix: "Check next.config.js docs for valid options.",
1853
+ confidence: 70,
1854
+ excludeInTests: true
1855
+ },
1856
+ // ── Fake test data in production ──
1857
+ {
1858
+ name: "test-email-prod",
1859
+ ruleId: "HAL003",
1860
+ regex: /['"`](?:test|user|admin|john|jane|foo|bar)@(?:test|example|fake|dummy|placeholder)\.\w+['"`]/i,
1861
+ severity: "medium",
1862
+ message: "Test/fake email address in production code",
1863
+ fix: "Replace with actual user data or remove.",
1864
+ confidence: 78,
1865
+ excludeInTests: true
1866
+ },
1867
+ {
1868
+ name: "test-phone-prod",
1869
+ ruleId: "HAL003",
1870
+ regex: /['"`]\+?1?[-.\s]?\(?(?:555|000|123)\)?[-.\s]?\d{3}[-.\s]?\d{4}['"`]/,
1871
+ severity: "medium",
1872
+ message: "Fake phone number (555/000/123 prefix) in production code",
1873
+ fix: "Replace with actual data or remove.",
1874
+ confidence: 82,
1875
+ excludeInTests: true
1876
+ },
1877
+ // ── Lorem ipsum ──
1878
+ {
1879
+ name: "lorem-ipsum",
1880
+ ruleId: "HAL003",
1881
+ regex: /lorem\s+ipsum/i,
1882
+ severity: "medium",
1883
+ message: "Lorem ipsum placeholder text in production code",
1884
+ fix: "Replace with actual content.",
1885
+ confidence: 95,
1886
+ excludeInTests: true
1887
+ }
1888
+ ];
1889
+ var hallucinationsEngine = {
1890
+ name: "hallucinations",
1891
+ description: "Detects AI-hallucinated code: fake packages, invented methods, placeholder URLs",
1892
+ async scan(files, options) {
1893
+ const findings = [];
1894
+ for (const [, file] of files) {
1895
+ const isTest = file.classification.category === "test";
1896
+ const isCritical = file.classification.isCriticalPath;
1897
+ for (let i = 0; i < file.lines.length; i++) {
1898
+ const line = file.lines[i];
1899
+ const trimmed = line.trim();
1900
+ if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
1901
+ for (const pattern of PATTERNS3) {
1902
+ if (isTest && pattern.excludeInTests) continue;
1903
+ const match = line.match(pattern.regex);
1904
+ if (!match) continue;
1905
+ if (pattern.validate && !pattern.validate(line, file)) continue;
1906
+ const rule = getRuleOrDefault(pattern.ruleId);
1907
+ const severity = escalateSeverity(pattern.severity, isCritical);
1908
+ findings.push({
1909
+ id: `${pattern.ruleId}-${file.path}-${i + 1}`,
1910
+ ruleId: pattern.ruleId,
1911
+ engine: "hallucinations",
1912
+ category: "hallucinations",
1913
+ severity,
1914
+ confidence: pattern.confidence >= 85 ? "certain" : pattern.confidence >= 65 ? "likely" : "possible",
1915
+ confidenceScore: pattern.confidence,
1916
+ file: file.path,
1917
+ line: i + 1,
1918
+ column: match.index,
1919
+ code: trimmed,
1920
+ message: pattern.message,
1921
+ why: rule.why,
1922
+ fix: pattern.fix,
1923
+ autoFixable: false,
1924
+ tags: [...rule.tags],
1925
+ verified: false,
1926
+ _dedup: `${pattern.name}:${file.path}:${i + 1}`
1927
+ });
1928
+ }
1929
+ }
1930
+ }
1931
+ return findings;
1932
+ }
1933
+ };
1934
+
1935
+ // src/scanner/engines/dead-ui.ts
1936
+ var LEGIT_HASH_LINK = [/aria-controls=/i, /role=["']tab["']/i, /role=["']button["']/i, /data-toggle=/i, /data-bs-toggle=/i, /onClick\s*=\s*\{[^}]+\}/, /tabIndex=/i];
1937
+ var LEGIT_NOOP = [/disabled/i, /aria-disabled/i, /loading/i, /pending/i, /isLoading/i, /stopPropagation/i, /preventDefault/i];
1938
+ var LEGIT_COMING_SOON = [/changelog/i, /readme/i, /roadmap/i, /\/\//, /\/\*/, /translation/i, /i18n/i, /t\(['"`]/i];
1939
+ var LEGIT_DISABLED = [/aria-disabled/i, /isDisabled/i, /isLoading/i, /disabled=\{/i, /&&\s*disabled/i, /\?\s*disabled/i];
1940
+ var LEGIT_FETCH = [/useQuery/i, /useMutation/i, /useSWR/i, /createApi/i, /trpc/i, /\.server\./i, /route\.ts$/i, /api\/.*\.ts$/i];
1941
+ function hasContext(line, patterns) {
1942
+ return patterns.some((p) => p.test(line));
1943
+ }
1944
+ var deadUIEngine = {
1945
+ name: "dead-ui",
1946
+ description: "Detects dead links, noop handlers, coming soon UI, disabled without reason",
1947
+ async scan(files, options) {
1948
+ const findings = [];
1949
+ for (const [, file] of files) {
1950
+ if (!isUIFile(file.path)) continue;
1951
+ if (file.classification.category === "test" && !options.includeTests) continue;
1952
+ if (file.classification.category === "documentation") continue;
1953
+ const isApiFile = /\/api\/|\/routes\/|\.server\./.test(file.path.toLowerCase());
1954
+ for (let i = 0; i < file.lines.length; i++) {
1955
+ const line = file.lines[i];
1956
+ const trimmed = line.trim();
1957
+ if (/href=["']#["']|href=["']javascript:void\(0\)["']/g.test(line)) {
1958
+ if (!hasContext(line, LEGIT_HASH_LINK)) {
1959
+ findings.push(make("UI001", file, i, trimmed, "high", 88));
1960
+ }
1961
+ }
1962
+ if (/onClick\s*=\s*\{\s*\(\)\s*=>\s*\{\s*\}\s*\}/.test(line)) {
1963
+ if (!hasContext(line, LEGIT_NOOP)) {
1964
+ findings.push(make("UI002", file, i, trimmed, "high", 90));
1965
+ }
1966
+ }
1967
+ if (/coming\s+soon|under\s+construction|not\s+available|work\s+in\s+progress/gi.test(line)) {
1968
+ if (!hasContext(line, LEGIT_COMING_SOON) && !trimmed.startsWith("//") && !trimmed.startsWith("*")) {
1969
+ findings.push(make("UI003", file, i, trimmed, "medium", 72));
1970
+ }
1971
+ }
1972
+ if (line.includes("disabled") && !/tooltip|aria-label|aria-describedby|title|reason/i.test(line)) {
1973
+ if (!hasContext(line, LEGIT_DISABLED) && !/disabled=\{[^}]*\}/.test(line)) {
1974
+ findings.push(make("UI004", file, i, trimmed, "medium", 68));
1975
+ }
1976
+ }
1977
+ if (!isApiFile && /fetch\s*\(\s*["'`]\/api\//.test(line)) {
1978
+ if (!hasContext(line, LEGIT_FETCH)) {
1979
+ findings.push(make("UI005", file, i, trimmed, "low", 60));
1980
+ }
1981
+ }
1982
+ }
1983
+ }
1984
+ return findings;
1985
+ }
1986
+ };
1987
+ function make(ruleId, file, lineIdx, code, severity, confidence) {
1988
+ const rule = getRuleOrDefault(ruleId);
1989
+ return {
1990
+ id: `${ruleId}-${file.path}-${lineIdx + 1}`,
1991
+ ruleId,
1992
+ engine: "dead-ui",
1993
+ category: "dead-ui",
1994
+ severity,
1995
+ confidence: confidence >= 85 ? "certain" : confidence >= 65 ? "likely" : "possible",
1996
+ confidenceScore: confidence,
1997
+ file: file.path,
1998
+ line: lineIdx + 1,
1999
+ code,
2000
+ message: rule.description,
2001
+ why: rule.why,
2002
+ fix: rule.fix,
2003
+ autoFixable: false,
2004
+ tags: rule.tags,
2005
+ verified: false,
2006
+ _dedup: `${ruleId}:${file.path}:${lineIdx + 1}`
2007
+ };
2008
+ }
2009
+
2010
+ // src/scanner/engines/code-quality.ts
2011
+ var PATTERNS4 = [
2012
+ // ── Debug Code ──
2013
+ {
2014
+ name: "console-log",
2015
+ ruleId: "QLT001",
2016
+ regex: /\bconsole\.log\s*\(/,
2017
+ severity: "medium",
2018
+ message: "console.log() in production code",
2019
+ fix: "Remove or replace with proper logger (winston, pino)",
2020
+ confidence: 92,
2021
+ excludeInTests: true,
2022
+ autoFixable: true
2023
+ },
2024
+ {
2025
+ name: "console-debug",
2026
+ ruleId: "QLT001",
2027
+ regex: /\bconsole\.debug\s*\(/,
2028
+ severity: "medium",
2029
+ message: "console.debug() in production code",
2030
+ fix: "Remove or replace with proper logger",
2031
+ confidence: 92,
2032
+ excludeInTests: true,
2033
+ autoFixable: true
2034
+ },
2035
+ {
2036
+ name: "console-trace",
2037
+ ruleId: "QLT001",
2038
+ regex: /\bconsole\.trace\s*\(/,
2039
+ severity: "medium",
2040
+ message: "console.trace() in production code",
2041
+ fix: "Remove or use proper debugging tools",
2042
+ confidence: 92,
2043
+ excludeInTests: true,
2044
+ autoFixable: true
2045
+ },
2046
+ {
2047
+ name: "debugger",
2048
+ ruleId: "QLT002",
2049
+ regex: /\bdebugger\b\s*;?/,
2050
+ severity: "high",
2051
+ message: "debugger statement left in code",
2052
+ fix: "Remove the debugger statement",
2053
+ confidence: 98,
2054
+ excludeInTests: true,
2055
+ autoFixable: true
2056
+ },
2057
+ {
2058
+ name: "alert-call",
2059
+ ruleId: "QLT001",
2060
+ regex: /\balert\s*\(\s*['"`]/,
2061
+ severity: "medium",
2062
+ message: "alert() call in production code",
2063
+ fix: "Use a proper notification/toast component",
2064
+ confidence: 85,
2065
+ excludeInTests: true,
2066
+ autoFixable: false
2067
+ },
2068
+ // ── Type Safety ──
2069
+ {
2070
+ name: "as-any",
2071
+ ruleId: "QLT005",
2072
+ regex: /\bas\s+any\b/,
2073
+ severity: "medium",
2074
+ message: '"as any" bypasses TypeScript safety',
2075
+ fix: "Fix the type properly. Use type guards or generics.",
2076
+ confidence: 88,
2077
+ excludeInTests: true,
2078
+ autoFixable: false
2079
+ },
2080
+ {
2081
+ name: "ts-ignore",
2082
+ ruleId: "QLT005",
2083
+ regex: /@ts-ignore(?!\s*\()/,
2084
+ severity: "medium",
2085
+ message: "@ts-ignore suppresses type errors without explanation",
2086
+ fix: "Use @ts-expect-error with explanation, or fix the type.",
2087
+ confidence: 90,
2088
+ excludeInTests: true,
2089
+ autoFixable: false
2090
+ },
2091
+ {
2092
+ name: "any-type-annotation",
2093
+ ruleId: "QLT005",
2094
+ regex: /:\s*any\b(?!\s*\[\])/,
2095
+ severity: "low",
2096
+ message: 'Explicit "any" type annotation',
2097
+ fix: "Use a proper type. Use unknown for truly dynamic values.",
2098
+ confidence: 75,
2099
+ excludeInTests: true,
2100
+ autoFixable: false
2101
+ },
2102
+ // ── Mock Data in Production ──
2103
+ {
2104
+ name: "mock-data-array",
2105
+ ruleId: "QLT003",
2106
+ regex: /(?:const|let|var)\s+(?:mock|fake|dummy|sample|test)(?:Data|Users?|Items?|Products?|Orders?)\s*[=:]/i,
2107
+ severity: "high",
2108
+ message: "Mock/fake data variable in production code",
2109
+ fix: "Connect to actual data source. Move mock data to test files.",
2110
+ confidence: 80,
2111
+ excludeInTests: true,
2112
+ autoFixable: false
2113
+ },
2114
+ {
2115
+ name: "hardcoded-user",
2116
+ ruleId: "QLT003",
2117
+ regex: /(?:name|email|username)\s*:\s*['"`](?:John\s*Doe|Jane\s*Doe|Test\s*User|Admin\s*User|user@example\.com)['"`]/i,
2118
+ severity: "high",
2119
+ message: "Hardcoded fake user data in production code",
2120
+ fix: "Replace with actual user data from database/auth.",
2121
+ confidence: 85,
2122
+ excludeInTests: true,
2123
+ autoFixable: false
2124
+ },
2125
+ {
2126
+ name: "hardcoded-price",
2127
+ ruleId: "QLT003",
2128
+ regex: /(?:price|amount|cost)\s*:\s*(?:9\.99|19\.99|29\.99|49\.99|99\.99|0\.00)\b/i,
2129
+ severity: "medium",
2130
+ message: "Hardcoded price/amount \u2014 likely mock data",
2131
+ fix: "Fetch pricing from database/config.",
2132
+ confidence: 72,
2133
+ excludeInTests: true,
2134
+ autoFixable: false
2135
+ },
2136
+ // ── Error Handling Smells ──
2137
+ {
2138
+ name: "throw-string",
2139
+ ruleId: "QLT001",
2140
+ regex: /throw\s+['"`][^'"`]+['"`]/,
2141
+ severity: "medium",
2142
+ message: "Throwing a string instead of an Error object",
2143
+ fix: 'Throw an Error: throw new Error("message")',
2144
+ confidence: 92,
2145
+ excludeInTests: true,
2146
+ autoFixable: true
2147
+ },
2148
+ {
2149
+ name: "promise-no-catch",
2150
+ ruleId: "QLT001",
2151
+ regex: /\.then\s*\([^)]*\)\s*(?:$|;|\n)(?!.*\.catch)/,
2152
+ severity: "medium",
2153
+ message: "Promise chain without .catch() \u2014 unhandled rejection risk",
2154
+ fix: "Add .catch() handler or use async/await with try/catch.",
2155
+ confidence: 68,
2156
+ excludeInTests: true,
2157
+ autoFixable: false
2158
+ },
2159
+ // ── Dead Code Indicators ──
2160
+ {
2161
+ name: "unreachable-code",
2162
+ ruleId: "QLT004",
2163
+ regex: /^\s*(?:const|let|var|function|class|return|throw)\b.*$/,
2164
+ severity: "low",
2165
+ message: "Potentially unreachable code after return/throw",
2166
+ fix: "Remove dead code.",
2167
+ confidence: 45,
2168
+ excludeInTests: true,
2169
+ autoFixable: true
2170
+ },
2171
+ // ── Performance ──
2172
+ {
2173
+ name: "sync-fs-in-handler",
2174
+ ruleId: "QLT001",
2175
+ regex: /(?:readFileSync|writeFileSync|existsSync|readdirSync|statSync)\s*\(/,
2176
+ severity: "medium",
2177
+ message: "Synchronous file operation \u2014 blocks event loop",
2178
+ fix: "Use async version: fs.promises.readFile() or fs.readFile()",
2179
+ confidence: 70,
2180
+ excludeInTests: true,
2181
+ autoFixable: false
2182
+ }
2183
+ ];
2184
+ var codeQualityEngine = {
2185
+ name: "code-quality",
2186
+ description: "Detects debug code, type safety issues, mock data, and quality anti-patterns",
2187
+ async scan(files, options) {
2188
+ const findings = [];
2189
+ for (const [, file] of files) {
2190
+ const isTest = file.classification.category === "test";
2191
+ for (let i = 0; i < file.lines.length; i++) {
2192
+ const line = file.lines[i];
2193
+ const trimmed = line.trim();
2194
+ if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) continue;
2195
+ for (const pattern of PATTERNS4) {
2196
+ if (isTest && pattern.excludeInTests) continue;
2197
+ const match = line.match(pattern.regex);
2198
+ if (!match) continue;
2199
+ if (pattern.name === "unreachable-code") {
2200
+ if (i === 0 || !/\b(?:return|throw)\b/.test(file.lines[i - 1])) continue;
2201
+ }
2202
+ const rule = getRuleOrDefault(pattern.ruleId);
2203
+ findings.push({
2204
+ id: `${pattern.ruleId}-${file.path}-${i + 1}`,
2205
+ ruleId: pattern.ruleId,
2206
+ engine: "code-quality",
2207
+ category: "code-quality",
2208
+ severity: pattern.severity,
2209
+ confidence: pattern.confidence >= 85 ? "certain" : pattern.confidence >= 65 ? "likely" : "possible",
2210
+ confidenceScore: pattern.confidence,
2211
+ file: file.path,
2212
+ line: i + 1,
2213
+ column: match.index,
2214
+ code: trimmed,
2215
+ message: pattern.message,
2216
+ why: rule.why,
2217
+ fix: pattern.fix,
2218
+ autoFixable: pattern.autoFixable,
2219
+ tags: rule.tags,
2220
+ verified: false,
2221
+ _dedup: `${pattern.name}:${file.path}:${i + 1}`
2222
+ });
2223
+ }
2224
+ }
2225
+ }
2226
+ return findings;
2227
+ }
2228
+ };
2229
+
2230
+ // src/scanner/engines/import-graph.ts
2231
+ var import_fs = require("fs");
2232
+ var import_path2 = require("path");
2233
+ function extractImports(file) {
2234
+ const edges = [];
2235
+ const importRegex = /(?:import\s+(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
2236
+ for (let i = 0; i < file.lines.length; i++) {
2237
+ const line = file.lines[i];
2238
+ let match;
2239
+ importRegex.lastIndex = 0;
2240
+ const lineRegex = /(?:import\s+(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
2241
+ while ((match = lineRegex.exec(line)) !== null) {
2242
+ const specifier = match[1] || match[2] || match[3];
2243
+ if (specifier) {
2244
+ edges.push({
2245
+ from: file.path,
2246
+ to: specifier,
2247
+ specifier,
2248
+ line: i + 1
2249
+ });
2250
+ }
2251
+ }
2252
+ }
2253
+ return edges;
2254
+ }
2255
+ function resolveImport(from, specifier, fileMap) {
2256
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) return null;
2257
+ const fromDir = (0, import_path2.dirname)(from);
2258
+ const resolved = (0, import_path2.join)(fromDir, specifier).replace(/\\/g, "/");
2259
+ const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
2260
+ for (const ext of extensions) {
2261
+ const candidate = resolved + ext;
2262
+ const normalized = candidate.replace(/\\/g, "/");
2263
+ if (fileMap.has(normalized)) return normalized;
2264
+ const winNormalized = candidate.replace(/\//g, "\\");
2265
+ if (fileMap.has(winNormalized)) return winNormalized;
2266
+ }
2267
+ for (const ext of extensions) {
2268
+ const candidate = (resolved.startsWith("./") ? resolved.slice(2) : resolved) + ext;
2269
+ if (fileMap.has(candidate)) return candidate;
2270
+ }
2271
+ return null;
2272
+ }
2273
+ function findCircularDeps(adjacency) {
2274
+ const cycles = [];
2275
+ const visited = /* @__PURE__ */ new Set();
2276
+ const inStack = /* @__PURE__ */ new Set();
2277
+ const path = [];
2278
+ function dfs(node) {
2279
+ if (inStack.has(node)) {
2280
+ const cycleStart = path.indexOf(node);
2281
+ if (cycleStart >= 0) {
2282
+ cycles.push(path.slice(cycleStart).concat(node));
2283
+ }
2284
+ return;
2285
+ }
2286
+ if (visited.has(node)) return;
2287
+ visited.add(node);
2288
+ inStack.add(node);
2289
+ path.push(node);
2290
+ for (const neighbor of adjacency.get(node) ?? []) {
2291
+ dfs(neighbor);
2292
+ }
2293
+ path.pop();
2294
+ inStack.delete(node);
2295
+ }
2296
+ for (const node of adjacency.keys()) {
2297
+ dfs(node);
2298
+ }
2299
+ return cycles;
2300
+ }
2301
+ function extractFetchRoutes(file) {
2302
+ const routes = [];
2303
+ const fetchRegex = /(?:fetch|axios\.(?:get|post|put|patch|delete)|\.get|\.post|\.put|\.patch|\.delete)\s*\(\s*[`'"](\/api\/[^`'"]*)[`'"]/g;
2304
+ for (let i = 0; i < file.lines.length; i++) {
2305
+ let match;
2306
+ fetchRegex.lastIndex = 0;
2307
+ while ((match = fetchRegex.exec(file.lines[i])) !== null) {
2308
+ routes.push({ route: match[1], line: i + 1 });
2309
+ }
2310
+ }
2311
+ return routes;
2312
+ }
2313
+ function extractRouteHandlers(file) {
2314
+ const handlers = [];
2315
+ const handlerRegex = /(?:app|router|server)\s*\.\s*(?:get|post|put|patch|delete|all)\s*\(\s*[`'"](\/api\/[^`'"]*)[`'"]/g;
2316
+ for (const line of file.lines) {
2317
+ let match;
2318
+ handlerRegex.lastIndex = 0;
2319
+ while ((match = handlerRegex.exec(line)) !== null) {
2320
+ handlers.push(match[1]);
2321
+ }
2322
+ }
2323
+ const normalized = file.path.replace(/\\/g, "/");
2324
+ if (normalized.includes("/api/") || normalized.includes("/routes/")) {
2325
+ const apiMatch = normalized.match(/(?:pages|app)(\/api\/[^.]+)/);
2326
+ if (apiMatch) {
2327
+ handlers.push(apiMatch[1].replace(/\/route$/, "").replace(/\/index$/, ""));
2328
+ }
2329
+ }
2330
+ return handlers;
2331
+ }
2332
+ function extractEnvRefs(file) {
2333
+ const refs = [];
2334
+ const envRegex = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
2335
+ for (let i = 0; i < file.lines.length; i++) {
2336
+ const line = file.lines[i];
2337
+ if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;
2338
+ let match;
2339
+ envRegex.lastIndex = 0;
2340
+ while ((match = envRegex.exec(line)) !== null) {
2341
+ refs.push({ name: match[1], line: i + 1 });
2342
+ }
2343
+ }
2344
+ return refs;
2345
+ }
2346
+ function loadEnvVars(projectRoot) {
2347
+ const vars = /* @__PURE__ */ new Set();
2348
+ const envFiles = [".env", ".env.local", ".env.development", ".env.production", ".env.example"];
2349
+ for (const envFile of envFiles) {
2350
+ const envPath = (0, import_path2.join)(projectRoot, envFile);
2351
+ try {
2352
+ if ((0, import_fs.existsSync)(envPath)) {
2353
+ const content = (0, import_fs.readFileSync)(envPath, "utf-8");
2354
+ for (const line of content.split("\n")) {
2355
+ const match = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=/);
2356
+ if (match) vars.add(match[1]);
2357
+ }
2358
+ }
2359
+ } catch {
2360
+ }
2361
+ }
2362
+ const builtins = [
2363
+ "NODE_ENV",
2364
+ "PORT",
2365
+ "HOME",
2366
+ "PATH",
2367
+ "PWD",
2368
+ "USER",
2369
+ "SHELL",
2370
+ "TERM",
2371
+ "LANG",
2372
+ "CI",
2373
+ "GITHUB_ACTIONS",
2374
+ "VERCEL",
2375
+ "NETLIFY",
2376
+ "HEROKU",
2377
+ "npm_package_version"
2378
+ ];
2379
+ for (const b of builtins) vars.add(b);
2380
+ return vars;
2381
+ }
2382
+ function isBarrelFile(file) {
2383
+ if (!file.path.endsWith("index.ts") && !file.path.endsWith("index.js")) {
2384
+ return { isBarrel: false, reExportCount: 0 };
2385
+ }
2386
+ let reExportCount = 0;
2387
+ for (const line of file.lines) {
2388
+ if (/^export\s+\*\s+from\s+/.test(line.trim()) || /^export\s+\{[^}]+\}\s+from\s+/.test(line.trim())) {
2389
+ reExportCount++;
2390
+ }
2391
+ }
2392
+ return { isBarrel: reExportCount >= 5, reExportCount };
2393
+ }
2394
+ var importGraphEngine = {
2395
+ name: "import-graph",
2396
+ description: "Cross-file import graph analysis: circular deps, orphans, ghost routes, ghost env vars",
2397
+ async scan(files, options) {
2398
+ const findings = [];
2399
+ const adjacency = /* @__PURE__ */ new Map();
2400
+ const allImported = /* @__PURE__ */ new Set();
2401
+ const allEdges = [];
2402
+ for (const [path, file] of files) {
2403
+ const edges = extractImports(file);
2404
+ const resolved = [];
2405
+ for (const edge of edges) {
2406
+ const target = resolveImport(path, edge.specifier, files);
2407
+ if (target) {
2408
+ resolved.push(target);
2409
+ allImported.add(target);
2410
+ allEdges.push({ ...edge, to: target });
2411
+ }
2412
+ }
2413
+ adjacency.set(path, resolved);
2414
+ }
2415
+ const cycles = findCircularDeps(adjacency);
2416
+ const reportedCycles = /* @__PURE__ */ new Set();
2417
+ for (const cycle of cycles) {
2418
+ const cycleKey = [...cycle].sort().join("\u2192");
2419
+ if (reportedCycles.has(cycleKey)) continue;
2420
+ reportedCycles.add(cycleKey);
2421
+ const file = files.get(cycle[0]);
2422
+ if (!file) continue;
2423
+ const rule = getRuleOrDefault("IG001");
2424
+ const cyclePath = cycle.map((p) => p.split("/").pop()).join(" \u2192 ");
2425
+ findings.push({
2426
+ id: `IG001-${cycle[0]}-cycle`,
2427
+ ruleId: "IG001",
2428
+ engine: "import-graph",
2429
+ category: "import-graph",
2430
+ severity: "high",
2431
+ confidence: "certain",
2432
+ confidenceScore: 95,
2433
+ file: cycle[0],
2434
+ line: 1,
2435
+ code: `Circular: ${cyclePath}`,
2436
+ message: `Circular dependency chain: ${cyclePath}`,
2437
+ why: rule.why,
2438
+ fix: rule.fix,
2439
+ autoFixable: false,
2440
+ tags: rule.tags,
2441
+ verified: false,
2442
+ _dedup: `IG001:${cycleKey}`
2443
+ });
2444
+ }
2445
+ const entryPatterns = /(?:index|main|app|server|cli|entry)\.[tj]sx?$/;
2446
+ for (const [path, file] of files) {
2447
+ if (entryPatterns.test(path)) continue;
2448
+ if (file.classification.category === "test") continue;
2449
+ if (file.classification.category === "config") continue;
2450
+ if (path.includes(".config.")) continue;
2451
+ if (path.includes(".d.ts")) continue;
2452
+ if (!allImported.has(path)) {
2453
+ const rule = getRuleOrDefault("IG002");
2454
+ findings.push({
2455
+ id: `IG002-${path}`,
2456
+ ruleId: "IG002",
2457
+ engine: "import-graph",
2458
+ category: "import-graph",
2459
+ severity: "medium",
2460
+ confidence: "likely",
2461
+ confidenceScore: 75,
2462
+ file: path,
2463
+ line: 1,
2464
+ code: path,
2465
+ message: `Orphan module: ${path.split(/[/\\]/).pop()} is never imported`,
2466
+ why: rule.why,
2467
+ fix: rule.fix,
2468
+ autoFixable: false,
2469
+ tags: rule.tags,
2470
+ verified: false,
2471
+ _dedup: `IG002:${path}`
2472
+ });
2473
+ }
2474
+ }
2475
+ const allFetchedRoutes = [];
2476
+ const allHandlerRoutes = /* @__PURE__ */ new Set();
2477
+ for (const [, file] of files) {
2478
+ const fetched = extractFetchRoutes(file);
2479
+ for (const f of fetched) {
2480
+ allFetchedRoutes.push({ ...f, file: file.path });
2481
+ }
2482
+ const handlers = extractRouteHandlers(file);
2483
+ for (const h of handlers) {
2484
+ allHandlerRoutes.add(h);
2485
+ }
2486
+ }
2487
+ for (const fetchedRoute of allFetchedRoutes) {
2488
+ const normalized = fetchedRoute.route.replace(/\/$/g, "").replace(/\/:[^/]+/g, "/[param]");
2489
+ const hasHandler = [...allHandlerRoutes].some((handler) => {
2490
+ const normHandler = handler.replace(/\/$/g, "").replace(/\/:[^/]+/g, "/[param]");
2491
+ return normHandler === normalized || normalized.startsWith(normHandler);
2492
+ });
2493
+ if (!hasHandler && allHandlerRoutes.size > 0) {
2494
+ const rule = getRuleOrDefault("IG003");
2495
+ findings.push({
2496
+ id: `IG003-${fetchedRoute.file}-${fetchedRoute.line}`,
2497
+ ruleId: "IG003",
2498
+ engine: "import-graph",
2499
+ category: "import-graph",
2500
+ severity: "high",
2501
+ confidence: "likely",
2502
+ confidenceScore: 80,
2503
+ file: fetchedRoute.file,
2504
+ line: fetchedRoute.line,
2505
+ code: `fetch("${fetchedRoute.route}")`,
2506
+ message: `Ghost route: ${fetchedRoute.route} has no matching backend handler`,
2507
+ why: rule.why,
2508
+ fix: rule.fix,
2509
+ autoFixable: false,
2510
+ tags: rule.tags,
2511
+ verified: false,
2512
+ _dedup: `IG003:${fetchedRoute.file}:${fetchedRoute.route}`
2513
+ });
2514
+ }
2515
+ }
2516
+ const declaredEnvVars = loadEnvVars(options.projectRoot);
2517
+ const allEnvRefs = [];
2518
+ for (const [, file] of files) {
2519
+ const refs = extractEnvRefs(file);
2520
+ for (const ref of refs) {
2521
+ allEnvRefs.push({ ...ref, file: file.path });
2522
+ }
2523
+ }
2524
+ const reportedEnvVars = /* @__PURE__ */ new Set();
2525
+ for (const ref of allEnvRefs) {
2526
+ if (declaredEnvVars.has(ref.name)) continue;
2527
+ if (reportedEnvVars.has(ref.name)) continue;
2528
+ reportedEnvVars.add(ref.name);
2529
+ const rule = getRuleOrDefault("IG004");
2530
+ findings.push({
2531
+ id: `IG004-${ref.file}-${ref.line}`,
2532
+ ruleId: "IG004",
2533
+ engine: "import-graph",
2534
+ category: "import-graph",
2535
+ severity: "high",
2536
+ confidence: "likely",
2537
+ confidenceScore: 82,
2538
+ file: ref.file,
2539
+ line: ref.line,
2540
+ code: `process.env.${ref.name}`,
2541
+ message: `Ghost env variable: ${ref.name} not found in any .env file`,
2542
+ why: rule.why,
2543
+ fix: rule.fix,
2544
+ autoFixable: true,
2545
+ tags: rule.tags,
2546
+ verified: false,
2547
+ _dedup: `IG004:${ref.name}`
2548
+ });
2549
+ }
2550
+ for (const [path, file] of files) {
2551
+ const { isBarrel, reExportCount } = isBarrelFile(file);
2552
+ if (isBarrel) {
2553
+ const rule = getRuleOrDefault("IG005");
2554
+ findings.push({
2555
+ id: `IG005-${path}`,
2556
+ ruleId: "IG005",
2557
+ engine: "import-graph",
2558
+ category: "import-graph",
2559
+ severity: "low",
2560
+ confidence: "likely",
2561
+ confidenceScore: 78,
2562
+ file: path,
2563
+ line: 1,
2564
+ code: `${reExportCount} re-exports from index file`,
2565
+ message: `Barrel file with ${reExportCount} re-exports may defeat tree-shaking`,
2566
+ why: rule.why,
2567
+ fix: rule.fix,
2568
+ autoFixable: false,
2569
+ tags: rule.tags,
2570
+ verified: false,
2571
+ _dedup: `IG005:${path}`
2572
+ });
2573
+ }
2574
+ }
2575
+ return findings;
2576
+ }
2577
+ };
2578
+
2579
+ // src/scanner/engines/runtime-verify.ts
2580
+ function extractExports(file) {
2581
+ const exports2 = [];
2582
+ const exportRegex = /export\s+(?:async\s+)?(?:function|const|let|var|class|enum|type|interface)\s+([A-Za-z_$][A-Za-z0-9_$]*)/;
2583
+ for (let i = 0; i < file.lines.length; i++) {
2584
+ const line = file.lines[i];
2585
+ const match = line.match(exportRegex);
2586
+ if (match) {
2587
+ exports2.push({ name: match[1], line: i + 1 });
2588
+ }
2589
+ const namedExport = line.match(/export\s+\{([^}]+)\}/);
2590
+ if (namedExport) {
2591
+ const names = namedExport[1].split(",").map((n) => n.trim().split(/\s+as\s+/)[0].trim());
2592
+ for (const name of names) {
2593
+ if (name && /^[A-Za-z_$]/.test(name)) {
2594
+ exports2.push({ name, line: i + 1 });
2595
+ }
2596
+ }
2597
+ }
2598
+ }
2599
+ return exports2;
2600
+ }
2601
+ function extractImportedSymbols(file) {
2602
+ const symbols = /* @__PURE__ */ new Set();
2603
+ const importRegex = /import\s+\{([^}]+)\}\s+from/g;
2604
+ const defaultImportRegex = /import\s+([A-Za-z_$][A-Za-z0-9_$]*)\s+from/g;
2605
+ for (const line of file.lines) {
2606
+ let match;
2607
+ importRegex.lastIndex = 0;
2608
+ while ((match = importRegex.exec(line)) !== null) {
2609
+ const names = match[1].split(",").map((n) => {
2610
+ const parts = n.trim().split(/\s+as\s+/);
2611
+ return parts[0].trim();
2612
+ });
2613
+ for (const name of names) {
2614
+ if (name) symbols.add(name);
2615
+ }
2616
+ }
2617
+ defaultImportRegex.lastIndex = 0;
2618
+ while ((match = defaultImportRegex.exec(line)) !== null) {
2619
+ symbols.add(match[1]);
2620
+ }
2621
+ }
2622
+ return symbols;
2623
+ }
2624
+ function findAsyncFunctions(file) {
2625
+ const functions = [];
2626
+ const asyncDeclRegex = /(?:export\s+)?async\s+function\s+([A-Za-z_$][A-Za-z0-9_$]*)/;
2627
+ const asyncArrowRegex = /(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*async/;
2628
+ for (let i = 0; i < file.lines.length; i++) {
2629
+ const line = file.lines[i];
2630
+ const declMatch = line.match(asyncDeclRegex);
2631
+ const arrowMatch = line.match(asyncArrowRegex);
2632
+ const match = declMatch || arrowMatch;
2633
+ if (match) {
2634
+ const name = match[1];
2635
+ const startLine = i + 1;
2636
+ let braceCount = 0;
2637
+ let hasAwait = false;
2638
+ let endLine = startLine;
2639
+ let started = false;
2640
+ for (let j = i; j < file.lines.length; j++) {
2641
+ const fLine = file.lines[j];
2642
+ for (const ch of fLine) {
2643
+ if (ch === "{") {
2644
+ braceCount++;
2645
+ started = true;
2646
+ }
2647
+ if (ch === "}") braceCount--;
2648
+ }
2649
+ if (/\bawait\b/.test(fLine) && j > i) {
2650
+ hasAwait = true;
2651
+ }
2652
+ if (started && braceCount === 0) {
2653
+ endLine = j + 1;
2654
+ break;
2655
+ }
2656
+ }
2657
+ if (/\bawait\b/.test(line) && line.indexOf("await") > line.indexOf("async")) {
2658
+ hasAwait = true;
2659
+ }
2660
+ functions.push({ name, startLine, hasAwait, endLine });
2661
+ }
2662
+ }
2663
+ return functions;
2664
+ }
2665
+ function isInsideTryCatch(lines, lineIndex) {
2666
+ let braceDepth = 0;
2667
+ for (let i = lineIndex; i >= 0; i--) {
2668
+ const line = lines[i];
2669
+ for (let j = line.length - 1; j >= 0; j--) {
2670
+ if (line[j] === "}") braceDepth++;
2671
+ if (line[j] === "{") braceDepth--;
2672
+ }
2673
+ if (braceDepth < 0 && /\btry\b/.test(lines[i])) return true;
2674
+ if (braceDepth < 0 && /\bcatch\b/.test(lines[i])) return true;
2675
+ }
2676
+ return false;
2677
+ }
2678
+ var runtimeVerifyEngine = {
2679
+ name: "runtime-verify",
2680
+ description: "Static analysis for runtime issues: unhandled promises, dead exports, async bugs",
2681
+ async scan(files, options) {
2682
+ const findings = [];
2683
+ const allImportedSymbols = /* @__PURE__ */ new Set();
2684
+ for (const [, file] of files) {
2685
+ const symbols = extractImportedSymbols(file);
2686
+ for (const s of symbols) allImportedSymbols.add(s);
2687
+ }
2688
+ for (const [path, file] of files) {
2689
+ if (![".ts", ".tsx", ".js", ".jsx"].includes(file.ext)) continue;
2690
+ const asyncFunctions = findAsyncFunctions(file);
2691
+ for (let i = 0; i < file.lines.length; i++) {
2692
+ const line = file.lines[i];
2693
+ const trimmed = line.trim();
2694
+ if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
2695
+ if (/\.then\s*\(/.test(trimmed) && !/.catch\s*\(/.test(trimmed)) {
2696
+ const nextLines = file.lines.slice(i + 1, i + 4).join(" ");
2697
+ if (!/.catch\s*\(/.test(nextLines)) {
2698
+ const rule = getRuleOrDefault("RV001");
2699
+ findings.push({
2700
+ id: `RV001-${path}-${i + 1}`,
2701
+ ruleId: "RV001",
2702
+ engine: "runtime-verify",
2703
+ category: "runtime-verify",
2704
+ severity: "high",
2705
+ confidence: "likely",
2706
+ confidenceScore: 82,
2707
+ file: path,
2708
+ line: i + 1,
2709
+ code: trimmed,
2710
+ message: "Promise .then() without .catch() \u2014 rejection will be unhandled",
2711
+ why: rule.why,
2712
+ fix: rule.fix,
2713
+ autoFixable: false,
2714
+ tags: rule.tags,
2715
+ verified: false,
2716
+ _dedup: `RV001:${path}:${i + 1}`
2717
+ });
2718
+ }
2719
+ }
2720
+ if (/\bawait\b/.test(trimmed) && !isInsideTryCatch(file.lines, i)) {
2721
+ const rule = getRuleOrDefault("RV001");
2722
+ findings.push({
2723
+ id: `RV001-await-${path}-${i + 1}`,
2724
+ ruleId: "RV001",
2725
+ engine: "runtime-verify",
2726
+ category: "runtime-verify",
2727
+ severity: "medium",
2728
+ confidence: "possible",
2729
+ confidenceScore: 65,
2730
+ file: path,
2731
+ line: i + 1,
2732
+ code: trimmed,
2733
+ message: "await without surrounding try/catch \u2014 rejection may be unhandled",
2734
+ why: rule.why,
2735
+ fix: rule.fix,
2736
+ autoFixable: false,
2737
+ tags: rule.tags,
2738
+ verified: false,
2739
+ _dedup: `RV001-await:${path}:${i + 1}`
2740
+ });
2741
+ }
2742
+ }
2743
+ for (const fn of asyncFunctions) {
2744
+ if (!fn.hasAwait && fn.endLine - fn.startLine > 1) {
2745
+ const rule = getRuleOrDefault("RV002");
2746
+ findings.push({
2747
+ id: `RV002-${path}-${fn.startLine}`,
2748
+ ruleId: "RV002",
2749
+ engine: "runtime-verify",
2750
+ category: "runtime-verify",
2751
+ severity: "medium",
2752
+ confidence: "likely",
2753
+ confidenceScore: 80,
2754
+ file: path,
2755
+ line: fn.startLine,
2756
+ code: file.lines[fn.startLine - 1]?.trim() ?? "",
2757
+ message: `Async function '${fn.name}' never uses await`,
2758
+ why: rule.why,
2759
+ fix: rule.fix,
2760
+ autoFixable: false,
2761
+ tags: rule.tags,
2762
+ verified: false,
2763
+ _dedup: `RV002:${path}:${fn.startLine}`
2764
+ });
2765
+ }
2766
+ }
2767
+ const exports2 = extractExports(file);
2768
+ const isEntry = /(?:index|main|app|server|cli)\.[tj]sx?$/.test(path);
2769
+ if (!isEntry) {
2770
+ for (const exp of exports2) {
2771
+ if (!allImportedSymbols.has(exp.name)) {
2772
+ const rule = getRuleOrDefault("RV003");
2773
+ findings.push({
2774
+ id: `RV003-${path}-${exp.line}`,
2775
+ ruleId: "RV003",
2776
+ engine: "runtime-verify",
2777
+ category: "runtime-verify",
2778
+ severity: "medium",
2779
+ confidence: "possible",
2780
+ confidenceScore: 68,
2781
+ file: path,
2782
+ line: exp.line,
2783
+ code: file.lines[exp.line - 1]?.trim() ?? "",
2784
+ message: `Dead export: '${exp.name}' is exported but never imported`,
2785
+ why: rule.why,
2786
+ fix: rule.fix,
2787
+ autoFixable: false,
2788
+ tags: rule.tags,
2789
+ verified: false,
2790
+ _dedup: `RV003:${path}:${exp.name}`
2791
+ });
2792
+ }
2793
+ }
2794
+ }
2795
+ const asyncFuncNames = new Set(asyncFunctions.map((f) => f.name));
2796
+ for (let i = 0; i < file.lines.length; i++) {
2797
+ const line = file.lines[i];
2798
+ const trimmed = line.trim();
2799
+ if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
2800
+ if (trimmed.startsWith("import ") || trimmed.startsWith("export ")) continue;
2801
+ if (/^(?:async\s+)?function\s/.test(trimmed)) continue;
2802
+ for (const asyncName of asyncFuncNames) {
2803
+ const callRegex = new RegExp(`(?<!await\\s)(?<!void\\s)\\b${asyncName}\\s*\\(`);
2804
+ if (callRegex.test(trimmed) && !trimmed.includes(".then(") && !trimmed.includes("await ")) {
2805
+ if (/(?:const|let|var|return)\s/.test(trimmed)) continue;
2806
+ const rule = getRuleOrDefault("RV004");
2807
+ findings.push({
2808
+ id: `RV004-${path}-${i + 1}`,
2809
+ ruleId: "RV004",
2810
+ engine: "runtime-verify",
2811
+ category: "runtime-verify",
2812
+ severity: "high",
2813
+ confidence: "likely",
2814
+ confidenceScore: 78,
2815
+ file: path,
2816
+ line: i + 1,
2817
+ code: trimmed,
2818
+ message: `Floating promise: '${asyncName}()' called without await or .then()`,
2819
+ why: rule.why,
2820
+ fix: rule.fix,
2821
+ autoFixable: false,
2822
+ tags: rule.tags,
2823
+ verified: false,
2824
+ _dedup: `RV004:${path}:${i + 1}`
2825
+ });
2826
+ }
2827
+ }
2828
+ }
2829
+ const moduleVars = [];
2830
+ let depth = 0;
2831
+ for (let i = 0; i < file.lines.length; i++) {
2832
+ const line = file.lines[i];
2833
+ for (const ch of line) {
2834
+ if (ch === "{") depth++;
2835
+ if (ch === "}") depth--;
2836
+ }
2837
+ if (depth === 0) {
2838
+ const varMatch = line.match(/(?:let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)/);
2839
+ if (varMatch) {
2840
+ moduleVars.push({ name: varMatch[1], line: i + 1 });
2841
+ }
2842
+ }
2843
+ }
2844
+ for (const fn of asyncFunctions) {
2845
+ for (const v of moduleVars) {
2846
+ for (let j = fn.startLine - 1; j < fn.endLine && j < file.lines.length; j++) {
2847
+ const fLine = file.lines[j];
2848
+ const writeRegex = new RegExp(`\\b${v.name}\\s*(?:=|\\+=|-=|\\+\\+|--)`);
2849
+ if (writeRegex.test(fLine)) {
2850
+ const rule = getRuleOrDefault("RV005");
2851
+ findings.push({
2852
+ id: `RV005-${path}-${j + 1}`,
2853
+ ruleId: "RV005",
2854
+ engine: "runtime-verify",
2855
+ category: "runtime-verify",
2856
+ severity: "medium",
2857
+ confidence: "possible",
2858
+ confidenceScore: 65,
2859
+ file: path,
2860
+ line: j + 1,
2861
+ code: fLine.trim(),
2862
+ message: `Race condition risk: async function '${fn.name}' mutates shared variable '${v.name}'`,
2863
+ why: rule.why,
2864
+ fix: rule.fix,
2865
+ autoFixable: false,
2866
+ tags: rule.tags,
2867
+ verified: false,
2868
+ _dedup: `RV005:${path}:${v.name}:${fn.name}`
2869
+ });
2870
+ break;
2871
+ }
2872
+ }
2873
+ }
2874
+ }
2875
+ }
2876
+ return findings;
2877
+ }
2878
+ };
2879
+
2880
+ // src/scanner/engines/auto-fix.ts
2881
+ var import_fs2 = require("fs");
2882
+ var import_path3 = require("path");
2883
+ var fixStrategies = [
2884
+ // ── QLT001: Remove console.log ──
2885
+ {
2886
+ ruleId: "QLT001",
2887
+ apply(finding, lines) {
2888
+ const lineIdx = finding.line - 1;
2889
+ const line = lines[lineIdx];
2890
+ if (!line) return null;
2891
+ const trimmed = line.trim();
2892
+ if (/^\s*console\.(log|debug|trace|info|warn)\s*\(/.test(line)) {
2893
+ let fullStatement = trimmed;
2894
+ let endIdx = lineIdx;
2895
+ let parenCount = 0;
2896
+ for (let i = lineIdx; i < lines.length; i++) {
2897
+ for (const ch of lines[i]) {
2898
+ if (ch === "(") parenCount++;
2899
+ if (ch === ")") parenCount--;
2900
+ }
2901
+ if (parenCount <= 0) {
2902
+ endIdx = i;
2903
+ break;
2904
+ }
2905
+ }
2906
+ return {
2907
+ replacement: endIdx === lineIdx ? "" : "",
2908
+ description: `Remove ${trimmed.split("(")[0]}() statement`
2909
+ };
2910
+ }
2911
+ return null;
2912
+ }
2913
+ },
2914
+ // ── QLT002: Remove debugger ──
2915
+ {
2916
+ ruleId: "QLT002",
2917
+ apply(finding, lines) {
2918
+ const lineIdx = finding.line - 1;
2919
+ const line = lines[lineIdx];
2920
+ if (!line) return null;
2921
+ if (/^\s*debugger\s*;?\s*$/.test(line)) {
2922
+ return {
2923
+ replacement: "",
2924
+ description: "Remove debugger statement"
2925
+ };
2926
+ }
2927
+ return null;
2928
+ }
2929
+ },
2930
+ // ── SEC009: Replace Math.random() with crypto.randomUUID() ──
2931
+ {
2932
+ ruleId: "SEC009",
2933
+ apply(finding, lines) {
2934
+ const lineIdx = finding.line - 1;
2935
+ const line = lines[lineIdx];
2936
+ if (!line) return null;
2937
+ if (/Math\.random\(\)/.test(line)) {
2938
+ const fixed = line.replace(
2939
+ /Math\.random\(\)\.toString\(\d*\)(?:\.slice\(\d+\))?/g,
2940
+ "crypto.randomUUID()"
2941
+ ).replace(
2942
+ /Math\.random\(\)/g,
2943
+ "crypto.randomUUID()"
2944
+ );
2945
+ if (fixed !== line) {
2946
+ return {
2947
+ replacement: fixed,
2948
+ description: "Replace Math.random() with crypto.randomUUID()"
2949
+ };
2950
+ }
2951
+ }
2952
+ return null;
2953
+ }
2954
+ },
2955
+ // ── IG004/HAL006: Add missing env var to .env.example ──
2956
+ {
2957
+ ruleId: "IG004",
2958
+ apply(finding, _lines, projectRoot) {
2959
+ const envVarMatch = finding.code.match(/process\.env\.([A-Z_][A-Z0-9_]*)/);
2960
+ if (!envVarMatch) return null;
2961
+ const varName = envVarMatch[1];
2962
+ const envExamplePath = (0, import_path3.join)(projectRoot, ".env.example");
2963
+ if ((0, import_fs2.existsSync)(envExamplePath)) {
2964
+ const content = (0, import_fs2.readFileSync)(envExamplePath, "utf-8");
2965
+ if (content.includes(`${varName}=`)) return null;
2966
+ }
2967
+ return {
2968
+ replacement: `${varName}=`,
2969
+ description: `Add ${varName} to .env.example`
2970
+ };
2971
+ }
2972
+ }
2973
+ ];
2974
+ function applyFixes(options) {
2975
+ const startTime = Date.now();
2976
+ const fixResults = [];
2977
+ const fixable = options.findings.filter((f) => f.autoFixable);
2978
+ const byFile = /* @__PURE__ */ new Map();
2979
+ for (const f of fixable) {
2980
+ if (!byFile.has(f.file)) byFile.set(f.file, []);
2981
+ byFile.get(f.file).push(f);
2982
+ }
2983
+ const envVarsToAdd = [];
2984
+ for (const [filePath, findings] of byFile) {
2985
+ const absPath = (0, import_path3.join)(options.projectRoot, filePath);
2986
+ let lines;
2987
+ try {
2988
+ const content = (0, import_fs2.readFileSync)(absPath, "utf-8");
2989
+ lines = content.split("\n");
2990
+ } catch {
2991
+ for (const f of findings) {
2992
+ fixResults.push({
2993
+ findingId: f.id,
2994
+ ruleId: f.ruleId,
2995
+ file: filePath,
2996
+ line: f.line,
2997
+ original: f.code,
2998
+ replacement: "",
2999
+ applied: false,
3000
+ description: "File not readable, skipped"
3001
+ });
3002
+ }
3003
+ continue;
3004
+ }
3005
+ const sorted = [...findings].sort((a, b) => b.line - a.line);
3006
+ let modified = false;
3007
+ for (const finding of sorted) {
3008
+ const strategy = fixStrategies.find((s) => s.ruleId === finding.ruleId);
3009
+ if (!strategy) {
3010
+ fixResults.push({
3011
+ findingId: finding.id,
3012
+ ruleId: finding.ruleId,
3013
+ file: filePath,
3014
+ line: finding.line,
3015
+ original: finding.code,
3016
+ replacement: "",
3017
+ applied: false,
3018
+ description: "No fix strategy available"
3019
+ });
3020
+ continue;
3021
+ }
3022
+ const result = strategy.apply(finding, lines, options.projectRoot);
3023
+ if (!result) {
3024
+ fixResults.push({
3025
+ findingId: finding.id,
3026
+ ruleId: finding.ruleId,
3027
+ file: filePath,
3028
+ line: finding.line,
3029
+ original: finding.code,
3030
+ replacement: "",
3031
+ applied: false,
3032
+ description: "Fix not applicable to this code pattern"
3033
+ });
3034
+ continue;
3035
+ }
3036
+ const lineIdx = finding.line - 1;
3037
+ const original = lines[lineIdx] ?? "";
3038
+ if (finding.ruleId === "IG004" || finding.ruleId === "HAL006") {
3039
+ envVarsToAdd.push(result.replacement);
3040
+ fixResults.push({
3041
+ findingId: finding.id,
3042
+ ruleId: finding.ruleId,
3043
+ file: ".env.example",
3044
+ line: 0,
3045
+ original: "",
3046
+ replacement: result.replacement,
3047
+ applied: !options.dryRun,
3048
+ description: result.description
3049
+ });
3050
+ continue;
3051
+ }
3052
+ if (result.replacement === "") {
3053
+ lines.splice(lineIdx, 1);
3054
+ } else {
3055
+ lines[lineIdx] = result.replacement;
3056
+ }
3057
+ modified = true;
3058
+ fixResults.push({
3059
+ findingId: finding.id,
3060
+ ruleId: finding.ruleId,
3061
+ file: filePath,
3062
+ line: finding.line,
3063
+ original,
3064
+ replacement: result.replacement || "(line removed)",
3065
+ applied: !options.dryRun,
3066
+ description: result.description
3067
+ });
3068
+ }
3069
+ if (modified && !options.dryRun) {
3070
+ try {
3071
+ (0, import_fs2.writeFileSync)(absPath, lines.join("\n"), "utf-8");
3072
+ } catch {
3073
+ for (const r of fixResults) {
3074
+ if (r.file === filePath) r.applied = false;
3075
+ }
3076
+ }
3077
+ }
3078
+ }
3079
+ if (envVarsToAdd.length > 0 && !options.dryRun) {
3080
+ const envExamplePath = (0, import_path3.join)(options.projectRoot, ".env.example");
3081
+ try {
3082
+ const additions = envVarsToAdd.map((v) => `${v}
3083
+ `).join("");
3084
+ if ((0, import_fs2.existsSync)(envExamplePath)) {
3085
+ (0, import_fs2.appendFileSync)(envExamplePath, `
3086
+ # Added by VibeCheck auto-fix
3087
+ ${additions}`, "utf-8");
3088
+ } else {
3089
+ (0, import_fs2.writeFileSync)(envExamplePath, `# Environment Variables
3090
+ # Added by VibeCheck auto-fix
3091
+ ${additions}`, "utf-8");
3092
+ }
3093
+ } catch {
3094
+ for (const r of fixResults) {
3095
+ if (r.file === ".env.example") r.applied = false;
3096
+ }
3097
+ }
3098
+ }
3099
+ const applied = fixResults.filter((r) => r.applied).length;
3100
+ return {
3101
+ totalFixable: fixable.length,
3102
+ applied,
3103
+ skipped: fixable.length - applied,
3104
+ fixes: fixResults,
3105
+ durationMs: Date.now() - startTime
3106
+ };
3107
+ }
3108
+
3109
+ // src/scanner/index.ts
3110
+ var ALL_ENGINES = [
3111
+ credentialsEngine,
3112
+ securityEngine,
3113
+ fakeFeaturesEngine,
3114
+ hallucinationsEngine,
3115
+ deadUIEngine,
3116
+ codeQualityEngine,
3117
+ importGraphEngine,
3118
+ runtimeVerifyEngine
3119
+ ];
3120
+ var DEFAULT_EXCLUDE = [
3121
+ "node_modules",
3122
+ ".git",
3123
+ "dist",
3124
+ "build",
3125
+ ".next",
3126
+ ".nuxt",
3127
+ "coverage",
3128
+ "__pycache__",
3129
+ ".venv",
3130
+ "venv",
3131
+ ".output"
3132
+ ];
3133
+ function loadFiles(projectRoot, options) {
3134
+ const files = /* @__PURE__ */ new Map();
3135
+ const exclude = options.exclude ?? DEFAULT_EXCLUDE;
3136
+ const maxFileSize = 512 * 1024;
3137
+ function walk(dir) {
3138
+ let entries;
3139
+ try {
3140
+ entries = (0, import_fs3.readdirSync)(dir);
3141
+ } catch {
3142
+ return;
3143
+ }
3144
+ for (const entry of entries) {
3145
+ const fullPath = (0, import_path4.join)(dir, entry);
3146
+ if (exclude.some((e) => entry === e || entry.startsWith("."))) {
3147
+ try {
3148
+ if ((0, import_fs3.statSync)(fullPath).isDirectory()) continue;
3149
+ } catch {
3150
+ continue;
3151
+ }
3152
+ }
3153
+ let stat;
3154
+ try {
3155
+ stat = (0, import_fs3.statSync)(fullPath);
3156
+ } catch {
3157
+ continue;
3158
+ }
3159
+ if (stat.isDirectory()) {
3160
+ walk(fullPath);
3161
+ continue;
3162
+ }
3163
+ if (!isCodeFile(fullPath)) continue;
3164
+ if (stat.size > maxFileSize) continue;
3165
+ const relativePath = (0, import_path4.relative)(projectRoot, fullPath);
3166
+ const classification = classifyPath(relativePath);
3167
+ if (classification.excludeByDefault && classification.category !== "test") continue;
3168
+ if (classification.category === "test" && !options.includeTests) continue;
3169
+ try {
3170
+ const content = (0, import_fs3.readFileSync)(fullPath, "utf-8");
3171
+ const hash = (0, import_crypto.createHash)("md5").update(content).digest("hex");
3172
+ files.set(relativePath, {
3173
+ path: relativePath,
3174
+ absolutePath: fullPath,
3175
+ content,
3176
+ lines: content.split("\n"),
3177
+ ext: (0, import_path4.extname)(fullPath).toLowerCase(),
3178
+ hash,
3179
+ classification
3180
+ });
3181
+ } catch {
3182
+ }
3183
+ }
3184
+ }
3185
+ if (options.files && options.files.length > 0) {
3186
+ for (const filePath of options.files) {
3187
+ const fullPath = (0, import_path4.join)(projectRoot, filePath);
3188
+ if (!(0, import_fs3.existsSync)(fullPath)) continue;
3189
+ try {
3190
+ const content = (0, import_fs3.readFileSync)(fullPath, "utf-8");
3191
+ const hash = (0, import_crypto.createHash)("md5").update(content).digest("hex");
3192
+ const classification = classifyPath(filePath);
3193
+ files.set(filePath, {
3194
+ path: filePath,
3195
+ absolutePath: fullPath,
3196
+ content,
3197
+ lines: content.split("\n"),
3198
+ ext: (0, import_path4.extname)(fullPath).toLowerCase(),
3199
+ hash,
3200
+ classification
3201
+ });
3202
+ } catch {
3203
+ }
3204
+ }
3205
+ } else {
3206
+ walk(projectRoot);
3207
+ }
3208
+ return files;
3209
+ }
3210
+ function calculateHealthScore(findings) {
3211
+ let score = 100;
3212
+ for (const f of findings) {
3213
+ switch (f.severity) {
3214
+ case "critical":
3215
+ score -= 15;
3216
+ break;
3217
+ case "high":
3218
+ score -= 8;
3219
+ break;
3220
+ case "medium":
3221
+ score -= 3;
3222
+ break;
3223
+ case "low":
3224
+ score -= 1;
3225
+ break;
3226
+ }
3227
+ if (f.category === "hallucinations") score -= 3;
3228
+ if (f.ruleId === "FAKE005") score -= 5;
3229
+ }
3230
+ return Math.max(0, Math.min(100, score));
3231
+ }
3232
+ async function scan(options) {
3233
+ const startTime = Date.now();
3234
+ const scanId = `scan-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
3235
+ options.onProgress?.({
3236
+ phase: "loading",
3237
+ processed: 0,
3238
+ total: 0,
3239
+ percentage: 0,
3240
+ elapsedMs: 0
3241
+ });
3242
+ const files = loadFiles(options.projectRoot, options);
3243
+ options.onProgress?.({
3244
+ phase: "classifying",
3245
+ processed: files.size,
3246
+ total: files.size,
3247
+ percentage: 10,
3248
+ elapsedMs: Date.now() - startTime
3249
+ });
3250
+ const enabledEngines = options.engines ? ALL_ENGINES.filter((e) => options.engines.includes(e.name)) : ALL_ENGINES;
3251
+ const engineResults = [];
3252
+ let allFindings = [];
3253
+ if (options.parallel !== false) {
3254
+ const promises = enabledEngines.map(async (engine) => {
3255
+ const engineStart = Date.now();
3256
+ try {
3257
+ const findings = await Promise.race([
3258
+ engine.scan(files, options),
3259
+ new Promise(
3260
+ (_, reject) => setTimeout(() => reject(new Error("Engine timeout")), options.engineTimeout ?? 3e4)
3261
+ )
3262
+ ]);
3263
+ return {
3264
+ engine: engine.name,
3265
+ findings: findings.length,
3266
+ durationMs: Date.now() - engineStart,
3267
+ success: true,
3268
+ result: findings
3269
+ };
3270
+ } catch (err) {
3271
+ return {
3272
+ engine: engine.name,
3273
+ findings: 0,
3274
+ durationMs: Date.now() - engineStart,
3275
+ success: false,
3276
+ error: err instanceof Error ? err.message : "Unknown error",
3277
+ result: []
3278
+ };
3279
+ }
3280
+ });
3281
+ const results = await Promise.all(promises);
3282
+ for (const r of results) {
3283
+ engineResults.push({
3284
+ engine: r.engine,
3285
+ findings: r.findings,
3286
+ durationMs: r.durationMs,
3287
+ success: r.success,
3288
+ error: r.error
3289
+ });
3290
+ allFindings.push(...r.result);
3291
+ }
3292
+ } else {
3293
+ for (const engine of enabledEngines) {
3294
+ const engineStart = Date.now();
3295
+ try {
3296
+ const findings = await engine.scan(files, options);
3297
+ engineResults.push({
3298
+ engine: engine.name,
3299
+ findings: findings.length,
3300
+ durationMs: Date.now() - engineStart,
3301
+ success: true
3302
+ });
3303
+ allFindings.push(...findings);
3304
+ } catch (err) {
3305
+ engineResults.push({
3306
+ engine: engine.name,
3307
+ findings: 0,
3308
+ durationMs: Date.now() - engineStart,
3309
+ success: false,
3310
+ error: err instanceof Error ? err.message : "Unknown error"
3311
+ });
3312
+ }
3313
+ }
3314
+ }
3315
+ options.onProgress?.({
3316
+ phase: "deduplicating",
3317
+ processed: allFindings.length,
3318
+ total: allFindings.length,
3319
+ percentage: 85,
3320
+ elapsedMs: Date.now() - startTime
3321
+ });
3322
+ const { deduplicated, suppressedCount } = deduplicateFindings(allFindings);
3323
+ const severityOrder = ["low", "medium", "high", "critical"];
3324
+ const minSeverityIndex = severityOrder.indexOf(options.severityThreshold ?? "low");
3325
+ const filtered = deduplicated.filter(
3326
+ (f) => severityOrder.indexOf(f.severity) >= minSeverityIndex
3327
+ );
3328
+ if (options.onFinding) {
3329
+ for (const f of filtered) {
3330
+ options.onFinding(f);
3331
+ }
3332
+ }
3333
+ const bySeverity = { critical: 0, high: 0, medium: 0, low: 0 };
3334
+ const byCategory = {};
3335
+ const byEngine = {};
3336
+ for (const f of filtered) {
3337
+ bySeverity[f.severity]++;
3338
+ byCategory[f.category] = (byCategory[f.category] ?? 0) + 1;
3339
+ byEngine[f.engine] = (byEngine[f.engine] ?? 0) + 1;
3340
+ }
3341
+ const durationMs = Date.now() - startTime;
3342
+ const engineTimings = {};
3343
+ for (const r of engineResults) {
3344
+ engineTimings[r.engine] = r.durationMs;
3345
+ }
3346
+ options.onProgress?.({
3347
+ phase: "complete",
3348
+ processed: files.size,
3349
+ total: files.size,
3350
+ percentage: 100,
3351
+ elapsedMs: durationMs
3352
+ });
3353
+ return {
3354
+ scanId,
3355
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3356
+ findings: filtered,
3357
+ summary: {
3358
+ totalFiles: files.size,
3359
+ filesScanned: files.size,
3360
+ totalFindings: filtered.length,
3361
+ bySeverity,
3362
+ byCategory,
3363
+ byEngine,
3364
+ autoFixable: filtered.filter((f) => f.autoFixable).length,
3365
+ suppressedDuplicates: suppressedCount
3366
+ },
3367
+ healthScore: calculateHealthScore(filtered),
3368
+ engineResults,
3369
+ metrics: {
3370
+ durationMs,
3371
+ filesPerSecond: files.size > 0 ? Math.round(files.size / durationMs * 1e3) : 0,
3372
+ engineTimings
3373
+ }
3374
+ };
3375
+ }
3376
+ async function fix(options) {
3377
+ const report = await scan(options);
3378
+ const fixReport = applyFixes({
3379
+ findings: report.findings,
3380
+ projectRoot: options.projectRoot,
3381
+ dryRun: options.dryRun ?? false
3382
+ });
3383
+ return { report, fixReport };
3384
+ }
3385
+ // Annotate the CommonJS export names for ESM import in node:
3386
+ 0 && (module.exports = {
3387
+ ALL_ENGINES,
3388
+ RULE_CATALOG,
3389
+ applyFixes,
3390
+ classifyPath,
3391
+ fix,
3392
+ getRuleOrDefault,
3393
+ scan
3394
+ });
3395
+ //# sourceMappingURL=index.js.map