@clear-capabilities/agentic-security-scanner 0.145.0 → 0.147.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 (162) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/bin/agentic-security.js +3508 -69
  3. package/dist/1122.index.js +702 -0
  4. package/dist/{301.index.js → 1301.index.js} +2 -2
  5. package/dist/1379.index.js +591 -0
  6. package/dist/{444.index.js → 1444.index.js} +2 -2
  7. package/dist/{660.index.js → 1660.index.js} +2 -2
  8. package/dist/{700.index.js → 1700.index.js} +2 -2
  9. package/dist/{905.index.js → 1905.index.js} +3 -3
  10. package/dist/{920.index.js → 1920.index.js} +3 -3
  11. package/dist/{238.index.js → 2238.index.js} +3 -3
  12. package/dist/{271.index.js → 2271.index.js} +2 -2
  13. package/dist/{985.index.js → 2376.index.js} +1260 -340
  14. package/dist/2432.index.js +793 -0
  15. package/dist/2659.index.js +93 -0
  16. package/dist/{826.index.js → 2826.index.js} +2 -2
  17. package/dist/{830.index.js → 2830.index.js} +2 -2
  18. package/dist/2923.index.js +298 -0
  19. package/dist/{1.index.js → 3001.index.js} +5 -5
  20. package/dist/{117.index.js → 3117.index.js} +3 -3
  21. package/dist/3180.index.js +307 -0
  22. package/dist/3276.index.js +117 -0
  23. package/dist/{415.index.js → 3415.index.js} +2 -2
  24. package/dist/{499.index.js → 3499.index.js} +2 -2
  25. package/dist/3518.index.js +450 -0
  26. package/dist/{526.index.js → 3526.index.js} +5 -5
  27. package/dist/{736.index.js → 3736.index.js} +4 -4
  28. package/dist/{839.index.js → 3839.index.js} +4 -4
  29. package/dist/{113.index.js → 4113.index.js} +5 -5
  30. package/dist/{265.index.js → 4265.index.js} +2 -2
  31. package/dist/{384.index.js → 4384.index.js} +3 -3
  32. package/dist/4547.index.js +268 -0
  33. package/dist/4863.index.js +422 -0
  34. package/dist/{970.index.js → 4970.index.js} +2 -2
  35. package/dist/5051.index.js +770 -0
  36. package/dist/{144.index.js → 5144.index.js} +5 -5
  37. package/dist/{333.index.js → 5333.index.js} +3 -3
  38. package/dist/5343.index.js +185 -0
  39. package/dist/5350.index.js +866 -0
  40. package/dist/5561.index.js +436 -0
  41. package/dist/{637.index.js → 5637.index.js} +3 -3
  42. package/dist/{449.index.js → 5830.index.js} +2 -2
  43. package/dist/6626.index.js +532 -0
  44. package/dist/6662.index.js +297 -0
  45. package/dist/{675.index.js → 6675.index.js} +5 -5
  46. package/dist/{730.index.js → 6730.index.js} +6 -6
  47. package/dist/6829.index.js +225 -0
  48. package/dist/6944.index.js +130 -0
  49. package/dist/{178.index.js → 7178.index.js} +3 -3
  50. package/dist/{227.index.js → 7227.index.js} +2 -2
  51. package/dist/7310.index.js +520 -0
  52. package/dist/{552.index.js → 7552.index.js} +4 -4
  53. package/dist/7709.index.js +78 -0
  54. package/dist/8218.index.js +160 -0
  55. package/dist/{476.index.js → 8476.index.js} +4 -4
  56. package/dist/{513.index.js → 8513.index.js} +5 -5
  57. package/dist/{520.index.js → 8520.index.js} +2 -2
  58. package/dist/{718.index.js → 8718.index.js} +2 -2
  59. package/dist/{752.index.js → 8752.index.js} +2 -2
  60. package/dist/8846.index.js +100 -0
  61. package/dist/{435.index.js → 9091.index.js} +560 -189
  62. package/dist/{207.index.js → 9207.index.js} +2 -2
  63. package/dist/{220.index.js → 9220.index.js} +2 -2
  64. package/dist/9390.index.js +163 -0
  65. package/dist/{503.index.js → 9503.index.js} +2 -2
  66. package/dist/{801.index.js → 9801.index.js} +2 -2
  67. package/dist/{824.index.js → 9824.index.js} +2 -2
  68. package/dist/agentic-security.mjs +16 -16
  69. package/dist/agentic-security.mjs.sha256 +1 -1
  70. package/dist/compliance-frameworks/hipaa-security-rule.json +3 -2
  71. package/package.json +12 -5
  72. package/src/dataflow/CLAUDE.md +1 -1
  73. package/src/dataflow/catalog.js +42 -0
  74. package/src/dataflow/orm-write-catalog.js +175 -0
  75. package/src/engine.js +92 -1
  76. package/src/ir/CLAUDE.md +1 -0
  77. package/src/ir/chrome-probe.mjs +150 -0
  78. package/src/ir/parser-js.js +94 -7
  79. package/src/lineage/CLAUDE.md +1203 -0
  80. package/src/lineage/DESIGN_DESTINATION_RESOLVER.md +156 -0
  81. package/src/lineage/DESIGN_GRAPH_BUILDER.md +938 -0
  82. package/src/lineage/DESIGN_HANDLING_ANALYZER.md +355 -0
  83. package/src/lineage/DESIGN_INTRAPROCEDURAL.md +628 -0
  84. package/src/lineage/DESIGN_PATH_PROVENANCE.md +3451 -0
  85. package/src/lineage/DESIGN_QUEUE_DETAIL.md +120 -0
  86. package/src/lineage/DESIGN_REGISTRIES.md +880 -0
  87. package/src/lineage/DESIGN_STORE_DETAIL.md +143 -0
  88. package/src/lineage/DESIGN_TRANSIT_PROTECTION.md +245 -0
  89. package/src/lineage/classification.js +56 -0
  90. package/src/lineage/coverage.js +658 -0
  91. package/src/lineage/cross-repo-link.js +107 -0
  92. package/src/lineage/dataflow-graph.schema.json +184 -0
  93. package/src/lineage/decision-story.js +206 -0
  94. package/src/lineage/drift-policy.js +279 -0
  95. package/src/lineage/driver.js +135 -0
  96. package/src/lineage/engine.js +992 -0
  97. package/src/lineage/export-briefing.js +628 -0
  98. package/src/lineage/export-csv.js +62 -0
  99. package/src/lineage/export-json.js +238 -0
  100. package/src/lineage/export-privacy.js +258 -0
  101. package/src/lineage/federation-loader.js +111 -0
  102. package/src/lineage/field-identity.js +78 -0
  103. package/src/lineage/fixtures/build-flagship-fixture.mjs +272 -0
  104. package/src/lineage/fixtures/flagship-graph.json +1453 -0
  105. package/src/lineage/flow-grade.js +221 -0
  106. package/src/lineage/governance-edit.js +169 -0
  107. package/src/lineage/graph-builder.js +1114 -0
  108. package/src/lineage/graph-diff.js +431 -0
  109. package/src/lineage/graph-snapshot.js +180 -0
  110. package/src/lineage/handling-analyzer.js +168 -0
  111. package/src/lineage/ids.js +349 -0
  112. package/src/lineage/impact-assessment.js +76 -0
  113. package/src/lineage/impact-engine.js +268 -0
  114. package/src/lineage/index.js +281 -0
  115. package/src/lineage/language-coverage-tiers.js +58 -0
  116. package/src/lineage/obligation-mapping.js +126 -0
  117. package/src/lineage/obligation-predicates.js +235 -0
  118. package/src/lineage/observation-adapters.js +282 -0
  119. package/src/lineage/observation-correlation.js +622 -0
  120. package/src/lineage/observation-store.js +497 -0
  121. package/src/lineage/path-query.js +410 -0
  122. package/src/lineage/path-store.js +400 -0
  123. package/src/lineage/protection.js +53 -0
  124. package/src/lineage/recipient-profile.js +192 -0
  125. package/src/lineage/recipient-registry.js +394 -0
  126. package/src/lineage/redact-graph.js +224 -0
  127. package/src/lineage/remediation.js +417 -0
  128. package/src/lineage/resolve-destination.js +91 -0
  129. package/src/lineage/runtime-observation.js +464 -0
  130. package/src/lineage/scenario-diff.js +84 -0
  131. package/src/lineage/scenario-engine.js +251 -0
  132. package/src/lineage/scenario.js +101 -0
  133. package/src/lineage/schema.js +167 -0
  134. package/src/lineage/sink-registry.js +427 -0
  135. package/src/lineage/source-registry.js +357 -0
  136. package/src/lineage/source-seeding.js +212 -0
  137. package/src/lineage/summaries.js +590 -0
  138. package/src/lineage/transform-catalog.js +397 -0
  139. package/src/lineage/transit-protection.js +150 -0
  140. package/src/lineage/validate.js +285 -0
  141. package/src/mcp/CLAUDE.md +7 -1
  142. package/src/mcp/dataflow-tools.js +160 -0
  143. package/src/mcp/server.js +1 -1
  144. package/src/mcp/tools.js +2 -1
  145. package/src/pipeline/scan-health.js +19 -1
  146. package/src/posture/CLAUDE.md +19 -0
  147. package/src/posture/artifact-registry.js +52 -0
  148. package/src/posture/auditor-walkthrough.js +76 -0
  149. package/src/posture/compliance-frameworks/hipaa-security-rule.json +3 -2
  150. package/src/posture/obligation-evidence-pack.js +202 -0
  151. package/src/posture/remediation-ledger.js +337 -0
  152. package/src/server/CLAUDE.md +47 -0
  153. package/src/server/graph-loader.js +141 -0
  154. package/src/server/http-server.js +325 -0
  155. package/src/server/routes.js +129 -0
  156. package/src/server/security.js +111 -0
  157. package/src/server/static-assets.js +139 -0
  158. package/dist/11.index.js +0 -353
  159. package/dist/259.index.js +0 -975
  160. package/dist/317.index.js +0 -300
  161. package/dist/609.index.js +0 -741
  162. package/dist/838.index.js +0 -152
