@clear-capabilities/agentic-security-scanner 0.74.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 (331) hide show
  1. package/CHANGELOG.md +1580 -0
  2. package/bin/.agentic-security/findings.json +1577 -0
  3. package/bin/.agentic-security/last-scan.json +1577 -0
  4. package/bin/.agentic-security/last-scan.json.sig +1 -0
  5. package/bin/.agentic-security/scan-history.json +465 -0
  6. package/bin/.agentic-security/streak.json +25 -0
  7. package/bin/agentic-security-audit.js +198 -0
  8. package/bin/agentic-security-consistency.js +80 -0
  9. package/bin/agentic-security-diff.js +136 -0
  10. package/bin/agentic-security-lsp.js +12 -0
  11. package/bin/agentic-security-mcp.js +40 -0
  12. package/bin/agentic-security-rule.js +153 -0
  13. package/bin/agentic-security.js +1683 -0
  14. package/dist/117.index.js +207 -0
  15. package/dist/178.index.js +250 -0
  16. package/dist/218.index.js +793 -0
  17. package/dist/227.index.js +192 -0
  18. package/dist/301.index.js +167 -0
  19. package/dist/384.index.js +18 -0
  20. package/dist/476.index.js +126 -0
  21. package/dist/513.index.js +373 -0
  22. package/dist/520.index.js +13 -0
  23. package/dist/601.index.js +1038 -0
  24. package/dist/634.index.js +1892 -0
  25. package/dist/637.index.js +216 -0
  26. package/dist/660.index.js +131 -0
  27. package/dist/675.index.js +451 -0
  28. package/dist/826.index.js +188 -0
  29. package/dist/830.index.js +133 -0
  30. package/dist/agentic-security.mjs +272 -0
  31. package/dist/agentic-security.mjs.sha256 +1 -0
  32. package/dist/calibration-seed.json +27 -0
  33. package/package.json +77 -0
  34. package/src/.agentic-security/findings.json +80844 -0
  35. package/src/.agentic-security/last-scan.json +80844 -0
  36. package/src/.agentic-security/last-scan.json.sig +1 -0
  37. package/src/.agentic-security/scan-history.json +8408 -0
  38. package/src/.agentic-security/streak.json +26 -0
  39. package/src/badge.js +188 -0
  40. package/src/compare.js +203 -0
  41. package/src/dataflow/.agentic-security/findings.json +3487 -0
  42. package/src/dataflow/.agentic-security/last-scan.json +3487 -0
  43. package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
  44. package/src/dataflow/.agentic-security/scan-history.json +735 -0
  45. package/src/dataflow/.agentic-security/streak.json +24 -0
  46. package/src/dataflow/CLAUDE.md +38 -0
  47. package/src/dataflow/access-paths.js +172 -0
  48. package/src/dataflow/async-sequencing.js +177 -0
  49. package/src/dataflow/backward.js +201 -0
  50. package/src/dataflow/catalog-expanded.js +485 -0
  51. package/src/dataflow/catalog.js +659 -0
  52. package/src/dataflow/cross-repo.js +219 -0
  53. package/src/dataflow/engine.js +588 -0
  54. package/src/dataflow/exception-flow.js +116 -0
  55. package/src/dataflow/exploit-prover.js +187 -0
  56. package/src/dataflow/higher-order.js +221 -0
  57. package/src/dataflow/ifds.js +347 -0
  58. package/src/dataflow/implicit-flow.js +129 -0
  59. package/src/dataflow/incremental.js +229 -0
  60. package/src/dataflow/index.js +181 -0
  61. package/src/dataflow/numeric-domain.js +192 -0
  62. package/src/dataflow/path-feasibility.js +114 -0
  63. package/src/dataflow/points-to.js +337 -0
  64. package/src/dataflow/polyglot.js +190 -0
  65. package/src/dataflow/proven-clean.js +159 -0
  66. package/src/dataflow/receiver-context.js +76 -0
  67. package/src/dataflow/sanitizer-proof.js +154 -0
  68. package/src/dataflow/soft-taint.js +140 -0
  69. package/src/dataflow/string-domain.js +234 -0
  70. package/src/dataflow/stub-aware-filter.js +100 -0
  71. package/src/dataflow/summaries.js +132 -0
  72. package/src/dataflow/symbolic-exec.js +238 -0
  73. package/src/dataflow/tabulation.js +135 -0
  74. package/src/engine.js +7763 -0
  75. package/src/history-scan.js +229 -0
  76. package/src/index.js +3 -0
  77. package/src/integrations/.agentic-security/findings.json +1504 -0
  78. package/src/integrations/.agentic-security/last-scan.json +1504 -0
  79. package/src/integrations/.agentic-security/scan-history.json +40 -0
  80. package/src/integrations/.agentic-security/streak.json +21 -0
  81. package/src/integrations/index.js +321 -0
  82. package/src/integrations/tickets.js +200 -0
  83. package/src/ir/.agentic-security/findings.json +3036 -0
  84. package/src/ir/.agentic-security/last-scan.json +3036 -0
  85. package/src/ir/.agentic-security/last-scan.json.sig +1 -0
  86. package/src/ir/.agentic-security/scan-history.json +364 -0
  87. package/src/ir/.agentic-security/streak.json +23 -0
  88. package/src/ir/CLAUDE.md +172 -0
  89. package/src/ir/callgraph.js +73 -0
  90. package/src/ir/class-hierarchy.js +195 -0
  91. package/src/ir/index.js +152 -0
  92. package/src/ir/parser-cs.js +260 -0
  93. package/src/ir/parser-java.js +286 -0
  94. package/src/ir/parser-js.js +413 -0
  95. package/src/ir/parser-kt.js +258 -0
  96. package/src/ir/parser-py-cst.js +136 -0
  97. package/src/ir/parser-py.helper.py +501 -0
  98. package/src/ir/parser-py.js +312 -0
  99. package/src/ir/ssa.js +315 -0
  100. package/src/ir/type-stubs.js +288 -0
  101. package/src/leaderboard.js +152 -0
  102. package/src/llm-validator/.agentic-security/findings.json +1891 -0
  103. package/src/llm-validator/.agentic-security/last-scan.json +1891 -0
  104. package/src/llm-validator/.agentic-security/last-scan.json.sig +1 -0
  105. package/src/llm-validator/.agentic-security/scan-history.json +168 -0
  106. package/src/llm-validator/.agentic-security/streak.json +20 -0
  107. package/src/llm-validator/consistency.js +141 -0
  108. package/src/llm-validator/index.js +437 -0
  109. package/src/lsp/.agentic-security/findings.json +28 -0
  110. package/src/lsp/.agentic-security/last-scan.json +28 -0
  111. package/src/lsp/.agentic-security/scan-history.json +79 -0
  112. package/src/lsp/.agentic-security/streak.json +22 -0
  113. package/src/lsp/server.js +275 -0
  114. package/src/mcp/.agentic-security/findings.json +8358 -0
  115. package/src/mcp/.agentic-security/last-scan.json +8358 -0
  116. package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
  117. package/src/mcp/.agentic-security/scan-history.json +1125 -0
  118. package/src/mcp/.agentic-security/streak.json +22 -0
  119. package/src/mcp/CLAUDE.md +54 -0
  120. package/src/mcp/audit.js +136 -0
  121. package/src/mcp/redact.js +75 -0
  122. package/src/mcp/server.js +158 -0
  123. package/src/mcp/stdio.js +83 -0
  124. package/src/mcp/tools.js +940 -0
  125. package/src/mcp/validate.js +49 -0
  126. package/src/personality.js +164 -0
  127. package/src/poc-video.js +239 -0
  128. package/src/posture/.agentic-security/findings.json +51239 -0
  129. package/src/posture/.agentic-security/last-scan.json +51239 -0
  130. package/src/posture/.agentic-security/last-scan.json.sig +1 -0
  131. package/src/posture/.agentic-security/scan-history.json +5557 -0
  132. package/src/posture/.agentic-security/streak.json +24 -0
  133. package/src/posture/CLAUDE.md +42 -0
  134. package/src/posture/adversarial-self-test.js +114 -0
  135. package/src/posture/adversary-agent.js +204 -0
  136. package/src/posture/agents-memory.js +135 -0
  137. package/src/posture/ai-code-fingerprint.js +171 -0
  138. package/src/posture/aibom.js +284 -0
  139. package/src/posture/api-inventory.js +96 -0
  140. package/src/posture/attack-playbooks.js +305 -0
  141. package/src/posture/auditor-agent.js +115 -0
  142. package/src/posture/auth-posture-import.js +135 -0
  143. package/src/posture/baseline-compare.js +114 -0
  144. package/src/posture/blast-radius.js +836 -0
  145. package/src/posture/bounty-prediction.js +141 -0
  146. package/src/posture/business-logic.js +239 -0
  147. package/src/posture/calibration-drift.js +93 -0
  148. package/src/posture/calibration-seed.json +27 -0
  149. package/src/posture/calibration.js +204 -0
  150. package/src/posture/clustering.js +75 -0
  151. package/src/posture/concurrency-checker.js +265 -0
  152. package/src/posture/confidence.js +65 -0
  153. package/src/posture/container-runtime.js +149 -0
  154. package/src/posture/counterfactual.js +109 -0
  155. package/src/posture/cross-lang-graphql.js +165 -0
  156. package/src/posture/cross-lang-grpc.js +166 -0
  157. package/src/posture/cross-lang-meta.js +101 -0
  158. package/src/posture/cross-lang-openapi.js +187 -0
  159. package/src/posture/cross-lang-orm.js +153 -0
  160. package/src/posture/cross-lang-queues.js +210 -0
  161. package/src/posture/crown-jewels.js +110 -0
  162. package/src/posture/custom-rules.js +361 -0
  163. package/src/posture/cve-alert-daemon.js +433 -0
  164. package/src/posture/cve-lookup.js +129 -0
  165. package/src/posture/dead-code.js +430 -0
  166. package/src/posture/defender-agent.js +158 -0
  167. package/src/posture/deploy-platform.js +204 -0
  168. package/src/posture/detector-fuzz.js +61 -0
  169. package/src/posture/deterministic.js +99 -0
  170. package/src/posture/drift.js +165 -0
  171. package/src/posture/epss.js +156 -0
  172. package/src/posture/exploitability-probability.js +212 -0
  173. package/src/posture/exploitability.js +121 -0
  174. package/src/posture/feature-flags.js +110 -0
  175. package/src/posture/finding-defaults.js +132 -0
  176. package/src/posture/fix-history.js +411 -0
  177. package/src/posture/fix-plan.js +121 -0
  178. package/src/posture/fix-verify-loop.js +157 -0
  179. package/src/posture/fix-verify.js +130 -0
  180. package/src/posture/flow-narration.js +105 -0
  181. package/src/posture/grader-calibration.js +156 -0
  182. package/src/posture/harness-discovery.js +113 -0
  183. package/src/posture/holdout-eval.js +144 -0
  184. package/src/posture/iac-reachability.js +163 -0
  185. package/src/posture/iam-policy.js +128 -0
  186. package/src/posture/integrity.js +97 -0
  187. package/src/posture/learning.js +166 -0
  188. package/src/posture/license-policy.js +109 -0
  189. package/src/posture/llm-redteam-prompts.js +418 -0
  190. package/src/posture/llm-redteam.js +303 -0
  191. package/src/posture/material-change.js +163 -0
  192. package/src/posture/mitigation-composite.js +55 -0
  193. package/src/posture/mttr.js +91 -0
  194. package/src/posture/network-policy-import.js +126 -0
  195. package/src/posture/path-predicates.js +99 -0
  196. package/src/posture/persona-prioritization.js +153 -0
  197. package/src/posture/poc-cwe-map.js +51 -0
  198. package/src/posture/poc-generator.js +500 -0
  199. package/src/posture/policy-gate.js +174 -0
  200. package/src/posture/pre-incident-archaeology.js +110 -0
  201. package/src/posture/profile.js +93 -0
  202. package/src/posture/reachability-filter.js +42 -0
  203. package/src/posture/regression-test-gen.js +200 -0
  204. package/src/posture/reverse-blast-radius.js +110 -0
  205. package/src/posture/router.js +109 -0
  206. package/src/posture/rule-overrides.js +198 -0
  207. package/src/posture/rule-pack-signing.js +209 -0
  208. package/src/posture/rule-packs.js +143 -0
  209. package/src/posture/rule-synthesis.js +108 -0
  210. package/src/posture/ruleset-version.js +71 -0
  211. package/src/posture/sbom.js +129 -0
  212. package/src/posture/schema-aware-bridge.js +207 -0
  213. package/src/posture/security-trend.js +87 -0
  214. package/src/posture/semantic-clone.js +114 -0
  215. package/src/posture/specification-mining.js +170 -0
  216. package/src/posture/stable-id.js +75 -0
  217. package/src/posture/stack-playbook.js +229 -0
  218. package/src/posture/streak.js +249 -0
  219. package/src/posture/suppressions.js +135 -0
  220. package/src/posture/telemetry-ingest.js +112 -0
  221. package/src/posture/threat-model.js +145 -0
  222. package/src/posture/three-agent-pipeline.js +74 -0
  223. package/src/posture/triage.js +146 -0
  224. package/src/posture/trust-boundary-diagram.js +115 -0
  225. package/src/posture/type-narrowing.js +129 -0
  226. package/src/posture/validator-metrics.js +179 -0
  227. package/src/posture/verifier-ephemeral.js +118 -0
  228. package/src/posture/verifier-target.js +147 -0
  229. package/src/posture/verifier.js +257 -0
  230. package/src/posture/version.js +75 -0
  231. package/src/posture/waf-ingest.js +200 -0
  232. package/src/posture/why-fired.js +141 -0
  233. package/src/pr-comment.js +172 -0
  234. package/src/pr-delta.js +198 -0
  235. package/src/report/.agentic-security/findings.json +79 -0
  236. package/src/report/.agentic-security/last-scan.json +79 -0
  237. package/src/report/.agentic-security/last-scan.json.sig +1 -0
  238. package/src/report/.agentic-security/scan-history.json +332 -0
  239. package/src/report/.agentic-security/streak.json +23 -0
  240. package/src/report/index.js +1136 -0
  241. package/src/report/mascot.js +42 -0
  242. package/src/runScan.js +141 -0
  243. package/src/sast/.agentic-security/findings.json +5051 -0
  244. package/src/sast/.agentic-security/last-scan.json +5051 -0
  245. package/src/sast/.agentic-security/last-scan.json.sig +1 -0
  246. package/src/sast/.agentic-security/scan-history.json +788 -0
  247. package/src/sast/.agentic-security/streak.json +23 -0
  248. package/src/sast/CLAUDE.md +39 -0
  249. package/src/sast/_comment-strip.js +46 -0
  250. package/src/sast/agent-tool-escalation.js +131 -0
  251. package/src/sast/auth-provider.js +171 -0
  252. package/src/sast/authz.js +236 -0
  253. package/src/sast/bench-shape/.agentic-security/findings.json +28 -0
  254. package/src/sast/bench-shape/.agentic-security/last-scan.json +28 -0
  255. package/src/sast/bench-shape/.agentic-security/scan-history.json +24 -0
  256. package/src/sast/bench-shape/.agentic-security/streak.json +22 -0
  257. package/src/sast/bench-shape/index.js +62 -0
  258. package/src/sast/claude-hook-injection.js +199 -0
  259. package/src/sast/claude-md-prompt-injection.js +170 -0
  260. package/src/sast/claude-settings.js +165 -0
  261. package/src/sast/client-side.js +149 -0
  262. package/src/sast/cpp-bench-extras.js +122 -0
  263. package/src/sast/cpp-dataflow.js +430 -0
  264. package/src/sast/cpp.js +248 -0
  265. package/src/sast/csharp.js +152 -0
  266. package/src/sast/csrf.js +82 -0
  267. package/src/sast/dart-flutter.js +173 -0
  268. package/src/sast/db-rls.js +147 -0
  269. package/src/sast/db-taint.js +215 -0
  270. package/src/sast/defi-deep.js +242 -0
  271. package/src/sast/deserialization-gadgets.js +113 -0
  272. package/src/sast/django-hardening.js +230 -0
  273. package/src/sast/env-hygiene.js +125 -0
  274. package/src/sast/fastapi-hardening.js +145 -0
  275. package/src/sast/go-extended.js +84 -0
  276. package/src/sast/host-header.js +106 -0
  277. package/src/sast/index.js +17 -0
  278. package/src/sast/java-ast-folding.js +561 -0
  279. package/src/sast/java-bench-extras.js +708 -0
  280. package/src/sast/java-collection-passthrough.js +178 -0
  281. package/src/sast/java-constant-fold.js +244 -0
  282. package/src/sast/java-deserialization.js +125 -0
  283. package/src/sast/jndi.js +104 -0
  284. package/src/sast/juliet-shape.js +324 -0
  285. package/src/sast/jwt-exp.js +104 -0
  286. package/src/sast/kotlin.js +82 -0
  287. package/src/sast/laravel-hardening.js +198 -0
  288. package/src/sast/ldap-injection.js +100 -0
  289. package/src/sast/llm-owasp.js +465 -0
  290. package/src/sast/llm-stored-prompt.js +103 -0
  291. package/src/sast/llm-trading-agent.js +161 -0
  292. package/src/sast/llm.js +308 -0
  293. package/src/sast/logic.js +140 -0
  294. package/src/sast/mass-assignment.js +101 -0
  295. package/src/sast/mcp-audit.js +242 -0
  296. package/src/sast/mobile-manifest.js +195 -0
  297. package/src/sast/model-load.js +164 -0
  298. package/src/sast/mutation-xss.js +87 -0
  299. package/src/sast/nosql-injection.js +82 -0
  300. package/src/sast/open-redirect.js +119 -0
  301. package/src/sast/php.js +91 -0
  302. package/src/sast/pipeline.js +122 -0
  303. package/src/sast/primary-cwe-java.js +155 -0
  304. package/src/sast/prompt-firewall.js +151 -0
  305. package/src/sast/prompt-template.js +157 -0
  306. package/src/sast/prototype-pollution.js +112 -0
  307. package/src/sast/python-sinks.js +195 -0
  308. package/src/sast/quarkus-hardening.js +102 -0
  309. package/src/sast/rag-poisoning.js +118 -0
  310. package/src/sast/rate-limit.js +128 -0
  311. package/src/sast/response-splitting.js +138 -0
  312. package/src/sast/ruby.js +108 -0
  313. package/src/sast/rust.js +105 -0
  314. package/src/sast/solidity.js +167 -0
  315. package/src/sast/springboot-hardening.js +186 -0
  316. package/src/sast/ssrf-cloud-metadata.js +80 -0
  317. package/src/sast/ssti.js +116 -0
  318. package/src/sast/swift.js +162 -0
  319. package/src/sast/toctou.js +95 -0
  320. package/src/sast/webhook.js +101 -0
  321. package/src/sast/xpath-injection.js +51 -0
  322. package/src/sast/xxe.js +140 -0
  323. package/src/sast/zip-slip.js +200 -0
  324. package/src/sca/base-images.json +45 -0
  325. package/src/sca/container.js +107 -0
  326. package/src/sca/dep-confusion.js +134 -0
  327. package/src/sca/index.js +6 -0
  328. package/src/sca/popular-packages.json +41 -0
  329. package/src/sca/sarif-ingest.js +187 -0
  330. package/src/sca/vuln-function-hints.json +89 -0
  331. package/src/secrets/index.js +4 -0
