@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,134 @@
1
+ // 0.9.0 Feat-15: Dependency confusion + typosquat detection (Levenshtein distance against top-1000 npm/PyPI packages).
2
+ //
3
+ // (a) Typosquat: Levenshtein distance 1–2 from a popular package.
4
+ // (b) Confusion: internal-scoped names (`@your-org/...`) that also appear on the
5
+ // public registry — declared via .agentic-security/internal-scopes.yml.
6
+ //
7
+ // Both checks are local-first; we only consult OSV (already cached) for the
8
+ // confusion check when an internal-scoped name appears in the public registry.
9
+
10
+ import * as fs from 'node:fs';
11
+ import * as path from 'node:path';
12
+ import * as yaml from 'js-yaml';
13
+ import { createRequire } from 'node:module';
14
+
15
+ const _require = createRequire(import.meta.url);
16
+ const _POPULAR = (() => {
17
+ try {
18
+ const raw = _require('./popular-packages.json');
19
+ const out = {};
20
+ for (const [k, v] of Object.entries(raw)) {
21
+ if (k.startsWith('_')) continue;
22
+ out[k] = new Set(v.map(s => s.toLowerCase()));
23
+ }
24
+ return out;
25
+ } catch (_) {
26
+ return null;
27
+ }
28
+ })();
29
+
30
+ // Levenshtein distance with early-exit at maxDistance.
31
+ export function levenshtein(a, b, maxDistance = 2) {
32
+ if (a === b) return 0;
33
+ if (Math.abs(a.length - b.length) > maxDistance) return maxDistance + 1;
34
+ if (a.length === 0) return b.length;
35
+ if (b.length === 0) return a.length;
36
+ let prev = Array(b.length + 1).fill(0).map((_, i) => i);
37
+ let curr = Array(b.length + 1).fill(0);
38
+ for (let i = 1; i <= a.length; i++) {
39
+ curr[0] = i;
40
+ let rowMin = i;
41
+ for (let j = 1; j <= b.length; j++) {
42
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
43
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
44
+ if (curr[j] < rowMin) rowMin = curr[j];
45
+ }
46
+ if (rowMin > maxDistance) return maxDistance + 1;
47
+ [prev, curr] = [curr, prev];
48
+ }
49
+ return prev[b.length];
50
+ }
51
+
52
+ function _loadInternalScopes(scanRoot) {
53
+ if (!scanRoot) return [];
54
+ for (const name of ['internal-scopes.yml', 'internal-scopes.yaml']) {
55
+ const p = path.join(scanRoot, '.agentic-security', name);
56
+ if (!fs.existsSync(p)) continue;
57
+ try {
58
+ const doc = yaml.load(fs.readFileSync(p, 'utf8'));
59
+ return Array.isArray(doc?.scopes) ? doc.scopes : [];
60
+ } catch (_) { return []; }
61
+ }
62
+ return [];
63
+ }
64
+
65
+ function _eqEcosystemMap(eco) {
66
+ // Engine uses 'npm' / 'pypi' / etc. — map to popular-packages keys.
67
+ if (eco === 'pypi' || eco === 'pip') return 'pypi';
68
+ return eco;
69
+ }
70
+
71
+ // Run typosquat + confusion checks against a components[] array.
72
+ // Returns logicVulns-shaped findings (kind: 'sca').
73
+ export function detectDepConfusion(components, scanRoot) {
74
+ if (!_POPULAR) return [];
75
+ const internalScopes = _loadInternalScopes(scanRoot);
76
+ const findings = [];
77
+ const seen = new Set();
78
+ for (const c of components || []) {
79
+ if (!c.name) continue;
80
+ const eco = _eqEcosystemMap(c.ecosystem);
81
+ const popularSet = _POPULAR[eco];
82
+ if (!popularSet) continue;
83
+ const lowerName = c.name.toLowerCase();
84
+ // (1) Typosquat — only run if the dep is NOT itself in the popular set
85
+ if (!popularSet.has(lowerName)) {
86
+ let bestMatch = null, bestDist = 3;
87
+ for (const popular of popularSet) {
88
+ const d = levenshtein(lowerName, popular, 2);
89
+ if (d > 0 && d <= 2 && d < bestDist) { bestMatch = popular; bestDist = d; }
90
+ }
91
+ if (bestMatch) {
92
+ const id = `dep-confusion:${c.ecosystem}:${c.name}@${c.version}:typosquat`;
93
+ if (!seen.has(id)) {
94
+ seen.add(id);
95
+ findings.push({
96
+ id, kind: 'sca', severity: bestDist === 1 ? 'critical' : 'high',
97
+ vuln: `Possible typosquat: "${c.name}" (1–2 chars from "${bestMatch}")`,
98
+ cwe: 'CWE-1357', stride: 'Tampering',
99
+ file: c.filePath || 'package.json', line: 0,
100
+ snippet: `${c.ecosystem}:${c.name}@${c.version}`,
101
+ fix: `Verify "${c.name}" is the package you actually meant. The popular package "${bestMatch}" is ${bestDist} edit(s) away — typosquat malware commonly registers names like this. Double-check the publisher, weekly downloads, and recent changes before keeping this dep.`,
102
+ package: c.name, version: c.version, ecosystem: c.ecosystem, levenshteinDistance: bestDist,
103
+ });
104
+ }
105
+ }
106
+ }
107
+ // (2) Internal-scope confusion — declared scope, but published on public registry
108
+ for (const scope of internalScopes) {
109
+ const sc = String(scope).toLowerCase();
110
+ if (lowerName.startsWith(sc + '/') || lowerName === sc) {
111
+ // Heuristic: if this dep was successfully resolved by OSV (i.e. has CVE data),
112
+ // OR if the registry returned ANY metadata for it, it's published publicly —
113
+ // which is the threat. We approximate "published publicly" by
114
+ // assuming if components.parseManifests returned the dep, the user expected it
115
+ // to be installable; the OSV / queryRegistries pipeline upstream determines
116
+ // public availability. Here we just flag it for review.
117
+ const id = `dep-confusion:${c.ecosystem}:${c.name}@${c.version}:scope`;
118
+ if (!seen.has(id)) {
119
+ seen.add(id);
120
+ findings.push({
121
+ id, kind: 'sca', severity: 'high',
122
+ vuln: `Internal-scoped package on public registry: "${c.name}"`,
123
+ cwe: 'CWE-1357', stride: 'Tampering',
124
+ file: c.filePath || 'package.json', line: 0,
125
+ snippet: `${c.ecosystem}:${c.name}@${c.version}`,
126
+ fix: `"${c.name}" matches your internal scope "${scope}", but is being resolved from the public registry. Confirm whether your private registry is configured (e.g. .npmrc / .pypirc) — if a public copy exists with the same name an attacker could publish malicious updates and your installs would silently switch.`,
127
+ package: c.name, version: c.version, ecosystem: c.ecosystem,
128
+ });
129
+ }
130
+ }
131
+ }
132
+ }
133
+ return findings;
134
+ }
@@ -0,0 +1,6 @@
1
+ // SCA submodule view of the engine — dependency vulnerability + reachability.
2
+ export {
3
+ parseManifests, queryOSV, queryRegistries,
4
+ buildReachabilitySet, computeAttackPathComponents,
5
+ markUsedVulnFunctions, VULN_FUNCTION_HINTS,
6
+ } from '../engine.js';
@@ -0,0 +1,41 @@
1
+ {
2
+ "_doc": "Top-popular packages used as the typosquat reference set. A dep with Levenshtein distance 1-2 from any of these is flagged as a potential typosquat.",
3
+ "_format": "ecosystem -> sorted array of package names",
4
+
5
+ "npm": [
6
+ "lodash", "react", "vue", "angular", "express", "next", "axios", "moment", "jquery",
7
+ "underscore", "request", "chalk", "commander", "yargs", "minimist", "dotenv",
8
+ "jsonwebtoken", "bcrypt", "bcryptjs", "passport", "uuid", "debug", "ws", "node-fetch",
9
+ "cookie", "body-parser", "cors", "helmet", "morgan", "redis", "ioredis", "mongoose",
10
+ "mongodb", "pg", "mysql", "mysql2", "sequelize", "knex", "prisma", "typeorm",
11
+ "fs-extra", "glob", "rimraf", "mkdirp", "fast-glob", "chokidar", "anymatch",
12
+ "lodash.merge", "lodash.set", "lodash.get", "lodash.uniq", "lodash.clonedeep",
13
+ "react-dom", "react-router", "react-router-dom", "redux", "react-redux",
14
+ "vue-router", "vuex", "pinia", "@reduxjs/toolkit",
15
+ "typescript", "ts-node", "tsx", "esbuild", "vite", "webpack", "rollup", "parcel",
16
+ "babel", "@babel/core", "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript",
17
+ "eslint", "prettier", "husky", "lint-staged", "stylelint",
18
+ "jest", "mocha", "chai", "ava", "tap", "vitest", "cypress", "playwright", "puppeteer",
19
+ "supertest", "sinon", "nock", "msw",
20
+ "openai", "anthropic", "@anthropic-ai/sdk", "langchain", "@ai-sdk/openai",
21
+ "stripe", "twilio", "sendgrid", "mailgun", "@sentry/node", "newrelic", "datadog",
22
+ "fastify", "koa", "hapi", "nestjs", "@nestjs/core",
23
+ "yarn", "pnpm", "npm-check-updates", "depcheck", "snyk", "audit-ci"
24
+ ],
25
+ "pypi": [
26
+ "requests", "django", "flask", "fastapi", "starlette", "uvicorn", "gunicorn",
27
+ "numpy", "pandas", "scipy", "matplotlib", "seaborn", "plotly", "bokeh",
28
+ "pytest", "unittest", "nose", "tox", "coverage",
29
+ "celery", "redis", "kombu", "pika",
30
+ "sqlalchemy", "psycopg2", "pymongo", "asyncpg", "aioredis", "aiomysql",
31
+ "boto3", "botocore", "google-cloud-storage", "azure-storage-blob",
32
+ "openai", "anthropic", "langchain", "langgraph", "transformers", "torch", "tensorflow",
33
+ "scikit-learn", "xgboost", "lightgbm",
34
+ "pillow", "opencv-python", "lxml", "beautifulsoup4", "selenium", "playwright",
35
+ "pyyaml", "toml", "click", "rich", "typer", "pydantic", "marshmallow",
36
+ "cryptography", "pyjwt", "bcrypt", "argon2-cffi", "passlib",
37
+ "twisted", "tornado", "aiohttp", "httpx", "urllib3",
38
+ "jinja2", "mako", "chameleon",
39
+ "jupyter", "ipython", "notebook", "jupyterlab"
40
+ ]
41
+ }
@@ -0,0 +1,187 @@
1
+ // 0.7.0 Feat-7: SARIF 2.1.0 ingest — normalizes and merges external scanner findings into unified report.
2
+ //
3
+ // Reads SARIF 2.1.0 from gitleaks / Semgrep / Bandit / Trivy / Checkov / SonarQube
4
+ // and merges into our scan. Findings are normalised + deduplicated against our
5
+ // own findings via a fingerprint of (CWE/CVE, file, line ±2, rule), and a
6
+ // `sources[]` array is added to the merged finding so attribution is preserved.
7
+ //
8
+ // Pure ESM, no external deps. Reads JSON only — no schema validation library.
9
+
10
+ import * as fs from 'node:fs';
11
+ import * as crypto from 'node:crypto';
12
+
13
+ // Tool-name → (kind, defaultSeverityIfUnset)
14
+ const TOOL_PROFILE = {
15
+ 'semgrep': { kind: 'sast', defSev: 'medium' },
16
+ 'opengrep': { kind: 'sast', defSev: 'medium' },
17
+ 'bandit': { kind: 'sast', defSev: 'medium' },
18
+ 'eslint': { kind: 'sast', defSev: 'low' },
19
+ 'sonarqube': { kind: 'sast', defSev: 'medium' },
20
+ 'codeql': { kind: 'sast', defSev: 'high' },
21
+ 'gitleaks': { kind: 'secret', defSev: 'high' },
22
+ 'trufflehog': { kind: 'secret', defSev: 'high' },
23
+ 'detect-secrets':{ kind: 'secret', defSev: 'high' },
24
+ 'trivy': { kind: 'sca', defSev: 'high' },
25
+ 'grype': { kind: 'sca', defSev: 'high' },
26
+ 'osv-scanner': { kind: 'sca', defSev: 'high' },
27
+ 'checkov': { kind: 'iac', defSev: 'medium' },
28
+ 'tfsec': { kind: 'iac', defSev: 'medium' },
29
+ 'kics': { kind: 'iac', defSev: 'medium' },
30
+ };
31
+
32
+ // SARIF level → our severity tier
33
+ const LEVEL_MAP = { error: 'critical', warning: 'high', note: 'medium', none: 'low' };
34
+
35
+ // Normalize a tool name into a TOOL_PROFILE key (case + version-agnostic)
36
+ function _toolKey(rawName) {
37
+ if (!rawName) return null;
38
+ const n = String(rawName).toLowerCase().replace(/[^a-z0-9-]/g, '');
39
+ for (const k of Object.keys(TOOL_PROFILE)) if (n.includes(k)) return k;
40
+ return null;
41
+ }
42
+
43
+ function _fingerprint(file, line, vuln, cwe) {
44
+ // Bucketed line (±2) so that off-by-one rule reports merge correctly.
45
+ const bucket = Math.floor((line || 0) / 2) * 2;
46
+ const key = `${file}:${bucket}:${(cwe || '').toUpperCase()}:${(vuln || '').replace(/\W+/g, '_').toLowerCase()}`;
47
+ return crypto.createHash('sha256').update(key).digest('hex').slice(0, 16);
48
+ }
49
+
50
+ function _extractCWE(rule) {
51
+ // SARIF rules can carry CWE in `properties.tags`, `properties.cwe`, or `helpUri`.
52
+ const tags = rule?.properties?.tags || [];
53
+ for (const t of tags) {
54
+ const m = String(t).match(/CWE-?(\d+)/i);
55
+ if (m) return `CWE-${m[1]}`;
56
+ }
57
+ if (rule?.properties?.cwe) {
58
+ const m = String(rule.properties.cwe).match(/(\d+)/);
59
+ if (m) return `CWE-${m[1]}`;
60
+ }
61
+ if (rule?.helpUri) {
62
+ const m = rule.helpUri.match(/cwe\.mitre\.org\/data\/definitions\/(\d+)/i);
63
+ if (m) return `CWE-${m[1]}`;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ function _extractCVE(result) {
69
+ // Trivy / Grype / osv-scanner put CVE in ruleId or properties.
70
+ const id = result?.ruleId || '';
71
+ const m = id.match(/(CVE-\d{4}-\d{4,7})/i);
72
+ if (m) return m[1].toUpperCase();
73
+ const tags = result?.properties?.tags || [];
74
+ for (const t of tags) {
75
+ const tm = String(t).match(/(CVE-\d{4}-\d{4,7})/i);
76
+ if (tm) return tm[1].toUpperCase();
77
+ }
78
+ return null;
79
+ }
80
+
81
+ // Convert a single SARIF result + its run context into our normalized finding.
82
+ function _resultToFinding(result, run, toolKey, defSev) {
83
+ const loc = result.locations?.[0]?.physicalLocation;
84
+ const file = loc?.artifactLocation?.uri || result?.fingerprints?.path || '(unknown)';
85
+ const line = loc?.region?.startLine || 0;
86
+ const ruleId = result.ruleId || '';
87
+ const rule = (run.tool?.driver?.rules || []).find(r => r.id === ruleId);
88
+ const message = result.message?.text || rule?.fullDescription?.text || rule?.shortDescription?.text || ruleId;
89
+ const sev = LEVEL_MAP[result.level] || defSev;
90
+ const cwe = _extractCWE(rule || result);
91
+ const cve = _extractCVE(result);
92
+ const profile = TOOL_PROFILE[toolKey] || { kind: 'sast', defSev: 'medium' };
93
+ return {
94
+ file: file.replace(/^file:\/\//, ''),
95
+ line,
96
+ severity: sev,
97
+ vuln: cve ? `${cve}: ${message}`.slice(0, 240) : message.slice(0, 240),
98
+ cwe,
99
+ cve,
100
+ kind: profile.kind,
101
+ ruleId,
102
+ snippet: result.locations?.[0]?.physicalLocation?.contextRegion?.snippet?.text
103
+ || result.locations?.[0]?.physicalLocation?.region?.snippet?.text
104
+ || '',
105
+ sources: [run.tool?.driver?.name || toolKey],
106
+ };
107
+ }
108
+
109
+ // Load and parse a SARIF file. Returns an array of normalized findings.
110
+ export function ingestSARIFFile(filePath) {
111
+ let raw;
112
+ try { raw = fs.readFileSync(filePath, 'utf8'); }
113
+ catch (e) { throw new Error(`Cannot read SARIF file ${filePath}: ${e.message}`); }
114
+ let doc;
115
+ try { doc = JSON.parse(raw); }
116
+ catch (e) { throw new Error(`Invalid JSON in SARIF file ${filePath}: ${e.message}`); }
117
+ if (!doc || !Array.isArray(doc.runs)) {
118
+ throw new Error(`Not a SARIF document (missing runs[]): ${filePath}`);
119
+ }
120
+ const out = [];
121
+ for (const run of doc.runs) {
122
+ const toolName = run.tool?.driver?.name;
123
+ const toolKey = _toolKey(toolName);
124
+ const profile = TOOL_PROFILE[toolKey] || { kind: 'sast', defSev: 'medium' };
125
+ for (const result of (run.results || [])) {
126
+ try { out.push(_resultToFinding(result, run, toolKey || (toolName||'').toLowerCase(), profile.defSev)); }
127
+ catch (_) {}
128
+ }
129
+ }
130
+ return out;
131
+ }
132
+
133
+ // Merge an array of external findings into the scan result. Mutates `scan` in place.
134
+ // Behaviour:
135
+ // - Findings whose fingerprint matches an existing scan.findings entry have their
136
+ // source tool name appended to scan.findings[i].sources[] and (if the external
137
+ // finding's severity is higher) the existing severity is bumped.
138
+ // - New findings (no match) are appended to scan.findings, scan.secrets, or
139
+ // scan.supplyChain depending on `kind`.
140
+ export function mergeSARIFFindings(scan, externals) {
141
+ // Index existing findings by fingerprint
142
+ const existingFP = new Map();
143
+ for (const f of (scan.findings || [])) {
144
+ const fp = _fingerprint((f.file||'').split(' -> ').pop(), f.line||f.source?.line||f.sink?.line||0, f.vuln||f.type, f.cwe);
145
+ existingFP.set(fp, f);
146
+ }
147
+ let merged = 0, added = 0;
148
+ const SEV_RANK = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
149
+ for (const ext of externals) {
150
+ const fp = _fingerprint(ext.file, ext.line, ext.vuln, ext.cwe);
151
+ const existing = existingFP.get(fp);
152
+ if (existing) {
153
+ existing.sources = Array.from(new Set([...(existing.sources || ['agentic-security']), ...ext.sources]));
154
+ // Bump severity if the external tool reported higher
155
+ if ((SEV_RANK[ext.severity] || 0) > (SEV_RANK[existing.severity] || 0)) {
156
+ existing.severity = ext.severity;
157
+ }
158
+ merged++;
159
+ continue;
160
+ }
161
+ // Append as new finding into the right bucket
162
+ const id = `sarif-ingest:${fp}`;
163
+ const newFinding = {
164
+ id, kind: ext.kind, severity: ext.severity, vuln: ext.vuln,
165
+ cwe: ext.cwe, file: ext.file, line: ext.line, snippet: ext.snippet || '',
166
+ sources: ext.sources, parser: 'SARIF',
167
+ };
168
+ if (ext.kind === 'secret') (scan.secrets = scan.secrets || []).push(newFinding);
169
+ else if (ext.kind === 'sca') (scan.supplyChain = scan.supplyChain || []).push({ ...newFinding, type: 'vulnerable_dep' });
170
+ else if (ext.kind === 'iac' || ext.kind === 'sast' || ext.kind === 'logic') (scan.findings = scan.findings || []).push(newFinding);
171
+ added++;
172
+ }
173
+ return { merged, added };
174
+ }
175
+
176
+ export function ingestAndMerge(scan, sarifPaths) {
177
+ let totalMerged = 0, totalAdded = 0;
178
+ for (const p of sarifPaths) {
179
+ let externals;
180
+ try { externals = ingestSARIFFile(p); }
181
+ catch (e) { /* swallow per-file errors so one bad SARIF doesn't break the run */ continue; }
182
+ const { merged, added } = mergeSARIFFindings(scan, externals);
183
+ totalMerged += merged;
184
+ totalAdded += added;
185
+ }
186
+ return { merged: totalMerged, added: totalAdded };
187
+ }
@@ -0,0 +1,89 @@
1
+ {
2
+ "_doc": "package -> [vulnerable function names]. Used by markUsedVulnFunctions and _annotateFunctionReachability to determine whether developer code actually calls a vulnerable symbol in a flagged dep. Reachability analysis typically eliminates ~97% of false-positive SCA noise.",
3
+ "_format": "key: package name (npm/PyPI casing); value: array of function names known-vulnerable in at least one CVE for that package",
4
+
5
+ "lodash": ["merge", "defaultsDeep", "set", "setWith", "zipObjectDeep", "template", "templateSettings"],
6
+ "jsonwebtoken": ["decode", "verify", "sign"],
7
+ "marked": ["parse", "marked", "Lexer"],
8
+ "ejs": ["render", "renderFile", "compile"],
9
+ "node-fetch": ["default", "fetch"],
10
+ "xml2js": ["parseString", "parseStringPromise"],
11
+ "js-yaml": ["load", "loadAll"],
12
+ "minimist": ["parse"],
13
+ "yargs-parser": ["parse"],
14
+ "ms": ["ms"],
15
+ "moment": ["moment", "duration"],
16
+ "axios": ["get", "post", "request", "create"],
17
+ "express": ["use", "static", "json"],
18
+ "cookie": ["parse", "serialize"],
19
+ "qs": ["parse", "stringify"],
20
+ "tough-cookie": ["CookieJar", "Cookie"],
21
+ "ws": ["createServer", "Server"],
22
+ "http-proxy": ["createProxyServer"],
23
+ "send": ["send", "mime"],
24
+ "serve-static": ["serveStatic"],
25
+ "tar": ["x", "extract", "Parse"],
26
+ "tar-fs": ["extract", "x"],
27
+ "node-tar": ["x", "extract", "Parse"],
28
+ "decompress": ["decompress"],
29
+ "fast-xml-parser": ["parse", "XMLParser"],
30
+ "handlebars": ["compile", "registerHelper", "SafeString"],
31
+ "pug": ["compile", "render", "renderFile"],
32
+ "mustache": ["render", "to_html"],
33
+ "dompurify": ["sanitize"],
34
+ "validator": ["isURL", "isEmail", "escape"],
35
+ "sanitize-html": ["sanitizeHtml"],
36
+ "shell-quote": ["parse", "quote"],
37
+ "shelljs": ["exec", "config"],
38
+ "express-jwt": ["expressJwt"],
39
+ "passport": ["authenticate", "use"],
40
+ "passport-jwt": ["Strategy"],
41
+ "bcrypt": ["compare", "hash", "compareSync", "hashSync"],
42
+ "bcryptjs": ["compare", "hash", "compareSync", "hashSync"],
43
+ "crypto-js": ["AES", "DES", "MD5", "SHA1"],
44
+ "node-forge": ["pki", "rsa", "asn1", "util"],
45
+ "openssl": ["createCipher", "createDecipher"],
46
+ "request": ["get", "post", "request"],
47
+ "got": ["get", "post"],
48
+ "ssh2": ["Client", "Server"],
49
+ "mongodb": ["MongoClient", "ObjectId"],
50
+ "mongoose": ["model", "Schema"],
51
+ "sqlite3": ["Database", "run", "exec"],
52
+ "pg": ["Client", "Pool", "query"],
53
+ "mysql": ["createConnection", "createPool", "query"],
54
+ "mysql2": ["createConnection", "createPool", "query"],
55
+ "redis": ["createClient"],
56
+ "ioredis": ["Redis"],
57
+ "multer": ["diskStorage", "memoryStorage"],
58
+ "formidable": ["IncomingForm"],
59
+ "busboy": ["Busboy"],
60
+ "fast-redact": ["fastRedact"],
61
+ "pino": ["pino"],
62
+ "winston": ["createLogger"],
63
+ "ansi-html": ["ansiHTML"],
64
+ "underscore": ["template", "templateSettings"],
65
+ "vm2": ["VM", "NodeVM"],
66
+ "vm": ["runInNewContext", "runInThisContext", "Script"],
67
+ "object-path": ["set", "get", "del"],
68
+ "set-value": ["set"],
69
+ "deepmerge": ["all"],
70
+ "merge": ["recursive", "merge"],
71
+ "got-resolve-fn": ["resolveAlpnUsingNetwork"],
72
+ "follow-redirects":["http", "https"],
73
+
74
+ "_python_block_": "Python packages — keyed identically; engine ignores subsection markers",
75
+ "django": ["Template", "render"],
76
+ "flask": ["render_template_string", "send_file"],
77
+ "jinja2": ["Template", "render", "Environment"],
78
+ "pyyaml": ["load", "load_all"],
79
+ "pyjwt": ["decode", "encode"],
80
+ "requests": ["get", "post", "request"],
81
+ "urllib3": ["urlopen", "PoolManager"],
82
+ "lxml": ["fromstring", "parse"],
83
+ "pillow": ["open", "Image"],
84
+ "cryptography": ["Fernet", "primitives"],
85
+ "paramiko": ["SSHClient", "AutoAddPolicy"],
86
+ "sqlparse": ["parse"],
87
+ "psycopg2": ["connect"],
88
+ "sqlalchemy": ["create_engine", "text"]
89
+ }
@@ -0,0 +1,4 @@
1
+ // Secrets submodule view of the engine — credential + entropy + TODO scanning.
2
+ export {
3
+ scanCredentials, scanEntropySecrets, scanTodosNearSecurity,
4
+ } from '../engine.js';