@@ -1 +1 @@
1
- 72173ed6629d8035f14bf6bce9c6f268d40d179aeff64167dd471e8a829ef189 agentic-security.mjs
1
+ 0cfc21d9bdecf4422a0934216444b948dccc8c4c978b0f7eca92d318d8b61e9c agentic-security.mjs
@@ -5,7 +5,7 @@
5
5
  "license": "US Federal regulation (public)",
6
6
  "url": "https://www.ecfr.gov/current/title-45/subtitle-A/subchapter-C/part-164",
7
7
  "scope": "SELECTIVE SUBSET. 8 of the Security Rule technical safeguards. Administrative and physical safeguards are outside what a code scanner can observe and are NOT represented here.",
8
- "controlsDigest": "a99f9c4015ffbf72",
8
+ "controlsDigest": "7e94038dd9a64d3d",
9
9
  "controlCount": 8,
10
10
  "controls": [
11
11
  {
@@ -102,7 +102,8 @@
102
102
  ],
103
103
  "mapsTo": [
104
104
  "family:crypto-tls-no-verify",
105
- "family:crypto-tls-version"
105
+ "family:crypto-tls-version",
106
+ "graph:transit-protection:PHI:external:transit:protected"
106
107
  ]
107
108
  }
108
109
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.145.0",
3
+ "version": "0.147.0",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin — SAST, SCA (function-level reachability + CISA KEV), secrets, IaC, prompt-injection, MCP/agent-tool audit, auth/authZ deep analysis, attack chains, PoC generation, business logic, toxic-combinations scoring, SBOM, pipeline integrity, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -59,16 +59,18 @@
59
59
  "build": "ncc build bin/agentic-security.js -o dist --minify -e web-tree-sitter -e tree-sitter-wasms && rm -rf dist/compliance-frameworks && mkdir -p dist/compliance-frameworks && cp src/posture/compliance-frameworks/*.json dist/compliance-frameworks/ && mv dist/index.js dist/agentic-security.mjs && rm -f dist/package.json && chmod +x dist/agentic-security.mjs && node -e \"const fs=require('fs');const p='dist/agentic-security.mjs';const c=fs.readFileSync(p,'utf8');if(!c.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+c);\" && node -e \"const fs=require('fs');const c=require('crypto');const h=c.createHash('sha256').update(fs.readFileSync('dist/agentic-security.mjs')).digest('hex');fs.writeFileSync('dist/agentic-security.mjs.sha256',h+' agentic-security.mjs\\n');\"",
60
60
  "prepare": "node ../scripts/pre-push-gate.mjs --install-hook",
61
61
  "prepublishOnly": "npm run build && node ../scripts/sync-scanner-changelog.mjs && node ../scripts/release-check.mjs",
62
- "test": "node ../scripts/run-unit-tests.mjs && AGENTIC_SECURITY_CPP_DATAFLOW=1 node --test test/cpp-dataflow.test.js && node --experimental-test-module-mocks --test test/fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection-k8s-admission.test.js && npm run test:python",
62
+ "test": "node ../scripts/run-unit-tests.mjs && AGENTIC_SECURITY_CPP_DATAFLOW=1 node --test test/cpp-dataflow.test.js && node --experimental-test-module-mocks --test test/fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection-k8s-admission.test.js && node --experimental-test-module-mocks --test test/lineage-fault-injection.test.js && npm run test:python",
63
63
  "test:python": "node ../scripts/run-python-tests.mjs",
64
64
  "test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
65
65
  "test:glob": "node --test test/glob-compat.test.js",
66
66
  "test:sast": "node --test test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/reachability-missing-callgraph-data.test.js test/rate-limit.test.js test/auth-signals.test.js test/convention-deviation.test.js test/resource-exhaustion.test.js test/resource-exhaustion-realworld.test.js test/redirect-toctou.test.js test/redirect-toctou-realworld.test.js test/codegen-sink.test.js test/ownership-authz.test.js test/ownership-authz-realworld.test.js test/t21-precision-audit.test.js test/secrets-coverage.test.js test/prompt-injection-payloads.test.js test/iac-cloud-templates.test.js test/ruby-path-join.test.js test/sast/manifest-line-tracking.test.js",
67
- "test:posture": "node --test test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/release-workflow.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js",
68
- "test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js test/dataflow/provenance-pipeline-integration.test.js",
69
- "test:mcp": "node --test test/mcp.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js",
67
+ "test:posture": "node --test test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/release-workflow.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js",
68
+ "test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/chrome-probe.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js test/catalog-ai-model-provider-precision.test.js test/catalog-orm-write.test.js test/dataflow/provenance-pipeline-integration.test.js",
69
+ "test:mcp": "node --test test/mcp.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js test/mcp-dataflow-tools.test.js test/cli/governance-propose-edit.test.js test/cli/remediation-open-update.test.js test/cli/remediation-verify-reopen.test.js test/cli/dataflow-observations.test.js test/cli/federate-declare-list.test.js",
70
70
  "test:report": "node --test test/junit.test.js test/ci.test.js test/poc-generator.test.js test/verifier.test.js test/verifier-target.test.js test/annotator-errors.test.js test/scan-health.test.js test/finding-schema.test.js test/pipeline/finding-schema-provenance.test.js test/evidence-provenance.test.js test/report/provenance-output.test.js test/report/provenance-format-parity.test.js test/legacy-compat.test.js test/annotator-runner.test.js test/detector-runner.test.js test/detector-errors.test.js test/analyzer-supervisor.test.js test/cascade-worker-pool.test.js test/cascade-pool-wiring.test.js test/coverage-ledger.test.js test/coverage-ledger-completeness.test.js test/assurance-mode.test.js test/artifact-registry.test.js test/artifact-registry-completeness.test.js test/retention-policy.test.js test/state-lifecycle-report.test.js test/legal-hold.test.js test/production-feedback.test.js test/calibration-feedback.test.js test/encryption-provider.test.js test/grader-calibration.test.js test/pr-delta-gate.test.js test/vex.test.js test/report-render.test.js test/oscal-conformance.test.js test/cli-fix-apply.test.js test/apply-fix-service.test.js test/high-impact-approval-gate.test.js test/approver-registry.test.js test/suppression-exceptions.test.js test/fix-history.test.js test/producer-registry.test.js test/producer-collector.test.js test/enrichment-completion.test.js",
71
+ "test:lineage": "node --test test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js",
71
72
  "test:bench-modules": "node --test test/phase4-harness.test.js test/pipeline.test.js test/proof-corpus-lib.test.js test/proof-corpus-runner.test.js",