@@ -0,0 +1,229 @@
1
+ // Time-travel + counterfactual scanning (v0.68).
2
+ //
3
+ // Two new modes that exploit the pure-input shape of runFullScan:
4
+ //
5
+ // 1. `runHistory` — walks N historical git refs, scans each, emits a
6
+ // per-ref timeline of finding counts + identifies findings introduced
7
+ // and resolved between each pair of consecutive refs.
8
+ //
9
+ // 2. `runWhatIf` — overlays virtual file contents onto the working tree,
10
+ // scans the modified state, returns delta vs. the baseline scan.
11
+ // Useful for "what if I delete this middleware" / "what if I add this
12
+ // new route" / "what if I downgrade this dependency."
13
+ //
14
+ // Both modes read source via `git show <ref>:<path>` and feed the byte
15
+ // content into runFullScan's fileContents map directly. No `git checkout`
16
+ // or worktree write — the user's working tree is never disturbed.
17
+
18
+ import { spawnSync } from 'node:child_process';
19
+ import * as fs from 'node:fs';
20
+ import * as path from 'node:path';
21
+ import { runFullScan } from './engine.js';
22
+
23
+ const MAX_FILES_PER_SCAN = 5000;
24
+
25
+ function _git(root, args) {
26
+ const r = spawnSync('git', args, { cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
27
+ return { ok: r.status === 0, stdout: r.stdout || '', stderr: r.stderr || '' };
28
+ }
29
+
30
+ // Enumerate refs going back `since` from HEAD at `interval` steps.
31
+ // `since`: a git-readable duration like '6.months', '30.days', '1.year'.
32
+ // `interval`: same shape; used to step backward.
33
+ export function listHistoricalRefs(root, { since = '6.months', interval = '1.month' } = {}) {
34
+ // First, get the oldest reachable commit within `since`.
35
+ const log = _git(root, ['log', `--since=${since}`, '--format=%H %at %s']);
36
+ if (!log.ok) return [];
37
+ const rows = log.stdout.trim().split('\n').filter(Boolean).map(l => {
38
+ const [sha, ts, ...subject] = l.split(' ');
39
+ return { sha, timestamp: Number(ts), subject: subject.join(' ') };
40
+ });
41
+ if (rows.length === 0) return [];
42
+ // Step backward by `interval`-equivalent timestamps. Convert interval
43
+ // to seconds (rough: assume month=30d).
44
+ const ivSec = _approxSeconds(interval);
45
+ const picks = [];
46
+ let last = rows[0].timestamp + 1; // ensure HEAD itself is included
47
+ for (const row of rows) {
48
+ if (row.timestamp <= last - ivSec) {
49
+ picks.push(row);
50
+ last = row.timestamp;
51
+ }
52
+ }
53
+ // Always include HEAD as the first sample.
54
+ return [{ sha: 'HEAD', timestamp: rows[0].timestamp, subject: rows[0].subject }, ...picks];
55
+ }
56
+
57
+ function _approxSeconds(d) {
58
+ const m = String(d).match(/^(\d+(?:\.\d+)?)\.(seconds?|minutes?|hours?|days?|weeks?|months?|years?)$/);
59
+ if (!m) return 30 * 86400;
60
+ const v = Number(m[1]);
61
+ const u = m[2];
62
+ const mult = u.startsWith('second') ? 1
63
+ : u.startsWith('minute') ? 60
64
+ : u.startsWith('hour') ? 3600
65
+ : u.startsWith('day') ? 86400
66
+ : u.startsWith('week') ? 7 * 86400
67
+ : u.startsWith('month') ? 30 * 86400
68
+ : u.startsWith('year') ? 365 * 86400
69
+ : 86400;
70
+ return v * mult;
71
+ }
72
+
73
+ // List all the source files at a given ref (relative paths). Filter to
74
+ // files the scanner actually processes.
75
+ function _listFilesAtRef(root, ref) {
76
+ const r = _git(root, ['ls-tree', '-r', '--name-only', ref]);
77
+ if (!r.ok) return [];
78
+ return r.stdout.trim().split('\n').filter(p => {
79
+ if (!p) return false;
80
+ if (p.includes('/node_modules/') || p.includes('/.venv/')) return false;
81
+ return /\.(?:js|jsx|ts|tsx|mjs|cjs|py|java|cs|kt|go|rb|php|sol|swift|rs|tf|yml|yaml|json|toml|md)$/i.test(p);
82
+ });
83
+ }
84
+
85
+ function _readFileAtRef(root, ref, file) {
86
+ const r = _git(root, ['show', `${ref}:${file}`]);
87
+ if (!r.ok) return null;
88
+ return r.stdout;
89
+ }
90
+
91
+ async function _scanAtRef(root, ref) {
92
+ const files = _listFilesAtRef(root, ref).slice(0, MAX_FILES_PER_SCAN);
93
+ const fileContents = {};
94
+ for (const f of files) {
95
+ const c = _readFileAtRef(root, ref, f);
96
+ if (c != null) fileContents[f] = c;
97
+ }
98
+ const scan = await runFullScan({ fileContents, scanRoot: root }, () => {});
99
+ return {
100
+ ref,
101
+ fileCount: Object.keys(fileContents).length,
102
+ findings: (scan.findings || []).map(_compact),
103
+ logicVulns: (scan.logicVulns || []).length,
104
+ secrets: (scan.secrets || []).length,
105
+ };
106
+ }
107
+
108
+ function _compact(f) {
109
+ return {
110
+ stableId: f.stableId || f.id,
111
+ file: f.file,
112
+ line: f.line,
113
+ vuln: f.vuln,
114
+ severity: f.severity,
115
+ cwe: f.cwe,
116
+ family: f.family || null,
117
+ };
118
+ }
119
+
120
+ // Top-level: scan each historical ref + diff consecutive snapshots.
121
+ //
122
+ // Returns:
123
+ // {
124
+ // refs: [{ ref, when, fileCount, findings, secretsN, logicVulnsN }],
125
+ // timeline: [{ from, to, introduced: [...], resolved: [...] }],
126
+ // }
127
+ export async function runHistory(root, opts = {}) {
128
+ const refs = listHistoricalRefs(root, opts);
129
+ if (refs.length === 0) return { error: 'no-refs-in-window', refs: [], timeline: [] };
130
+ const snapshots = [];
131
+ for (const r of refs) {
132
+ try {
133
+ const s = await _scanAtRef(root, r.sha);
134
+ snapshots.push({ ...s, when: new Date(r.timestamp * 1000).toISOString(), subject: r.subject });
135
+ } catch (e) {
136
+ snapshots.push({ ref: r.sha, error: e.message, when: new Date(r.timestamp * 1000).toISOString() });
137
+ }
138
+ }
139
+ // Walk pairs from oldest → newest. snapshots[] is currently HEAD→old;
140
+ // reverse so timeline reads forward in time.
141
+ const ordered = [...snapshots].reverse();
142
+ const timeline = [];
143
+ for (let i = 1; i < ordered.length; i++) {
144
+ const a = ordered[i - 1];
145
+ const b = ordered[i];
146
+ const idsA = new Set((a.findings || []).map(f => f.stableId));
147
+ const idsB = new Set((b.findings || []).map(f => f.stableId));
148
+ const introduced = (b.findings || []).filter(f => !idsA.has(f.stableId));
149
+ const resolved = (a.findings || []).filter(f => !idsB.has(f.stableId));
150
+ timeline.push({
151
+ from: a.ref, fromWhen: a.when, to: b.ref, toWhen: b.when,
152
+ introducedN: introduced.length,
153
+ resolvedN: resolved.length,
154
+ introduced: introduced.slice(0, 50),
155
+ resolved: resolved.slice(0, 50),
156
+ });
157
+ }
158
+ return { refs: snapshots, timeline };
159
+ }
160
+
161
+ // Counterfactual scan: apply a virtual overlay then scan.
162
+ // overlays: [{ file, content }]
163
+ // remove: [file] — files to virtually delete (skipped from scan input)
164
+ export async function runWhatIf(root, { overlays = [], remove = [] } = {}) {
165
+ // Collect baseline working-tree files.
166
+ const baseFiles = _walkWorkingTree(root).slice(0, MAX_FILES_PER_SCAN);
167
+ const fileContents = {};
168
+ const removeSet = new Set(remove);
169
+ for (const rel of baseFiles) {
170
+ if (removeSet.has(rel)) continue;
171
+ try { fileContents[rel] = fs.readFileSync(path.join(root, rel), 'utf8'); } catch {}
172
+ }
173
+ // Apply overlays — these replace or add files.
174
+ for (const o of overlays) {
175
+ if (o && typeof o.file === 'string' && typeof o.content === 'string') {
176
+ fileContents[o.file] = o.content;
177
+ }
178
+ }
179
+ // Baseline (without overlays) for delta computation.
180
+ const baseScan = await runFullScan({ fileContents: _baselineFor(fileContents, overlays, remove, root), scanRoot: root }, () => {});
181
+ const whatIfScan = await runFullScan({ fileContents, scanRoot: root }, () => {});
182
+ const baseIds = new Set((baseScan.findings || []).map(f => f.stableId || f.id));
183
+ const wIds = new Set((whatIfScan.findings || []).map(f => f.stableId || f.id));
184
+ const introduced = (whatIfScan.findings || []).filter(f => !baseIds.has(f.stableId || f.id)).map(_compact);
185
+ const removed = (baseScan.findings || []).filter(f => !wIds.has(f.stableId || f.id)).map(_compact);
186
+ return {
187
+ baselineFindings: (baseScan.findings || []).length,
188
+ whatIfFindings: (whatIfScan.findings || []).length,
189
+ delta: whatIfScan.findings.length - baseScan.findings.length,
190
+ introduced,
191
+ removed,
192
+ };
193
+ }
194
+
195
+ function _baselineFor(fileContents, overlays, remove, root) {
196
+ // Reverse the overlay: restore original content (from disk) for any
197
+ // overlay'd file; re-add any virtually-removed file. Paths are resolved
198
+ // relative to the scan root, NOT the cwd.
199
+ const base = { ...fileContents };
200
+ for (const o of overlays) {
201
+ if (!o || typeof o.file !== 'string') continue;
202
+ try { base[o.file] = fs.readFileSync(path.join(root, o.file), 'utf8'); }
203
+ catch { delete base[o.file]; }
204
+ }
205
+ for (const rel of (remove || [])) {
206
+ try { base[rel] = fs.readFileSync(path.join(root, rel), 'utf8'); } catch {}
207
+ }
208
+ return base;
209
+ }
210
+
211
+ function _walkWorkingTree(root) {
212
+ const out = [];
213
+ const exclude = new Set(['node_modules', '.git', '.venv', 'dist', 'build', '__pycache__', 'target', '.next', '.nuxt']);
214
+ function walk(dir, rel = '') {
215
+ let entries;
216
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
217
+ for (const e of entries) {
218
+ if (exclude.has(e.name)) continue;
219
+ const full = path.join(dir, e.name);
220
+ const r = rel ? `${rel}/${e.name}` : e.name;
221
+ if (e.isDirectory()) walk(full, r);
222
+ else if (e.isFile() && /\.(?:js|jsx|ts|tsx|mjs|cjs|py|java|cs|kt|go|rb|php|sol|swift|rs|tf|yml|yaml|json|toml|md)$/i.test(e.name)) {
223
+ out.push(r);
224
+ }
225
+ }
226
+ }
227
+ walk(root);
228
+ return out;
229
+ }
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // Public entry point — re-exports the runScan API and the engine internals.
2
+ export { runScan, scanPath } from './runScan.js';
3
+ export * from './engine.js';