73
+ "test:server": "node --test test/server/graph-loader.test.js test/server/security.test.js test/server/routes.test.js test/server/http-server.test.js test/server/cmd-explore.test.js test/server/cmd-dataflow-export.test.js test/server/static-assets.test.js test/server/dataflow-scenario-cli.test.js test/server/dataflow-impact-cli.test.js",
72
74
  "test:lifecycle": "node --test test/lsp-protocol-smoke.test.js test/ide-surfaces.test.js test/detector-liveness.test.js test/bench-watchdog.test.js test/ci-parity.test.js test/dead-code.test.js test/no-dead-modules.test.js test/no-orphan-scripts.test.js test/no-stray-state.test.js test/tree-integrity.test.js test/stop-hook.test.js test/plugin-self-check.test.js test/skills-registry.test.js test/bodyguard.test.js test/cache-invalidator-guard.test.js test/dispatch-pre-tool.test.js test/dispatch-user-prompt.test.js test/session-start-model-capture.test.js test/sandbox.test.js test/sandbox-escape.test.js test/ci-templates.test.js test/agent-mcp-tools.test.js test/triage-command.test.js test/no-orphan-tests.test.js test/supply-command.test.js test/posture-command.test.js test/labs-command.test.js test/compliance-command.test.js test/check-doc-drift.test.js test/dist-chunks-tracked.test.js && node ../scripts/lint-command-descriptions.mjs",
73
75
  "test:eval": "node --test test/independent-eval.test.js test/realworld-recall.test.js",
74
76
  "test:discovery": "node --test test/discovery-partition.test.js test/discovery-lenses.test.js test/discovery-hunter.test.js test/discovery-confirm.test.js test/discovery-disprove.test.js test/discovery-judge.test.js test/discovery-run.test.js test/discovery-wiring.test.js test/discovery-llm-invoke.test.js test/local-only-mode.test.js",
@@ -81,6 +83,8 @@
81
83
  "prebench:realworld": "npm run build",
82
84
  "bench:realworld": "node test/benchmark/realworld/bench-realworld.js",
83
85
  "bench:llm-goats": "node test/benchmark/llm-goats/bench-llm-goats.js",
86
+ "bench:data-lineage": "node ../bench/data-lineage/runner.mjs",
87
+ "bench:data-lineage:check": "node ../bench/data-lineage/runner.mjs --check",
84
88
  "bench:cve-replay": "node ../bench/cve-replay/runner.mjs",
85
89
  "bench:cve-replay:ci": "node ../bench/cve-replay/runner.mjs --ci",
86
90
  "bench:cve-replay:check": "node ../bench/cve-replay/runner.mjs --check-baseline",
@@ -109,6 +113,7 @@
109
113
  "bench:self-scan": "node ../bench/self-scan/measure.mjs",
110
114
  "bench:self-scan:check": "node ../bench/self-scan/check.mjs",
111
115
  "bench:mutation:check": "node ../bench/mutation/runner.mjs",
116
+ "bench:protection-verdict:check": "node ../bench/protection-verdict/runner.mjs",
112
117
  "bench:fix-correctness": "node ../bench/fix-correctness/runner.mjs",
113
118
  "bench:iac-coverage": "node ../bench/iac-coverage/runner.mjs",
114
119
  "bench:prompt-injection": "node ../bench/prompt-injection/runner.mjs",
@@ -125,12 +130,14 @@
125
130
  "bench:provenance-accuracy:check": "node ../bench/provenance-accuracy/runner.mjs --check",
126
131
  "bench:provenance-accuracy:update-baseline": "node ../bench/provenance-accuracy/runner.mjs --update-baseline",
127
132
  "bench:privacy-recall": "node ../bench/privacy-recall/measure.mjs",
133
+ "bench:privacy-recall:compare-lineage": "node ../bench/privacy-recall/compare-lineage.mjs",
128
134
  "bench:privacy-recall:check": "node ../bench/privacy-recall/check.mjs",
129
135
  "bench:privacy-recall:update-baseline": "node ../bench/privacy-recall/check.mjs --update-baseline",
130
136
  "bench:self-scan:update-baseline": "node ../bench/self-scan/check.mjs --update-baseline",
131
137
  "bench:engine-recall": "node ../bench/engine-recall/measure.mjs",
132
138
  "scorecard": "node ../scripts/scorecard.mjs",
133
139
  "check-doc-drift": "node ../scripts/check-doc-drift.mjs",
140
+ "docs:verify-examples": "node ../scripts/verify-doc-examples.mjs",
134
141
  "harness-score": "node ../scripts/harness-score.cjs",
135
142
  "corpus:enroll": "node ../scripts/enroll-proven-finding.mjs",
136
143
  "corpus:provenance": "node ../scripts/corpus-provenance-check.mjs",
@@ -6,7 +6,7 @@ Layer-2 taint engine. Walks the Layer-1 IR (`../ir/`) with field-sensitive forwa
6
6
 
7
7
  - **Intra-procedural field-sensitive taint** with access-path lattice (`access-paths.js`). `user.email` is distinguishable from `user.password`.
8
8
  - **Value-context-sensitive interprocedural taint (FR-SEM-2).** `SummaryCache` (`summaries.js`) holds a distinct summary per distinct entry-taint-state. A pre-pass computes the empty-entry base for every function; call sites then lazily compute the summary under their actual tainted-arg context (at both assign-from-call and plain-call sites) so a helper that is clean with clean args but tainted with user input is detected per call site. Bounded by a per-function context cap (`AGENTIC_SECURITY_KCFA_MAX_CONTEXTS`, default 16; 0 = monovariant). Over the cap → reuse the empty-entry summary.
9
- - **Catalog-driven source/sink matching.** Add entries in `catalog.js`. Each entry: `kind` ∈ {source, sink, sanitizer}, plus language + framework + match shape. `match.type` ∈ {`call`, `member`, `global`, `annotation`} — 655 entries (149 source, 124 sink, 382 sanitizer — re-derive via `CATALOG.length` / a per-`kind` count rather than trusting a hardcoded number here again; this has already drifted once) spanning Express/Flask/FastAPI/Django/Rails/PHP/Go-net-http/Gin/Echo.
9
+ - **Catalog-driven source/sink matching.** Add entries in `catalog.js`. Each entry: `kind` ∈ {source, sink, sanitizer}, plus language + framework + match shape. `match.type` ∈ {`call`, `member`, `global`, `annotation`} — 655 entries (149 source, 124 sink, 382 sanitizer — re-derive via `CATALOG.length` / a per-`kind` count rather than trusting a hardcoded number here again; this has already drifted once) **— and it has now drifted a SECOND time: as of 2026-08-31 the real figures are 756 (180 source, 194 sink, 382 sanitizer). See `../lineage/DESIGN_REGISTRIES.md` §7.3 item 1 — originally measured and pinned by the design-phase PoC, since deleted; the 180-source and 194-sink figures are now pinned as shipped, permanent equality tests in the lineage package's own source-registry.test.js and sink-registry.test.js (see `../lineage/CLAUDE.md`'s module table for their exact paths). The stale numbers are left in place above rather than hand-patched a third time, because nothing in this file can keep them true; trust `CATALOG.length`, not this sentence — and it HAS drifted a third time already, predictably: Sub-project H's AC-07 closure (2026-08-31) added 4 sink entries, so the real sink-entry count is now 198, not 194** — spanning Express/Flask/FastAPI/Django/Rails/PHP/Go-net-http/Gin/Echo.
10
10
  - **`match.type: 'call'`/`'member'`/`'global'`** are matched against an *expression* the taint-walker encounters while visiting a CFG node (`matchSource` / `matchSinkOrSanitizer`) — `call` against a call expression's callee, `member` against a property-access chain (`match.object` + `match.prop`), `global` against a bare identifier read.
11
11
  - **`match.type: 'annotation'`** (R14(a): Spring `@RequestParam`/`@PathVariable`/`@RequestBody`/`@RequestHeader`, ASP.NET Core `[FromQuery]`/`[FromBody]`/`[FromForm]`/`[FromRoute]`/`[FromHeader]`, NestJS `@Query()`/`@Body()`/`@Param()`/`@Headers()`) works differently from the other three, and this is the one fact worth internalizing before touching it: **it is not consulted during the CFG walk.** There is no CFG node for "this function's own declared parameter list." Instead `matchAnnotationParams` (`catalog.js`) is called once per function — by `engine.js`'s `_unionAnnotationTaint`, before `analyzeFunction` runs — against the IR's `fn.paramAnnotations` side-channel (`{index, name, decorator}[]`, populated by `parser-cs.js`/`parser-js.js`/`parser-java.js`; see `../ir/CLAUDE.md`), and the resulting tainted param names are unioned into the entry taint-state for that function/call-site. A reader who assumes `annotation` entries get matched against expressions mid-walk, the way `call`/`member`/`global` do, will be wrong.
12
12
  - **Sanitizer entries are RECORDED, never trusted to kill taint.** `_sanitizersForExpr()` collects the sanitizer callees applied to the value reaching each sink argument — inline in the argument, or inherited from the variable it reads (`_sanitizersByVar`, updated on every assignment and cleared when the RHS is clean). They are stamped on the finding as `_sanitizersOnPath` and consumed by `sanitizer-gate.js`, which sets `sanitized` + `sanitizerProof` **only when the sanitizer's `appliesTo` family covers the finding's threat class** (`*` covers everything). The proof gate then demotes. Taint itself still dies only on clean re-assignment (`removePathAndDescendants`).
@@ -606,6 +606,48 @@ export const CATALOG = [
606
606
  vuln: { name: 'SSRF (axios/http.get)', severity: 'high', cwe: 'CWE-918',
607
607
  remediation: 'Validate the target URL against an allow-list before fetching; reject RFC1918 + metadata endpoints.' } },
608
608
 
609
+ // ─── SINKS (AI model provider) ────────────────────────────────────────────
610
+ // AC-07 closure (Data Flow Explorer, Sub-project H). Regulated data
611
+ // (PCI/PHI/PII) reaching a hosted third-party AI model provider is a real
612
+ // data-exposure concern the same way any other third-party API call is —
613
+ // CWE-201 (Insertion of Sensitive Information Into Sent Data), NOT CWE-359
614
+ // (reserved exclusively for privacy-catalog.js's own "Privacy Leak" family
615
+ // — sink-registry.js's completeness/1c test enforces this).
616
+ //
617
+ // Precision comes from `receiver`/`receiverBase`: CALLEE_INDEX is keyed on
618
+ // the bare name `create`, so every `X.create(...)` in the tree is a
619
+ // candidate. Every receiver-bearing entry below constrains BOTH the
620
+ // property chain AND the receiver's own name/alias — `receiver` alone
621
+ // (checked via `_receiverAllowed`'s `.some()` over the whole member
622
+ // chain) is not enough: a first cut of `js-anthropic-messages-create`/
623
+ // `js-openai-responses-create` shipped with only `receiver` set, and a
624
+ // task review found it matched ordinary `db.messages.create()`/
625
+ // `prisma.messages.create()` (Sequelize/Prisma's idiomatic row-insert
626
+ // shape) — a real false-attribution SAST finding, not a hypothetical.
627
+ // `receiverBase: '^chat$'` on the OpenAI chat-completions entry is what
628
+ // separates it from the legacy `anthropic.completions.create` shape
629
+ // (deliberately not cataloged here — AC-07 names only OpenAI/Anthropic/
630
+ // Bedrock).
631
+ { kind: 'sink', id: 'js-openai-chat-completions-create', language: 'js', framework: 'openai',
632
+ match: { type: 'call', callee: 'create', receiver: '^completions$', receiverBase: '^chat$' }, argIndex: 0,
633
+ vuln: { name: 'Regulated Data to AI Model Provider (OpenAI chat.completions.create)', severity: 'medium', cwe: 'CWE-201',
634
+ remediation: 'Confirm the request payload carries no PCI/PHI/PII before sending to a third-party model provider, or route through an approved DPA / redaction layer.' } },
635
+ { kind: 'sink', id: 'js-openai-responses-create', language: 'js', framework: 'openai',
636
+ match: { type: 'call', callee: 'create', receiver: '^responses$', receiverBase: '^(?:openai|client|oai)$' }, argIndex: 0,
637
+ vuln: { name: 'Regulated Data to AI Model Provider (OpenAI responses.create)', severity: 'medium', cwe: 'CWE-201',
638
+ remediation: 'Confirm the request payload carries no PCI/PHI/PII before sending to a third-party model provider, or route through an approved DPA / redaction layer.' } },
639
+ { kind: 'sink', id: 'js-anthropic-messages-create', language: 'js', framework: 'anthropic',
640
+ match: { type: 'call', callee: 'create', receiver: '^messages$', receiverBase: '^(?:anthropic|client|claude)$' }, argIndex: 0,
641
+ vuln: { name: 'Regulated Data to AI Model Provider (Anthropic messages.create)', severity: 'medium', cwe: 'CWE-201',
642
+ remediation: 'Confirm the request payload carries no PCI/PHI/PII before sending to a third-party model provider, or route through an approved DPA / redaction layer.' } },
643
+ // Bare, constructor-shaped call with no fixed receiver — safe to match on the
644
+ // bare name alone, the same precedent `js-fetch` already uses, because
645
+ // `InvokeModelCommand` is a distinctive, unambiguous identifier.
646
+ { kind: 'sink', id: 'js-bedrock-invoke-model-command', language: 'js', framework: 'bedrock',
647
+ match: { type: 'call', callee: 'InvokeModelCommand' }, argIndex: 0,
648
+ vuln: { name: 'Regulated Data to AI Model Provider (AWS Bedrock InvokeModelCommand)', severity: 'medium', cwe: 'CWE-201',
649
+ remediation: 'Confirm the request payload carries no PCI/PHI/PII before sending to a third-party model provider, or route through an approved DPA / redaction layer.' } },
650
+
609
651
  // ─── SINKS (command exec) ─────────────────────────────────────────────────
610
652
  { kind: 'sink', id: 'py-subprocess-run', language: 'py', framework: 'subprocess', match: { type: 'call', callee: 'run', requireKeyword: { name: 'shell', pattern: '^(?:true|True|1)$' } }, argIndex: 0,
611
653
  vuln: { name: 'Command Injection (subprocess.run shell=True)', severity: 'critical', cwe: 'CWE-78',
@@ -0,0 +1,175 @@
1
+ // Milestone 2, Sub-project E, increment 1 (ORM-write sink recognition).
2
+ // Per docs/superpowers/plans/2026-08-31-data-flow-explorer-m2-subproject-e1-plan.md
3
+ // and its own referenced scoping doc's Finding 3 (the precision-mechanism
4
+ // gap): a catalog of ORM row-write call shapes (`Model.create({...})`,
5
+ // `Model.save({...})`, `Model.update({...})`, `Model.upsert({...})`) for
6
+ // `src/lineage/`'s Data Flow Explorer only.
7
+ //
8
+ // DELIBERATELY NOT ADDED TO `dataflow/catalog.js`'s own `CATALOG`, and NOT
9
+ // wired into `matchSinkOrSanitizer`/`runTaintEngine` — the exact same
10
+ // isolation reasoning `privacy-catalog.js`'s own header comment gives for
11
+ // itself (see that file, lines 12-32), reapplied here for a stronger
12
+ // reason: `CATALOG` is a shared, always-active singleton consulted for
13
+ // EVERY scan, with no per-family filtering, so merging these entries in
14
+ // would make every already-tainted general source (`req.body`, etc.)
15
+ // immediately produce a new, unreviewed SAST finding class the moment it
16
+ // reached an ORM `.create()`/`.save()`/`.update()`/`.upsert()` call — a
17
+ // real, unintended expansion of the general SAST pipeline's output as a
18
+ // side effect of a Data Flow Explorer feature, not something this
19
+ // increment is chartered to decide. Unlike `privacy-catalog.js` (which
20
+ // shares its entry shape with something that COULD wire into `CATALOG`
21
+ // later), this catalog's own precision gate — "the call's first argument
22
+ // must be an object-literal expression" — cannot be expressed inside
23
+ // `matchSinkOrSanitizer`'s existing `(calleeExpr, file, receiverType)`
24
+ // signature at all, since that signature never receives the call's
25
+ // arguments. Making that check work requires a caller with `site.args`
26
+ // (`src/lineage/graph-builder.js`'s own site enumeration), so the
27
+ // isolation here is closer to structural than a mere policy choice.
28
+ //
29
+ // Keeping this catalog in its own file, imported ONLY by `src/lineage/`'s
30
+ // own registry/graph-builder modules, is what makes it genuinely inert to
31
+ // every other scan surface.
32
+ //
33
+ // TWO signals gate a real match, and only one lives in this file:
34
+ // 1. Callee shape (HERE, in `ORM_WRITE_CATALOG` + `matchOrmWrite`):
35
+ // method name is one of create/save/update/upsert, receiver is a
36
+ // BARE, CAPITALIZED identifier (`^[A-Z]\w*$`) — the SAME shape
37
+ // `sast/mass-assignment.js` (line 27) and `posture/cross-lang-orm.js`
38
+ // (`findOrmWrites`) already use in production as real SAST
39
+ // detectors; this is not a new or untested heuristic, it is the
40
+ // established one, reused here for a lower-stakes (candidate-tier,
41
+ // disclosed) purpose than either of those two modules' own
42
+ // full-confidence findings.
43
+ // 2. Argument shape (NOT here — lineage-side, in `graph-builder.js`'s
44
+ // site enumeration): the matched call's first argument must be an
45
+ // object-literal expression (`kind: 'object'`). A call whose first
46
+ // argument is not an object literal (a bare variable, a spread, a
47
+ // positional string — e.g. `User.create(req.body)`) never becomes an
48
+ // ORM-write sink candidate at all; this file cannot express that gate
49
+ // itself, since `matchOrmWrite`'s signature (mirroring
50
+ // `matchPrivacySink`'s) never receives the call's arguments.
51
+ //
52
+ // `coverageStatus` for every site this catalog recognizes is the literal
53
+ // string 'candidate', unconditionally, computed nowhere — see
54
+ // `sink-registry.js`'s `reclassifyOrmWrite`. Even with both signals
55
+ // present, this is real, disclosed uncertainty: an arbitrary
56
+ // capitalized-identifier receiver could still be a non-ORM builder
57
+ // pattern that happens to take an object-literal argument.
58
+ //
59
+ // Scope boundary, disclosed rather than silently assumed: JS/TS only.
60
+ // Python/Java/Go/Ruby/PHP ORM shapes (Django `.objects.create()`, GORM,
61
+ // ActiveRecord, Eloquent) are real, common shapes this catalog does not
62
+ // recognize — out of scope for this increment, per the plan's own
63
+ // "Explicitly deferred" list. Table/column/operation extraction, and
64
+ // Prisma's distinctive `data: {...}` wrapper form's own precise handling,
65
+ // are likewise deferred (the object-literal-argument check as specified
66
+ // sees Prisma's OUTER `{ data: {...} }` literal and passes, which is
67
+ // consistent for the callee-match gate, but this file makes no claim
68
+ // about extracting the inner Prisma shape — that's E2's job).
69
+ //
70
+ // `category: 'database'` reuses the existing `SINK_CATEGORIES` value
71
+ // (schema.js) rather than minting a new `orm-write`-style category — a
72
+ // genuine, lower-confidence detection of the same kind of destination a
73
+ // raw-SQL sink already models. Whether a dedicated category is warranted
74
+ // is an open question left to a future increment, not decided here.
75
+
76
+ import { _languageFamilyExtensions } from './catalog.js';
77
+
78
+ // The method-name vocabulary is verified against `cross-lang-orm.js`'s own
79
+ // `findOrmWrites` regex (`create|save|update|build|insert|upsert`) and
80
+ // `mass-assignment.js`'s own JS pattern (`create|update|build|save`) —
81
+ // this catalog uses the plan's suggested starting set (create/save/update/
82
+ // upsert), a subset of both, not the full union: `build`/`insert` are
83
+ // real ORM-adjacent method names too, but the plan's own "starting point,
84
+ // not a mandate" framing leaves widening this list to a future increment.
85
+ export const ORM_WRITE_CATALOG = [
86
+ { kind: 'sink', id: 'orm-write-js-create', language: 'js', framework: 'orm', category: 'database',
87
+ match: { type: 'call', callee: 'create' }, argIndex: 0,
88
+ vuln: { name: 'ORM Write (create)', severity: 'info', cwe: null,
89
+ remediation: 'Heuristic ORM row-write recognition for the Data Flow Explorer only — not a SAST finding. No remediation is implied.' } },
90
+ { kind: 'sink', id: 'orm-write-js-save', language: 'js', framework: 'orm', category: 'database',
91
+ match: { type: 'call', callee: 'save' }, argIndex: 0,
92
+ vuln: { name: 'ORM Write (save)', severity: 'info', cwe: null,
93
+ remediation: 'Heuristic ORM row-write recognition for the Data Flow Explorer only — not a SAST finding. No remediation is implied.' } },
94
+ { kind: 'sink', id: 'orm-write-js-update', language: 'js', framework: 'orm', category: 'database',
95
+ match: { type: 'call', callee: 'update' }, argIndex: 0,
96
+ vuln: { name: 'ORM Write (update)', severity: 'info', cwe: null,
97
+ remediation: 'Heuristic ORM row-write recognition for the Data Flow Explorer only — not a SAST finding. No remediation is implied.' } },
98
+ { kind: 'sink', id: 'orm-write-js-upsert', language: 'js', framework: 'orm', category: 'database',
99
+ match: { type: 'call', callee: 'upsert' }, argIndex: 0,
100
+ vuln: { name: 'ORM Write (upsert)', severity: 'info', cwe: null,
101
+ remediation: 'Heuristic ORM row-write recognition for the Data Flow Explorer only — not a SAST finding. No remediation is implied.' } },
102
+ ];
103
+
104
+ function _ormLanguageAllowed(entry, file) {
105
+ if (!file) return true;
106
+ const res = _languageFamilyExtensions(entry.language);
107
+ if (!res.length) return true; // unmapped language stays permissive
108
+ return res.some((re) => re.test(file));
109
+ }
110
+
111
+ // Small, local copy of the callee-name extraction shape `privacy-catalog.js`'s
112
+ // own `_privacyCalleeNames` establishes — not imported from that file (it's
113
+ // private/unexported there), matching this package's established precedent
114
+ // of a small local duplicate over an awkward cross-module dependency on
115
+ // another module's private helper (see `src/lineage/handling-analyzer.js`'s
116
+ // own disclosed `calleeDescriptorOf` duplicate).
117
+ function _ormCalleeNames(calleeExpr) {
118
+ let last = null;
119
+ let full = null;
120
+ if (typeof calleeExpr === 'string') {
121
+ full = calleeExpr;
122
+ last = calleeExpr.includes('.') ? calleeExpr.slice(calleeExpr.lastIndexOf('.') + 1) : calleeExpr;
123
+ } else if (calleeExpr && calleeExpr.kind === 'member' && calleeExpr.prop) {
124
+ last = calleeExpr.prop;
125
+ if (calleeExpr.object && calleeExpr.object.kind === 'ident') full = `${calleeExpr.object.name}.${calleeExpr.prop}`;
126
+ } else if (calleeExpr && calleeExpr.kind === 'ident') {
127
+ last = calleeExpr.name || null;
128
+ }
129
+ return { last, full };
130
+ }
131
+
132
+ // Bare, capitalized receiver identifier — a SHAPE regex on the receiver's
133
+ // own name, not a `receiverTypeIn` exact-alias lookup (unrelated to the
134
+ // IR's CHA-resolved `receiverType`). Only a `kind: 'member'` calleeExpr
135
+ // whose `object` is itself a plain `kind: 'ident'` can satisfy this — a
136
+ // computed or chained receiver (`db.models.User.create(...)`) does not,
137
+ // by design (the same conservative shape `mass-assignment.js`/
138
+ // `cross-lang-orm.js` already use in production).
139
+ const CAPITALIZED_IDENT_RE = /^[A-Z]\w*$/;
140
+
141
+ function _ormReceiverIsCapitalizedIdent(calleeExpr) {
142
+ return Boolean(
143
+ calleeExpr && typeof calleeExpr === 'object' &&
144
+ calleeExpr.kind === 'member' &&
145
+ calleeExpr.object && calleeExpr.object.kind === 'ident' &&
146
+ typeof calleeExpr.object.name === 'string' &&
147
+ CAPITALIZED_IDENT_RE.test(calleeExpr.object.name),
148
+ );
149
+ }
150
+
151
+ /**
152
+ * Signal 1 only (callee shape) — the argument-shape signal (signal 2) is
153
+ * NOT checked here; see this file's own header for why it structurally
154
+ * cannot be. Mirrors `matchPrivacySink`'s `(calleeExpr, file, receiverType)`
155
+ * contract, minus the unused third parameter (this catalog's receiver
156
+ * constraint is a name-shape regex, not a `receiverTypeIn` lookup, so no
157
+ * `receiverType` input is ever consulted). Returns an array of hits, or
158
+ * `null` — same "array of hits, or null" contract `matchSinkOrSanitizer`/
159
+ * `matchPrivacySink` already establish.
160
+ */
161
+ export function matchOrmWrite(calleeExpr, file) {
162
+ if (!calleeExpr) return null;
163
+ if (!_ormReceiverIsCapitalizedIdent(calleeExpr)) return null;
164
+ const { last, full } = _ormCalleeNames(calleeExpr);
165
+ if (!last && !full) return null;
166
+ const hits = ORM_WRITE_CATALOG.filter((e) => {
167
+ if (!e || e.kind !== 'sink') return false;
168
+ const cName = e.match && e.match.callee;
169
+ if (!cName) return false;
170
+ if (cName !== last && cName !== full) return false;
171
+ if (!_ormLanguageAllowed(e, file)) return false;
172
+ return true;
173
+ });
174
+ return hits.length ? hits : null;
175
+ }
package/src/engine.js CHANGED
@@ -206,6 +206,7 @@ import { applyPathConstraints } from './posture/path-predicates.js';
206
206
  import { buildProjectIR, buildProjectIRAsync } from './ir/index.js';
207
207
  import { collectIrStats, irStatsTarget, writeIrStats } from './ir/ir-stats.js';
208
208
  import { runDeepAnalysis } from './dataflow/index.js';
209
+ import { buildLineageGraph } from './lineage/index.js';
209
210
  // v3 next-gen — Pillars 1, 4, 5, 6, 8, 9.
210
211
  import { annotateCloneClusters, findCloneOutliers } from './posture/semantic-clone.js';
211
212
  import { annotateAiProvenance } from './posture/ai-code-fingerprint.js';
@@ -9229,6 +9230,95 @@ function _deterministicFileTimings(timings) {
9229
9230
  : (_inCi ? 'not requested (deep analysis defaults to off in CI)' : 'not requested')),
9230
9231
  failure: _deepFailure,
9231
9232
  };
9233
+ // Sub-project E, increment 5 — Data Flow Explorer lineage graph.
9234
+ // DELIBERATELY INDEPENDENT of AGENTIC_SECURITY_DEEP/_deepEnabled above
9235
+ // (see src/lineage/DESIGN_GRAPH_BUILDER.md §9.5): lineage analysis has no
9236
+ // degraded/non-IR-backed mode, so gating it on deep mode ALSO being on
9237
+ // would make AGENTIC_SECURITY_LINEAGE_DEEP=1 alone silently produce
9238
+ // nothing whenever deep mode itself was off.
9239
+ //
9240
+ // Undisclosed-until-final-review side effect (N-4): this block populates
9241
+ // the SAME `_sharedIR` memo the privacy-taint block further below reads
9242
+ // (`_privacyIrBacked = !!(_sharedIR && _sharedIR.perFile)`), so setting
9243
+ // AGENTIC_SECURITY_LINEAGE_DEEP=1 also upgrades privacy-taint annotation
9244
+ // to IR-backed (`privacyIrBacked: true` instead of `false`) even with
9245
+ // AGENTIC_SECURITY_DEEP itself off. A real, deliberate-once-noticed
9246
+ // consequence of the memo sharing, not a bug — but worth knowing about
9247
+ // before being surprised by it.
9248
+ const _lineageRequested = process.env.AGENTIC_SECURITY_LINEAGE_DEEP === '1';
9249
+ const _lineageStatus = {
9250
+ requested: _lineageRequested,
9251
+ enabled: false,
9252
+ reason: _lineageRequested ? null : 'not requested',
9253
+ failure: null,
9254
+ };
9255
+ let _lineageGraph = null;
9256
+ if (_lineageRequested) {
9257
+ _lineageStatus.enabled = true;
9258
+ const _lineageBudgetMs = parseInt(process.env.AGENTIC_SECURITY_LINEAGE_TIMEOUT_MS || '300000', 10);
9259
+ try {
9260
+ // Reuses the SAME _sharedIR memo _deepEnabled's own block above uses —
9261
+ // if deep mode already built it, this is free; if not, this is what
9262
+ // first triggers the build (the whole reason this gate is independent).
9263
+ const { perFile, callGraph } = _sharedIR || (_sharedIR = await _buildIR());
9264
+ const _lr = buildLineageGraph(callGraph, {
9265
+ // scanRoot can be null (e.g. an MCP scan_diff caller, or
9266
+ // runFullScan invoked directly with no scanRoot) — fall back to
9267
+ // undefined rather than inventing a repository name from the
9268
+ // process's own CWD basename, which would make lineage node ids
9269
+ // depend on wherever the process happened to be launched from and
9270
+ // collide across two unrelated repos sharing a directory basename.
9271
+ // `undefined` lets graph-builder.js's own `opts.repository ?? 'repo'`
9272
+ // default apply — the single source of truth for that literal.
9273
+ repository: scanRoot ? path.basename(path.resolve(scanRoot)) : undefined,
9274
+ // Milestone 2, Sub-project G, increment 1 (FR-408/AC-09): the real
9275
+ // scan root, threaded straight through — distinct from `repository`
9276
+ // above, which is only a basename. `index.js`'s own `buildLineageGraph`
9277
+ // uses this ONLY to load `.agentic-security/privacy-policy.json`
9278
+ // (never to derive `repository`/node ids), mirroring how `fileContents`
9279
+ // was threaded through for Sub-project B's own transit-evidence scan.
9280
+ scanRoot,
9281
+ deterministic: isDeterministic(),
9282
+ perFile,
9283
+ fileContents,
9284
+ });
9285
+ if (_lr.status === 'complete') {
9286
+ _lineageGraph = _lr.graph;
9287
+ if (_lr.elapsedMs > _lineageBudgetMs) {
9288
+ // Same "measured, not truly interrupted" disclosure _deepEnabled's
9289
+ // own elapsed > budgetMs branch already makes for IR-taint — see
9290
+ // src/lineage/DESIGN_GRAPH_BUILDER.md §9.5.
9291
+ aF.push({
9292
+ id: `lineage-timeout:${scanRoot || ''}`,
9293
+ file: '(lineage-engine)', line: 0,
9294
+ vuln: `Lineage graph build exceeded ${_lineageBudgetMs}ms budget (${_lr.elapsedMs}ms used) — results may be incomplete`,
9295
+ severity: 'info',
9296
+ parser: 'LINEAGE',
9297
+ confidence: 0.5,
9298
+ });
9299
+ }
9300
+ } else if (_lr.status === 'failed') {
9301
+ _lineageStatus.failure = _lr.failure;
9302
+ }
9303
+ // status === 'not_available' needs no extra handling: _lineageGraph
9304
+ // stays null, and enabled:true/failure:null correctly reads as "ran,
9305
+ // produced nothing" (a genuinely empty/malformed callGraph), distinct
9306
+ // from "never ran" (requested:false).
9307
+ } catch (e) {
9308
+ // Lineage is best-effort, mirroring _deepEnabled's own catch above
9309
+ // (engine.js, `_deepFailure = String((e && e.message) || e);`):
9310
+ // anything OUTSIDE buildLineageGraph's own try/catch — the _buildIR()
9311
+ // call itself, path.resolve, etc. — must never kill the scan. Without
9312
+ // this, a throw here (e.g. from buildCallGraph, which
9313
+ // buildProjectIR/buildProjectIRAsync do NOT guard per-file the way
9314
+ // per-file parsing is guarded) previously took down the entire scan
9315
+ // the moment AGENTIC_SECURITY_LINEAGE_DEEP=1 was set — see
9316
+ // src/lineage/DESIGN_GRAPH_BUILDER.md §9.5 item 1 and the Global
9317
+ // Constraint this violated ("a lineage-build failure must never fail
9318
+ // the scan"). Regression-tested in test/lineage-fault-injection.test.js.
9319
+ _lineageStatus.failure = String((e && e.message) || e);
9320
+ }
9321
+ }
9232
9322
  // Java SCA enrichment: use deep-mode IR call graph to improve Java function reachability
9233
9323
  if (_deepCallGraph) {
9234
9324
  try {
@@ -10585,6 +10675,7 @@ function _deterministicFileTimings(timings) {
10585
10675
  engineErrors: { cppDataflowParseErrors: _cppDataflowParseErrors.value },
10586
10676
  deepStatus: _deepStatus,
10587
10677
  analyzerCoverage: summarizeCoverageForScanHealth(_coverageLedger),
10678
+ lineageStatus: _lineageStatus,
10588
10679
  });
10589
10680
  // FR-207: stale vulnerability feeds, calibration data, and compliance
10590
10681
  // evidence are real assurance gaps, not just findings the feed omits --
@@ -10600,7 +10691,7 @@ function _deterministicFileTimings(timings) {
10600
10691
  compliance: _complianceReport ? { stale: _complianceReport.summary?.stale || 0 } : null,
10601
10692
  });
10602
10693
  } // end if (!skipAnnotators) — FR-PROV-029
10603
- return{entrypointInventory:_entrypointInventory,rootCauseSweep:_rootCauseSweep,proofCoverage:_proofCoverage,kevCatalog:kevCatalogMeta(),routes:dd(aR,r=>`${r.method}:${r.path}:${r.file}:${r.line}`),findings:finalFindings,sources:aSrc,sinks:aSink,sanitizers:aSan,filesScanned:files.length,crossFileCount:cf.length,logicVulns:aLogic,supplyChain,components:annotatedComponents,secrets:aSecrets,ciphers:{atRest:aCiphersRest,inTransit:aCiphersTransit},pfr,fc,suppressions:_getSuppressions(),_v3,_scanMeta,_engineErrors:{cppDataflowParseErrors:_cppDataflowParseErrors.value},annotatorErrors:_annotatorErrors,detectorErrors:_detectorErrors,executionProof:_executionProofSummary,logicClaims:_logicClaims,vulnHistory:_vulnHistory,threatModel:_threatModel,privacyFramework:_privacyFramework,privacyIrBacked:_privacyIrBacked,privacyTaxonomyVersion:_privacyTaxonomyVersion,sbomDiff:_sbomDiff,complianceReport:_complianceReport,exploitBundles:_exploitBundles,pqcPlan:_pqcPlan,licenseGraph:_licenseGraph,attributions:_attributions,attackTaxonomy:_taxonomySummary,scanHealth:_scanHealth,coverageLedger:_coverageLedger};}
10694
+ return{entrypointInventory:_entrypointInventory,rootCauseSweep:_rootCauseSweep,proofCoverage:_proofCoverage,kevCatalog:kevCatalogMeta(),routes:dd(aR,r=>`${r.method}:${r.path}:${r.file}:${r.line}`),findings:finalFindings,sources:aSrc,sinks:aSink,sanitizers:aSan,filesScanned:files.length,crossFileCount:cf.length,logicVulns:aLogic,supplyChain,components:annotatedComponents,secrets:aSecrets,ciphers:{atRest:aCiphersRest,inTransit:aCiphersTransit},pfr,fc,suppressions:_getSuppressions(),_v3,_scanMeta,_engineErrors:{cppDataflowParseErrors:_cppDataflowParseErrors.value},annotatorErrors:_annotatorErrors,detectorErrors:_detectorErrors,executionProof:_executionProofSummary,logicClaims:_logicClaims,vulnHistory:_vulnHistory,threatModel:_threatModel,privacyFramework:_privacyFramework,privacyIrBacked:_privacyIrBacked,privacyTaxonomyVersion:_privacyTaxonomyVersion,sbomDiff:_sbomDiff,complianceReport:_complianceReport,exploitBundles:_exploitBundles,pqcPlan:_pqcPlan,licenseGraph:_licenseGraph,attributions:_attributions,attackTaxonomy:_taxonomySummary,scanHealth:_scanHealth,coverageLedger:_coverageLedger,lineageGraph:_lineageGraph,lineageStatus:_lineageStatus};}
10604
10695
 
10605
10696
  // Post-aggregation classification: every source becomes "unsafe"|"safe"; every sink becomes "confirmed"|"safe".
10606
10697
  // Orphans (no finding linkage) are bucketed by file-local heuristic so the UI shows binary states only.
package/src/ir/CLAUDE.md CHANGED
@@ -10,6 +10,7 @@ consumed by `scanner/src/dataflow/` for taint analysis.
10
10
  | JS / TS | `parser-js.js` | `@babel/parser`. **Taint-recall PRD (80%) Tier 3: JSX had ZERO IR modeling** — every `JSXElement` fell through `exprOf`'s switch to `{kind:'unknown'}`, so `return <div dangerouslySetInnerHTML={{__html: html}} />` (React's canonical XSS sink, and a real corpus miss) silently dropped `html`'s taint entirely, even though the plain-JS member-write form (`x.dangerouslySetInnerHTML = {...}`) was already cataloged. Deliberately narrow, not general JSX modeling: `exprOf`'s new `JSXElement` case (via `_findDangerouslySetInnerHTML`) extracts ONLY the `dangerouslySetInnerHTML` attribute's `__html` property value — found on the element itself or, recursively, any descendant (the attribute can sit on a nested element, not just the one directly returned) — and lowers it to a synthetic call (`__jsx_dangerously_set_inner_html__`), targeted by a sink of the same name (`react-jsx-dangerouslySetInnerHTML`) sibling to the existing member-write entry. Ordinary JSX children (`<div>{unsafeText}</div>`) are deliberately NOT modeled — React auto-escapes children by default, so that shape isn't itself a vulnerability the way `dangerouslySetInnerHTML` is. Guarded by `test/catalog-xss-p4.test.js`. |
11
11
  | Python | `parser-py-cst.js` | Python 3.8+ stdlib `ast` via subprocess (default when available) |
12
12
  | Python | `parser-py.js` | Hand-rolled regex parser (fallback when python3 missing) |
13
+ | (tool capability probe) | `chrome-probe.mjs` | Chrome/Chromium binary discovery — optional local tool, detects and probes availability via `probeChromeAvailable()`, following `parser-py-cst.js`'s established graceful-degradation pattern. Consumed by Milestone 4's image export functions (`exportPng`/`exportPdf`/`exportSvg` in `scripts/export-image.mjs`) for headless rendering. |
13
14
  | Java | `parser-java.js` | `java-parser` npm package (**async only** — the deep path in `engine.js` therefore awaits `buildProjectIRAsync` when any `.java` file is present, and uses the sync builder otherwise).
14
15
  ⚠ Three defects made Java taint impossible until v0.136.3+: the sync-only call site; a CST walk looking for `blockStatement` on a `block` (java-parser nests `block → blockStatements → blockStatement`), which emptied every method CFG; and `exprFromCst` missing the `primary → primaryPrefix + primarySuffix` form that models **every** method call. Guarded by `test/java-taint-flow.test.js`. Real parameter names (previously always `params: []`, marked "deferred") plus Spring `@RequestParam`/`@PathVariable`/`@RequestBody`/`@RequestHeader` param annotations (`fn.paramAnnotations`) are extracted in one CST walk over `formalParameterList` (PRD R14(a) Task 5). Varargs parameters (`String... args`) live under a distinct `variableArityParameter` node this walk doesn't extract from — they're gracefully dropped, not corrupted or crashed on. **PRD R9 (partial): `fn.calls` is now populated** via the shared `call-sites.js#callSitesFromCfg` (the same language-agnostic helper `parser-py-cst.js` uses) — Java's CFG nodes (`call`, `assign`, `return`, `if`) already matched the documented contract. This creates real cross-file call-graph edges for Java (`callgraph.js`'s `edges`/`callersOf`/`resolveKnownCallee`, previously always empty for Java) — it does NOT change the generic tainted-call-argument fallback in `engine.js`'s `exprTaint`, which reads CFG expression args directly off `expr.args` and already worked for Java independent of `fn.calls`. However, **same-class (intra-class) method calls do not resolve, in both the bare and `this.`-qualified forms**: `parser-java.js` names functions `"App.buildCmd"` (class-qualified), but a bare call extracts `"buildCmd"` (unqualified) and `callgraph.js`'s name-based resolution cannot match them — this is the most idiomatic Java call shape (private helpers, intra-class delegation) and remains a real gap, documented here as a candidate follow-up PRD item (a per-file bare-tail fallback in `callgraph.js`, mirroring the existing `~bare~`-key collision-refusal pattern, would plausibly fix it without touching `parser-java.js`). `this.buildCmd(id)` is not merely unresolved, it's worse: `parser-java.js:76` lowers any `this.`-qualified call whose prefix isn't a plain FQN to the literal callee string `"unknown"`, so it doesn't fail closed, it fails to a fabricated name. Guarded by `test/parser-java-calls.test.js` (the test's honest caveat discloses this; the fixture shows that edges exist but unresolved) (mirrors `test/parser-rb-calls.test.js`, the identical `fn.calls` wiring, though Ruby's unqualified names make bare-call resolution work there). **PRD R8: `walkStmts` now recurses into `for`/`try`/`switch`/`do`/bare-block bodies** (previously only `if`/`while` were walked — every other braced statement kind silently dropped its body from the CFG, including try-with-resources, the single most idiomatic JDBC shape). A fix round closed two further gaps the initial review found: enhanced-for (`for (x : xs)`) now synthesizes an assign binding the loop variable to the iterated expression, so the variable itself carries taint provenance (mirroring `parser-js.js`'s `ForOfStatement` pattern); and Java 14+ arrow-form `switch` (`case 1 -> …`) is now recognized via a second CST-shape branch. Deferred, not fixed: `forInit`/`forUpdate` clauses of a basic 3-clause `for` loop are still not walked (only the loop body is); `synchronized` blocks and labeled statements are still fully dropped. Guarded by `test/parser-java-control-flow.test.js`. **Measured `bench/layer-recall` impact: unchanged, 1/25 before and after** — the fix is real and directly proven by the dedicated unit tests above, but this corpus's existing 25 Java fixtures happen not to place a sink genuinely inside a braced control-flow body (the ones with `if`/`try` syntax use it as a single-line guard clause ahead of a flat-level sink, not a nested one) — see the PRD R8 status entry for the full explanation and the same finding for C#. **Taint-recall PRD (80%) Tier 4: chained-call CST fix.** `exprFromCst`'s `primaryPrefix` handling previously used `.find(Boolean)` to grab the FIRST `methodInvocationSuffix` in a `primary` node's `primarySuffix[]` array and discarded the rest — so any 2+-level fluent chain (`DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xml)`, `new SpelExpressionParser().parseExpression(expr).getValue()`) resolved to the wrong (inner, usually untainted) call, and the real sink call at the end of the chain was invisible to the taint walker. Rewritten to walk the full `primarySuffix[]` array in source order, dot-joining bare member-access segments (`{Dot, Identifier}` suffixes) into the callee name and accumulating args from every `methodInvocationSuffix` encountered — same outermost-first accumulation convention (`args.concat(soFar)`) as the `_followChain` helper shared by the other five hand-rolled parsers (this PRD's earlier, language-general fix for the identical class of bug: a trailing call/member continuation being silently dropped rather than followed). A companion catalog entry (`java-spel-getvalue`, receiver-scoped to `^parseExpression`, `argIndex: 'all'`) was added because the chain fix shifts the terminal callee segment from `parseExpression` to `getValue` — same "terminal segment shift" pattern documented for Kotlin/Go/Java elsewhere in this PRD. Fixes `CVE-2018-1000632-java-xxe` (DocumentBuilderFactory chain) and `CVE-2018-1273-java-code-injection` (SpelExpressionParser chain). Guarded by two new cases in `test/java-taint-flow.test.js`. **UPDATE (Tier 3 command-injection audit): the `_SHELL_META` gap this note originally flagged is fixed.** `CVE-2016-10033-java-cmdi`'s real-world shape `Runtime.getRuntime().exec("ping " + host)` was blocked by a SEPARATE, language-agnostic core-engine bug (`engine.js`'s `literalSkeletonMatchesFamily`/`_SHELL_META`, requiring a shell metacharacter in the STATIC portion of a CWE-78 concat — backwards for command injection, see `dataflow/CLAUDE.md`), now removed entirely. The corpus fixture also needed a Tier 2 fixture enrichment (`@RequestParam` on the `host` parameter — the committed fixture had no cataloged source at all, a second, independent blocker this note's original git-stash bisection had isolated away from before concluding `_SHELL_META` was the (sole, at the time) remaining issue). Both fixed together; `CVE-2016-10033-java-cmdi` now scores `pre:TP post:TN`. **Taint-recall PRD (80%) Tier 5: a THIRD, independent chain-CST bug — a constructor STARTING a chain.** Found via `CVE-2019-3799-spring-ssrf-shape`'s real-world shape `new URL(url).openStream()`: the chained-call fix above (the `primarySuffix[]` walk) only handles a chain that starts from an FQN/identifier prefix (`prefix?.children?.fqnOrRefType?.[0]`) — a chain whose FIRST segment is itself `new X(args)` takes a completely different CST shape and was never checked, so both the class name and the constructor's own args were silently dropped, leaving only the trailing `.openStream()` call with no receiver. Root-caused via temporary `console.error(Object.keys(prefix?.children))` tracing (the first guess, matching the STANDALONE `new X(args)` handling elsewhere in this file at `unqualifiedClassInstanceCreationExpression` directly off `node`, was wrong — a chain-starting constructor sits one level deeper, under `prefix?.children?.newExpression?.[0]?.children?.unqualifiedClassInstanceCreationExpression?.[0]`). Fixed by extending the same `primaryPrefix` chain-seed logic to also recognize this shape (`ctorPrefix`), extracting the class name and constructor args exactly like the FQN case, so the chain fix now covers both "chain starts on an identifier" and "chain starts on a constructor". Companion catalog fix: `java-url-openstream` was previously keyed to the (now-unreachable-via-chain) bare `callee: 'URL'`; corrected to `callee: 'openStream', receiver: '^URL'` — the same "terminal segment shift" pattern this PRD documents repeatedly elsewhere (Kotlin/Go). Guarded by `test/catalog-path-ssrf-p3.test.js`. |
15
16
  | Go | `parser-go.js` | Hand-rolled. **Taint-recall PRD (80%) Tier 4: inline anonymous closures.** Go's dominant HTTP-handler-registration idiom across every framework this file targets (net/http, gin, echo, fiber, chi) is an inline closure passed as the LAST argument to a registration call — `app.Get(path, func(c *fiber.Ctx) error { ... })`, `http.HandleFunc(path, func(w, r) { ... })`. This parser previously had ZERO support for anonymous functions at all: `FUNC_RE` requires a NAME between `func` and `(`, so an inline closure never matched it, and `_lowerExpr`'s generic call-matching regex mis-parsed `func(params) rtype { body }` as a call to something literally named "func" — `matchBalancedCall` correctly captured only `(params)` as that "call"'s own args, and the return-type token plus the ENTIRE closure body were silently discarded (no `.method(...)` continuation follows a return type, so `_followChain` found nothing to recover). This was very likely this PRD's single most consequential Go gap — it made every framework's route-handler BODY invisible to taint analysis regardless of what it did — found via this PRD's Tier 3 command-injection audit, not the parent PRD's original per-language sweep. Fixed by `_extractTrailingClosureArg` detecting a statement's LAST top-level call argument as an inline closure literal and INLINING its body directly into the enclosing function's CFG right after the registration call — a permissive, recall-preserving approximation (the closure genuinely runs later, but what matters for taint is that its statements become real CFG nodes at all), mirroring the Kotlin trailing-lambda / Ruby trailing-block precedent elsewhere in this PRD. The closure's OWN parameter (`c`, `w`, `r`) needs no synthetic taint binding, unlike Kotlin's `.forEach { x -> ... }`: a framework context object isn't itself a source — `c.Query(...)` is recognized by the EXISTING member/call-source catalog matching regardless of which function scope `c` was declared in. Chained calls before the closure arg (`app.Group("/api").Get(path, func(){...})`) are not specially handled — out of scope, matches this file's "handle the dominant shape" precedent elsewhere. Guarded by `test/parser-go.test.js`. Two catalog/engine precision bugs surfaced in the SAME audit, both fixed alongside: `go-os-exec-command`'s `argIndex: 0` checked the WRONG argument (the real dangerous shape is `exec.Command("/bin/sh", "-c", tainted)`, where arg 0 is always the literal interpreter and the tainted content sits later) — widened to `argIndex: 'all'`, gated by a new `match.requireLiteralArg` precision primitive (`catalog.js`/`engine.js`) requiring arg 0 to literally be a shell interpreter, so the safe array-execve form (`exec.Command("ping", "-c", "1", host)`) does not spuriously fire; and a "terminal segment shift" (same pattern as `kt-xpath-evaluate`/`java-spel-getvalue`/`go-r-uquery-get` elsewhere in this PRD) for `exec.Command(...).Output()`/`.Run()`/`.CombinedOutput()`/`.Start()` — chaining a Cmd-execution method directly onto `exec.Command(...)`, the dominant idiom for actually RUNNING the command, collapses the chain into one dotted string whose last segment is no longer "Command". Guarded by `test/catalog-command-injection-p4.test.js`. **Measured `bench/layer-recall` impact: real movement — command-injection (this corpus's dominant Go family) moved from 5/23 → 18/23 across this fix plus the sibling `literalSkeletonMatchesFamily`/CWE-78 engine fix (`dataflow/CLAUDE.md`).** |