@clear-capabilities/agentic-security-scanner 0.186.2 → 0.188.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 (39) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/1602.index.js +275 -19
  3. package/dist/{4d06c8944ae121d7085c.json → 92f78fd3e2dbe30fb58d.json} +5 -5
  4. package/dist/9560.index.js +61 -4
  5. package/dist/agentic-security.mjs +2 -2
  6. package/dist/agentic-security.mjs.sha256 +1 -1
  7. package/package.json +5 -5
  8. package/src/capabilities/decide.js +7 -1
  9. package/src/dataflow/CLAUDE.md +15 -0
  10. package/src/dataflow/catalog-haskell.js +7 -1
  11. package/src/dataflow/catalog.js +14 -0
  12. package/src/dataflow/engine.js +147 -10
  13. package/src/dataflow/privacy-catalog-haskell.js +2 -1
  14. package/src/engine.js +11 -3
  15. package/src/language/CLAUDE.md +26 -0
  16. package/src/language/compliance-map.js +6 -0
  17. package/src/language/context.js +20 -0
  18. package/src/language/haskell-comments.js +65 -0
  19. package/src/language/haskell-disclosure.js +16 -4
  20. package/src/language/haskell-findings.js +2 -1
  21. package/src/language/haskell-fix.js +118 -2
  22. package/src/language/haskell-guards.js +132 -5
  23. package/src/language/haskell-ir.js +228 -13
  24. package/src/language/haskell-models.js +108 -8
  25. package/src/language/haskell-security-rules.js +223 -41
  26. package/src/language/haskell-syntax.js +10 -1
  27. package/src/language/haskell-trust.js +268 -0
  28. package/src/language/haskell-web.js +46 -9
  29. package/src/language/support-registry.js +3 -2
  30. package/src/mcp/tools.js +54 -3
  31. package/src/posture/attack-taxonomy.js +10 -0
  32. package/src/posture/entrypoint-inventory.js +6 -2
  33. package/src/posture/proof-fabric/generate-haskell.js +266 -17
  34. package/src/posture/proof-fabric/generate.js +10 -3
  35. package/src/posture/proof-fabric/oracles.js +21 -0
  36. package/src/posture/stable-id.js +2 -1
  37. package/src/report/index.js +3 -0
  38. package/src/sast/cloud-iam.js +3 -1
  39. package/dist/2066.index.js +0 -851
@@ -1 +1 @@
1
- 38adcd8324aa7552b7bf61f5ac662fe383ed41f0bcf7f6eb2cad89de080a88c5 agentic-security.mjs
1
+ f70bb09f8b0030636bb4ba27a72de9beb7087bcedadb2d27127bad3cfb960f01 agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.186.2",
3
+ "version": "0.188.0",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin: SAST, SCA with reachability and CISA KEV, secrets, IaC, Haskell and Nix/NixOS, prompt-injection, MCP/agent-tool audit, authZ analysis, SBOM, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -86,16 +86,16 @@
86
86
  "test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
87
87
  "test:glob": "node --test test/glob-compat.test.js",
88
88
  "test:sast": "node --test test/dag-precision.test.js 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/zip-slip-go.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/csharp-cross-file.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/java-structural-cross-file.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/java-ast-folding.test.js test/java-bench-extras.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 test/rust-sast.test.js test/java-deserialization-precision.test.js test/pipeline-env-secret.test.js test/mark-used-vuln-functions.test.js",
89
- "test:posture": "node --test test/contracts/root-cause-sweep.contract.test.js test/contracts/model-validation.contract.test.js test/contracts/agentic-discovery.contract.test.js test/contracts/local-models.contract.test.js test/contracts/dependency-finding.contract.test.js test/contracts/taint-flow.contract.test.js test/contracts/runtime-corroboration.contract.test.js test/compliance-coverage.test.js test/contracts/blast-radius.contract.test.js test/contracts/federation.contract.test.js test/contracts/eval-lab-report.contract.test.js test/contracts/fleet.contract.test.js test/contracts/rule-compiler.contract.test.js test/contracts/release-assurance.contract.test.js test/contracts/dnr.contract.test.js test/contracts/strategic-metrics.contract.test.js test/contracts/exploit-proof.contract.test.js test/contracts/artifact-provenance.contract.test.js test/dnr/artifact-join.test.js test/rule-pack-signing.test.js test/threat-model.test.js 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/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.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/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.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/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.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/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.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 test/posture/assurance-baseline.test.js test/posture/assurance-contracts.test.js test/posture/assurance-config.test.js test/evidence-issuer.test.js test/load-factor.test.js test/fleet/job-store.test.js test/fleet/enterprise-graph.test.js test/fleet/discover-edges.test.js test/fleet/code-boundaries.test.js test/fleet/job-runner.test.js test/fleet/fleet-org-cli.test.js test/eval-lab/dataset-neutralize.test.js test/eval-lab/arms-score-stats.test.js test/eval-lab/run-report.test.js test/eval-lab/router.test.js test/eval-lab/arm-ollama.test.js test/eval-lab/import-independent.test.js test/cli/eval-lab-cli.test.js test/dnr/ir-and-outputs.test.js test/dnr/logs-and-hunt.test.js test/dnr/window-store-view.test.js test/dnr/exploit-correlation.test.js test/cli/dnr-cli.test.js test/proof-fabric/core.test.js test/proof-fabric/container.test.js test/proof-fabric/generate.test.js test/proof-fabric/siblings.test.js test/proof-fabric/generate-python.test.js test/proof-fabric/generate-haskell.test.js test/cli/proof-lifecycle-demo.test.js test/mcp-proof.test.js test/proof-fabric/fix-store-runner.test.js test/proof-fabric/fixtures.test.js test/cli/proof-fabric-cli.test.js test/rule-compiler/core.test.js test/rule-compiler/synthesis.test.js test/rule-compiler/structure.test.js test/cli/rule-compiler-cli.test.js test/fleet/fleet-graph.test.js test/fleet/fleet-manifest.test.js test/fleet/fleet-cli.test.js test/artifact/safe-tar.test.js test/artifact/image-archive.test.js test/artifact/filesystem.test.js test/artifact/layer-secrets.test.js test/artifact/inventory.test.js test/artifact/vuln-correlate.test.js test/artifact/signature.test.js test/artifact/provenance-graph.test.js test/artifact/mismatch.test.js test/artifact/vex.test.js test/artifact/registry.test.js test/artifact/sources-cosign-evidence.test.js test/cli/artifact-sources-cli.test.js test/cli/artifact-provenance-cli.test.js test/witness-bundle.test.js",
90
- "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-rust.test.js test/rust-taint-flow.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 test/dataflow-progress.test.js test/scan-progress.test.js test/sard-leakage-pipeline.test.js test/sard-split-scoring.test.js test/sard-llm-isolation.test.js test/sard-mutation-adversarial.test.js test/sard-baseline-adaptive-tolerance.test.js test/sard-independent-holdout.test.js test/sard-flow-aware-scoring.test.js test/sard-macro-score-support-floor.test.js test/sard-php-split.test.js test/parser-java-nary-concat.test.js test/sard-naive-baseline.test.js test/field-taint-cross-method.test.js test/field-taint-engine-unit.test.js test/coercion-sanitizer-kill.test.js test/constant-propagation-path-sensitivity.test.js test/parser-java-assignments.test.js test/catalog-java-sard.test.js test/parser-cs-juliet-shapes.test.js test/catalog-cs-sard-p2.test.js test/sard-cwe-key-merge.test.js test/parser-php-backtick.test.js test/parser-php-include.test.js test/php-include-merge.test.js test/parser-php-array-field-taint.test.js test/parser-php-cond-assign.test.js test/parser-rust-redos.test.js test/multi-sink-taint-chain.test.js test/static-field-cross-class-call-string.test.js test/regex-guard-narrowing.test.js test/object-instance-field-taint.test.js test/param-type-inference.test.js test/smt-path-feasibility.test.js test/cross-service-taint.test.js test/dataflow-expr-taint-scaling.test.js test/evaluation/guard-dominance.test.js test/evaluation/engine-mechanisms.test.js test/rx-exec-receiver.test.js test/incremental-dependency.test.js test/incremental-integrity.test.js test/demand-query.test.js test/ir-cache.test.js test/canonicalize-parity.test.js",
89
+ "test:posture": "node --test test/contracts/root-cause-sweep.contract.test.js test/contracts/model-validation.contract.test.js test/contracts/agentic-discovery.contract.test.js test/contracts/local-models.contract.test.js test/contracts/dependency-finding.contract.test.js test/contracts/taint-flow.contract.test.js test/contracts/runtime-corroboration.contract.test.js test/compliance-coverage.test.js test/contracts/blast-radius.contract.test.js test/contracts/federation.contract.test.js test/contracts/eval-lab-report.contract.test.js test/contracts/fleet.contract.test.js test/contracts/rule-compiler.contract.test.js test/contracts/release-assurance.contract.test.js test/contracts/dnr.contract.test.js test/contracts/strategic-metrics.contract.test.js test/contracts/exploit-proof.contract.test.js test/contracts/artifact-provenance.contract.test.js test/dnr/artifact-join.test.js test/rule-pack-signing.test.js test/threat-model.test.js 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/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.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/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.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/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.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/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.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 test/posture/assurance-baseline.test.js test/posture/assurance-contracts.test.js test/posture/assurance-config.test.js test/evidence-issuer.test.js test/load-factor.test.js test/fleet/job-store.test.js test/fleet/enterprise-graph.test.js test/fleet/discover-edges.test.js test/fleet/code-boundaries.test.js test/fleet/job-runner.test.js test/fleet/fleet-org-cli.test.js test/eval-lab/dataset-neutralize.test.js test/eval-lab/arms-score-stats.test.js test/eval-lab/run-report.test.js test/eval-lab/router.test.js test/eval-lab/arm-ollama.test.js test/eval-lab/import-independent.test.js test/cli/eval-lab-cli.test.js test/dnr/ir-and-outputs.test.js test/dnr/logs-and-hunt.test.js test/dnr/window-store-view.test.js test/dnr/exploit-correlation.test.js test/cli/dnr-cli.test.js test/proof-fabric/core.test.js test/proof-fabric/container.test.js test/proof-fabric/generate.test.js test/proof-fabric/siblings.test.js test/proof-fabric/generate-python.test.js test/proof-fabric/generate-haskell.test.js test/proof-fabric/generate-haskell-widened.test.js test/cli/proof-lifecycle-demo.test.js test/mcp-proof.test.js test/proof-fabric/fix-store-runner.test.js test/proof-fabric/fixtures.test.js test/cli/proof-fabric-cli.test.js test/rule-compiler/core.test.js test/rule-compiler/synthesis.test.js test/rule-compiler/structure.test.js test/cli/rule-compiler-cli.test.js test/fleet/fleet-graph.test.js test/fleet/fleet-manifest.test.js test/fleet/fleet-cli.test.js test/artifact/safe-tar.test.js test/artifact/image-archive.test.js test/artifact/filesystem.test.js test/artifact/layer-secrets.test.js test/artifact/inventory.test.js test/artifact/vuln-correlate.test.js test/artifact/signature.test.js test/artifact/provenance-graph.test.js test/artifact/mismatch.test.js test/artifact/vex.test.js test/artifact/registry.test.js test/artifact/sources-cosign-evidence.test.js test/cli/artifact-sources-cli.test.js test/cli/artifact-provenance-cli.test.js test/witness-bundle.test.js",
90
+ "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-rust.test.js test/rust-taint-flow.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 test/dataflow-progress.test.js test/scan-progress.test.js test/sard-leakage-pipeline.test.js test/sard-split-scoring.test.js test/sard-llm-isolation.test.js test/sard-mutation-adversarial.test.js test/sard-baseline-adaptive-tolerance.test.js test/sard-independent-holdout.test.js test/sard-flow-aware-scoring.test.js test/sard-macro-score-support-floor.test.js test/sard-php-split.test.js test/parser-java-nary-concat.test.js test/sard-naive-baseline.test.js test/field-taint-cross-method.test.js test/field-taint-engine-unit.test.js test/coercion-sanitizer-kill.test.js test/constant-propagation-path-sensitivity.test.js test/parser-java-assignments.test.js test/catalog-java-sard.test.js test/parser-cs-juliet-shapes.test.js test/catalog-cs-sard-p2.test.js test/sard-cwe-key-merge.test.js test/parser-php-backtick.test.js test/parser-php-include.test.js test/php-include-merge.test.js test/parser-php-array-field-taint.test.js test/parser-php-cond-assign.test.js test/parser-rust-redos.test.js test/multi-sink-taint-chain.test.js test/static-field-cross-class-call-string.test.js test/regex-guard-narrowing.test.js test/object-instance-field-taint.test.js test/param-type-inference.test.js test/smt-path-feasibility.test.js test/cross-service-taint.test.js test/dataflow-expr-taint-scaling.test.js test/evaluation/guard-dominance.test.js test/evaluation/engine-mechanisms.test.js test/rx-exec-receiver.test.js test/incremental-dependency.test.js test/incremental-integrity.test.js test/demand-query.test.js test/ir-cache.test.js test/canonicalize-parity.test.js test/dataflow-source-attribution.test.js",
91
91
  "test:mcp": "node --test test/mcp.test.js test/mcp-apply-fix-multifile.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 test/capabilities/tools.test.js",
92
92
  "test:report": "node --test test/report-scan-invariants.test.js 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/git-history-batch.test.js test/default-scan-drift.test.js test/bundle-optional-deps.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",
93
93
  "test:lineage": "node --test test/lineage/analysis-completion.test.js 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 test/lineage/runtime-assurance.test.js test/lineage/runtime-edge-states.test.js test/lineage/runtime-explorer-view.test.js test/cli/runtime-explorer-cli.test.js test/cli/runtime-identity-cli.test.js test/cli/runtime-twin-cli.test.js test/lineage/cut-planner.test.js test/lineage/scc.test.js test/lineage/scc-solver.test.js test/graph/graph-views.test.js test/lineage/dag07-completion.test.js test/lineage/owned-build.test.js test/dataflow/boundary-propagation.test.js test/lineage/boundary-contract.test.js test/lineage/correlation-v2.test.js test/lineage/path-control-evidence.test.js test/lineage/smt-z3-backend.test.js test/cli/dag-commands.test.js",
94
94
  "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",
95
95
  "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",
96
- "test:haskell": "node --test test/haskell/haskell-weak-hash-names.test.js test/haskell/haskell-models-extended.test.js test/haskell/haskell-rules-extended.test.js test/haskell/haskell-injection.test.js test/haskell/haskell-ir.test.js test/haskell/haskell-manifests.test.js test/haskell/haskell-parser.test.js test/haskell/haskell-remediation.test.js test/haskell/haskell-resolved-graph.test.js test/haskell/haskell-sca.test.js test/haskell/haskell-sca-severity.test.js test/haskell/haskell-web-guards.test.js test/haskell/haskell-advisory-feed.test.js test/haskell/haskell-security-rules.test.js test/haskell/haskell-taint.test.js test/haskell/haskell-boundaries.test.js test/haskell/haskell-live-feed-scan.test.js test/haskell/haskell-supply-bounds.test.js test/haskell/haskell-ir-scaling.test.js test/haskell/haskell-boundary-propagation.test.js",
96
+ "test:haskell": "node --test test/haskell/haskell-alias-taint.test.js test/haskell/haskell-weak-hash-names.test.js test/haskell/haskell-models-extended.test.js test/haskell/haskell-rules-extended.test.js test/haskell/haskell-injection.test.js test/haskell/haskell-ir.test.js test/haskell/haskell-manifests.test.js test/haskell/haskell-parser.test.js test/haskell/haskell-remediation.test.js test/haskell/haskell-resolved-graph.test.js test/haskell/haskell-sca.test.js test/haskell/haskell-sca-severity.test.js test/haskell/haskell-web-guards.test.js test/haskell/haskell-advisory-feed.test.js test/haskell/haskell-security-rules.test.js test/haskell/haskell-taint.test.js test/haskell/haskell-boundaries.test.js test/haskell/haskell-live-feed-scan.test.js test/haskell/haskell-supply-bounds.test.js test/haskell/haskell-ir-scaling.test.js test/haskell/haskell-boundary-propagation.test.js test/haskell/haskell-generalization.test.js test/haskell/haskell-trust-boundary.test.js test/haskell/haskell-comment-invariance.test.js test/haskell/haskell-remediation-classes.test.js test/haskell/haskell-trust-boundary-frameworks.test.js test/haskell/haskell-generic-detectors.test.js",
97
97
  "test:nix": "node --test test/nix/nix-option-types.test.js test/nix/nixos-hardening-daemon.test.js test/nix/nixos-hardening-extended.test.js test/nix/nix-build-trust.test.js test/nix/nix-eval-isolation.test.js test/nix/nix-input-inventory.test.js test/nix/nix-parser-ir.test.js test/nix/nix-remediation.test.js test/nix/nix-resolved-closure.test.js test/nix/nix-sca-patches.test.js test/nix/nix-sca-severity.test.js test/nix/nix-haskell-closure-names.test.js test/nix/nix-script-taint.test.js test/nix/nix-secrets-store.test.js test/nix/nixos-hardening.test.js test/nix/nixos-module-resolution.test.js test/nix/nix-fix-multifile.test.js test/nix/nixos-evaluator-forms.test.js test/nix/nix-advisory-feed.test.js test/nix/nix-fix-prewrite.test.js test/nix/nix-boundary-scope.test.js",
98
- "test:language": "node --test test/language/capability-ledger.test.js test/language/language-accuracy-regression.test.js test/language/language-aibom.test.js test/language/language-assurance.test.js test/language/language-bom.test.js test/language/language-compliance.test.js test/language/language-contracts.test.js test/language/language-corpora-integrity.test.js test/language/language-discovery.test.js test/language/language-doc-coverage.test.js test/language/language-evidence-posture.test.js test/language/language-explorer.test.js test/language/language-gap-fixes.test.js test/language/language-gap-fixes-2.test.js test/language/language-governance.test.js test/language/language-integrations.test.js test/language/language-llm-agent.test.js test/language/language-local-ai.test.js test/language/language-polyglot-bridges.test.js test/language/language-privacy-lineage.test.js test/language/language-proof-remediation.test.js test/language/language-report-formats.test.js test/language/language-secrets.test.js test/language/language-trust-boundary.test.js",
98
+ "test:language": "node --test test/language/capability-ledger.test.js test/language/language-accuracy-regression.test.js test/language/language-aibom.test.js test/language/language-assurance.test.js test/language/language-bom.test.js test/language/language-compliance.test.js test/language/language-contracts.test.js test/language/language-corpora-integrity.test.js test/language/language-discovery.test.js test/language/language-doc-coverage.test.js test/language/language-evidence-posture.test.js test/language/language-explorer.test.js test/language/language-gap-fixes.test.js test/language/language-gap-fixes-2.test.js test/language/language-governance.test.js test/language/language-integrations.test.js test/language/language-llm-agent.test.js test/language/language-local-ai.test.js test/language/language-parity-haskell.test.js test/language/language-polyglot-bridges.test.js test/language/language-privacy-lineage.test.js test/language/language-proof-remediation.test.js test/language/language-report-formats.test.js test/language/language-secrets.test.js test/language/language-trust-boundary.test.js",
99
99
  "test:language-stress": "node --test --test-concurrency=1 test/language/language-stress-offline.test.js",
100
100
  "test:language-tools": "node --test test/haskell/haskell-web-auth.test.js test/haskell/haskell-support-gate.test.js",
101
101
  "bench:language-support:check": "node ../bench/language-support/check.mjs",
@@ -42,8 +42,14 @@ const INTERPRETER_NAMES = new Set([
42
42
  'time', 'timeout', 'sudo', 'doas', 'su', 'ssh', 'osascript', 'launchctl', 'script', 'expect', 'tclsh', 'wish',
43
43
  'awk', 'gawk', 'mawk', 'nawk', 'java', 'jshell', 'deno', 'bun', 'nodejs', 'swift', 'make', 'gmake',
44
44
  'npm', 'npx', 'yarn', 'pnpm', 'pip', 'pip3', 'pipx', 'gem', 'bundle', 'cargo', 'go', 'gradle', 'mvn', 'watch',
45
+ // Haskell and Nix toolchains run code supplied by the project: GHC and cabal execute Template Haskell splices and a
46
+ // custom Setup.hs at build time, runghc/ghci interpret a source file, and the Nix evaluators run the expressions they
47
+ // are given (builders run the derivation's build script).
48
+ 'ghc', 'ghci', 'runghc', 'runhaskell', 'cabal', 'stack',
49
+ 'nix', 'nix-build', 'nix-shell', 'nix-instantiate', 'nix-env', 'nix-store',
45
50
  ]);
46
- const INTERPRETER_FAMILY = /^(?:python|pypy|ruby|perl|php|lua|luajit|node|tclsh)[0-9.]*$/;
51
+ // Versioned toolchain binaries (`ghc-9.14.1`, `runghc-9.14.1`, `cabal-3.14`) are the same executables under another name.
52
+ const INTERPRETER_FAMILY = /^(?:python|pypy|ruby|perl|php|lua|luajit|node|tclsh)[0-9.]*$|^(?:ghc|ghci|runghc|runhaskell|cabal|stack)-[0-9][0-9.]*$/;
47
53
 
48
54
  /**
49
55
  * What kind of executable this is. `interpreter` is true for a known shell or
@@ -38,6 +38,19 @@ Layer-2 taint engine. Walks the Layer-1 IR (`../ir/`) with field-sensitive forwa
38
38
  - **Flat-string callees — the one that mattered most.** The mutator rule tested `callee.kind === 'member'`, a shape ONLY `parser-js.js` (Babel) emits. The seven hand-rolled parsers emit a flat dot-joined string (`"items.append"`), so container writes in Python/Ruby/PHP/Go/Java/C#/Kotlin never matched at all. Same frontend duality `_calleeReceiverTainted` documents. This was invisible from the outside because a PY-SAST pattern rule caught the same sink, so the taint-layer miss looked like a detection.
39
39
  - **Measured honestly:** `bench:layer-recall` is UNCHANGED at 116/215 after this landed — the corpus holds no container-shaped entries, which is exactly why the taint-recall-to-80% push closed at 83.9% (115/137 of the dataflow-shaped subset) "without Tier 5". This is capability proven by targeted tests (`test/container-taint.test.js`, including two precision cases that must NOT fire), not a measured recall gain. Do not quote it as one. Self-scan held at 427 with zero new findings across ~700 files.
40
40
 
41
+ ## Source attribution (evidence chain origin)
42
+
43
+ A finding's `source`/`chain` come from `_taintSources`: records keyed by the variable a source call was assigned to. Before this was
44
+ extended, a value DERIVED from a source (`b = 'ls ' + a`), a value that crossed a call, or one returned from a function reached the sink with an
45
+ empty chain, so nothing downstream could tell a request read from a command-line argument. Now: `_inheritSources` copies the records reaching
46
+ a derived value; a summary computed under an entry-taint state names its tainted parameters with placeholder records (`_paramPlaceholders`,
47
+ `isParam`) that `_mergeSummaryFindings` resolves, at the call site that merges the finding, to the sources of the actual arguments
48
+ (`_resolveParams`); a function's return keeps the sources behind it (`returnSources`, followed through `from` into the finding's
49
+ `sourceRoots`). A parameter annotation that is a catalog source is recorded too (`matchAnnotationParamSources`, `annotation: true`, mapped to
50
+ `source.kind: 'parameter'`). The summary cache is keyed by entry state only, so the cached summary never carries a caller's sources: they
51
+ are added to a COPY at each merge. Pinned in `test/dataflow-source-attribution.test.js`. The multi-sink aggregation in `engine.js` ignores
52
+ `info` findings (a finding lowered by a guard or a trust verdict is not a flow worth aggregating).
53
+
41
54
  ## Scope — what we still do NOT model (today)
42
55
 
43
56
  - **Call-string (k>1) context-sensitivity.** Context is the *value* abstraction — which params are tainted at entry — not the call stack. Two call paths that reach a helper with the same tainted-arg shape share a summary. Entry-state granularity is also param-level, not arbitrary access paths (`f(obj)` with `obj.a` tainted ≡ `obj.b` tainted).
@@ -182,3 +195,5 @@ member-based: `hs-src-caller-controlled-param` (a text parameter of an exported
182
195
  `hs-lin-src-record-parameter` (the lineage form of a record parameter). `engine.js` skips the literal-skeleton filter for Haskell
183
196
  catalog entries and requires a markup literal for `htmlSkeleton` sinks. Pinned counts in the lineage registry tests change
184
197
  deliberately when the model registry grows. Details: `scanner/src/language/CLAUDE.md`.
198
+
199
+ Three Haskell-scoped behaviours live in `engine.js` (all guarded by `_currentFile` being a `.hs`/`.lhs` file, so no other language changes): a `union` (the branches of an `if`, a `case`, guards) handed to another call as an argument (`get "/p" $ do .. case m of A -> sink x`) or bound as a statement is a sink site in EACH branch (`_nestedSinkFindings`; the `assign` case); `exprTaint` of a `member` whose object is a call result (the elements of `words s`) is the object's taint; and the catalog gains four annotation sources, `hs-src-handler-param-<framework>` (a text parameter of an exported function that returns a Scotty/Yesod/Servant/WAI handler value: request input, provenance `url-param`), beside the weaker `hs-src-caller-controlled-param`.
@@ -3,7 +3,7 @@
3
3
  // callee the Haskell IR produces (`System.Process.callCommand`), never a bare name, so a user's own
4
4
  // function with the same name resolves to its own module and cannot match.
5
5
 
6
- import { HS_SOURCES, HS_SINKS, HS_SANITIZERS } from '../language/haskell-models.js';
6
+ import { HS_SOURCES, HS_SINKS, HS_SANITIZERS, HS_HANDLER_PARAM_SOURCES } from '../language/haskell-models.js';
7
7
 
8
8
  const idOf = (kind, e, extra = '') => `hs-${kind}-${e.module}.${e.name}${extra}`.replace(/[^A-Za-z0-9.\-_]/g, '-').toLowerCase();
9
9
 
@@ -30,6 +30,12 @@ export const HASKELL_CATALOG = Object.freeze([
30
30
  // LINEAGE form of a record parameter of an exported function (see lineage/haskell-view.js)
31
31
  { kind: 'source', id: 'hs-lin-src-record-parameter', language: 'hs', framework: 'haskell', match: { type: 'member', object: '$hs', prop: 'hs:record-parameter' }, label: 'record parameter of an exported function (caller-supplied customer data)' },
32
32
  { kind: 'source', id: 'hs-src-caller-controlled-param', language: 'hs', framework: 'haskell', match: { type: 'annotation', name: 'hs:caller-controlled' }, label: 'text parameter of an exported function (caller-controlled)' },
33
+ // A text parameter of an exported function that returns a framework handler value: request input the framework passed in (see
34
+ // language/haskell-models.js `HS_HANDLER_PARAM_SOURCES`). A plain library export keeps the weaker caller-controlled source above.
35
+ ...Object.values(HS_HANDLER_PARAM_SOURCES).map((d) => ({
36
+ kind: 'source', id: `hs-src-handler-param-${d.framework}`, language: 'hs', framework: d.framework,
37
+ match: { type: 'annotation', name: d.decorator }, label: d.label, provenance: d.provenance,
38
+ })),
33
39
  ...HS_SANITIZERS.map((e) => ({
34
40
  kind: 'sanitizer', id: idOf('san', e), language: 'hs',
35
41
  match: { type: 'call', callee: `${e.module}.${e.name}` }, effect: 'strip', appliesTo: e.appliesTo, note: e.note,
@@ -2743,5 +2743,19 @@ export function matchAnnotationParams(paramAnnotations, file) {
2743
2743
  return tainted;
2744
2744
  }
2745
2745
 
2746
+ // The same lookup as matchAnnotationParams, keeping what each matched source says about itself so the engine can record an origin for the
2747
+ // parameter (an evidence chain that begins at "parameter x of f, request input" instead of at the sink).
2748
+ export function matchAnnotationParamSources(paramAnnotations, file) {
2749
+ const out = [];
2750
+ if (!paramAnnotations || !paramAnnotations.length) return out;
2751
+ for (const pa of paramAnnotations) {
2752
+ const raw = ANNOTATION_INDEX.get(pa.decorator);
2753
+ if (!raw) continue;
2754
+ const hit = filterByProvenance(raw).filter(h => _languageAllowed(h, file)).find(h => h.kind === 'source');
2755
+ if (hit) out.push({ name: pa.name, id: hit.id, label: hit.label || pa.decorator, provenance: hit.provenance || null });
2756
+ }
2757
+ return out;
2758
+ }
2759
+
2746
2760
  // For tests and reflection.
2747
2761
  export function _catalogSize() { return CATALOG.length; }
@@ -42,7 +42,7 @@
42
42
  // sanitizer must never silently drop a real vulnerability, so the walk never
43
43
  // treats a sanitizer call as clearing the tainted path on its own.
44
44
 
45
- import { matchSource, matchSinkOrSanitizer, matchMemberWriteSink, matchAnnotationParams } from './catalog.js';
45
+ import { matchSource, matchSinkOrSanitizer, matchMemberWriteSink, matchAnnotationParams, matchAnnotationParamSources } from './catalog.js';
46
46
  import { functionRecord } from '../ir/callgraph.js';
47
47
  import { accessPathOf, isCoveredBy, hasTaintedDescendant, addPath, removePathAndDescendants, joinSets as joinAccessSets, setsEqual as accessSetsEqual, pathIsCoveredByPrefix } from './access-paths.js';
48
48
  import { aliasesForVar } from './points-to.js';
@@ -394,7 +394,7 @@ function _nestedCallReturnTainted(calleeExpr, argExprs, state, callContext, argT
394
394
  if (!sum && fn && fn.cfg) {
395
395
  sum = callContext._summaryCache.compute(qid, entry, () => {
396
396
  const inner = {
397
- _findings: [], _taintSources: [], _returnTainted: false,
397
+ _findings: [], _taintSources: _paramPlaceholders(fn, entry), _returnTainted: false,
398
398
  _stack: new Set(), deadlineMs: callContext.deadlineMs,
399
399
  _summaryCache: callContext._summaryCache,
400
400
  _callGraph: callContext._callGraph,
@@ -404,6 +404,7 @@ function _nestedCallReturnTainted(calleeExpr, argExprs, state, callContext, argT
404
404
  try { analyzeFunction(fn, _unionAnnotationTaint(fn, entry), inner); } catch {}
405
405
  return {
406
406
  returnTainted: !!inner._returnTainted,
407
+ returnSources: inner._returnSources || [],
407
408
  mutatedParams: inner._mutatedParamsOut || new Set(),
408
409
  mutatedThisFields: inner._mutatedThisFieldsOut || new Set(),
409
410
  taintedGlobals: new Set(),
@@ -411,7 +412,7 @@ function _nestedCallReturnTainted(calleeExpr, argExprs, state, callContext, argT
411
412
  };
412
413
  });
413
414
  }
414
- _mergeSummaryFindings(callContext, callContext._currentFnQid, sum, 'interproc');
415
+ _mergeSummaryFindings(callContext, callContext._currentFnQid, sum, 'interproc', { args: argExprs || [], state });
415
416
  // Next-gen taint capability #3: a single-field getter's return is tainted
416
417
  // whenever THIS call's own receiver already carries that field as
417
418
  // tainted, independent of `sum.returnTainted` (which reflects only the
@@ -628,6 +629,9 @@ function exprTaint(expr, state, callContext) {
628
629
  return argsTainted || nestedTainted || receiverTainted;
629
630
  }
630
631
  case 'unknown': return false;
632
+ // Haskell: the element of a collection that is itself a call result (`words s`'s elements, `lines body`'s) is a `member` whose object is a call,
633
+ // which is not an access path. An element of a tainted collection is tainted; reading a field of a tainted value is the same fact.
634
+ case 'member': return /\.(?:hs|lhs)$/i.test(_currentFile || '') ? exprTaint(expr.object, state, callContext) : false;
631
635
  default: return false;
632
636
  }
633
637
  }
@@ -1430,6 +1434,15 @@ const _sinkLine = (call, fallback) => (/\.(?:hs|lhs)$/i.test(_currentFile || '')
1430
1434
  function _nestedSinkFindings(argExprs, state, callContext, line) {
1431
1435
  const findings = [];
1432
1436
  for (const arg of (argExprs || [])) {
1437
+ // Haskell: a conditional or `case` handed to a combinator (`get "/p" $ do ... case m of A -> sink x; B -> next`) is a union of the branch
1438
+ // ACTIONS, each of which runs when its branch is taken: every branch call is a sink site, as at the top of a return (see the `return` case).
1439
+ if (arg && arg.kind === 'union' && /\.(?:hs|lhs)$/i.test(_currentFile || '')) {
1440
+ const leaves = [];
1441
+ const walk = (v, d) => { if (!v || d > 40) return; if (v.kind === 'union') (v.branches || []).forEach((b) => walk(b, d + 1)); else if (v.kind === 'call') leaves.push(v); };
1442
+ walk(arg, 0);
1443
+ findings.push(..._nestedSinkFindings(leaves, state, callContext, line));
1444
+ continue;
1445
+ }
1433
1446
  if (!arg || arg.kind !== 'call') continue;
1434
1447
  const { cat, argTaints } = _matchCallCatalog(arg.callee, arg.args, state, callContext);
1435
1448
  const at = _sinkLine(arg, line);
@@ -1490,14 +1503,100 @@ function _memberWriteSinkFindings(hits, sourceExpr, state, callContext, line, ta
1490
1503
  // list: a chain of 20 one-line wrappers produced 131072 copies of one finding, and the explosion ended in the whole result being dropped. The
1491
1504
  // same sink reached again by the same finding is one finding, so a copy is skipped when this context already holds it.
1492
1505
  function _findingKey(f) { return `${f.file ?? ''}|${f.line ?? ''}|${f.cwe ?? ''}|${f.vuln ?? ''}|${typeof f.callee === 'string' ? f.callee : ''}|${f.id ?? ''}`; }
1493
- function _mergeSummaryFindings(callContext, callerQid, sum, via) {
1506
+ // A variable that is TAINTED BY DERIVATION (`y = f(x)`, `(a : b) <- return x`) has no source of its own: the sources the engine recorded are
1507
+ // keyed by the variable a source call was assigned to. Without a record for `y` the evidence chain of a sink reached through `y` begins at the
1508
+ // sink, and nothing downstream can say where the data came from. The sources reaching the expression it was derived from are copied to it.
1509
+ // Bounded: at most 5 per assignment and 4000 records per analysis; the original record is untouched.
1510
+ // A callee is analysed once per ENTRY STATE (which parameters carry taint), not per caller, so inside that analysis a tainted parameter has no
1511
+ // origin of its own. Each tainted parameter gets a placeholder record (`isParam`); a finding or return that rests on it is resolved to the
1512
+ // sources of the arguments at the call site that merges it (`_resolveParams`), and a placeholder that no call site resolves is dropped.
1513
+ function _paramPlaceholders(fn, entry) {
1514
+ const out = [];
1515
+ if (!fn || !Array.isArray(fn.params) || !entry || !entry.size) return out;
1516
+ for (const p of fn.params) {
1517
+ if (typeof p !== 'string') continue;
1518
+ let hit = entry.has(p);
1519
+ if (!hit) for (const x of entry) if (typeof x === 'string' && x.startsWith(p + '.')) { hit = true; break; }
1520
+ if (hit) out.push({ varName: p, sourceId: `param:${fn.qid}:${p}`, sourceLabel: `parameter ${p}`, provenance: 'param', line: fn.line || 0, isParam: true });
1521
+ }
1522
+ return out;
1523
+ }
1524
+
1525
+ function _resolveParams(entries, replacement, depth = 0) {
1526
+ const out = [];
1527
+ const seen = new Set();
1528
+ const add = (e) => { const k = `${e.sourceId}|${e.line}`; if (!seen.has(k)) { seen.add(k); out.push(e); } };
1529
+ for (const e of entries || []) {
1530
+ if (!e) continue;
1531
+ if (e.isParam) { for (const r of replacement || []) add(r); continue; }
1532
+ if (Array.isArray(e.from) && e.from.length && depth < 8) add({ ...e, from: _resolveParams(e.from, replacement, depth + 1) });
1533
+ else add(e);
1534
+ }
1535
+ return out;
1536
+ }
1537
+
1538
+ function _inheritSources(callContext, target, exprs, state) {
1539
+ const list = callContext._taintSources;
1540
+ if (!target || !Array.isArray(list) || list.length === 0 || list.length > 4000) return;
1541
+ let n = 0;
1542
+ for (const ex of exprs) {
1543
+ for (const s of _sourcesReachingExpr(ex, state, list)) {
1544
+ if (n >= 5) return;
1545
+ if (s.varName === target) continue;
1546
+ if (list.some((x) => x.varName === target && x.sourceId === s.sourceId && x.line === s.line)) continue;
1547
+ list.push({ ...s, varName: target, derivedFrom: s.varName });
1548
+ n++;
1549
+ }
1550
+ }
1551
+ }
1552
+
1553
+ function _rootSources(trace, depth = 0, out = [], seen = new Set()) {
1554
+ for (const t of trace || []) {
1555
+ if (!t) continue;
1556
+ if (Array.isArray(t.from) && t.from.length && depth < 8) { _rootSources(t.from, depth + 1, out, seen); continue; }
1557
+ const k = `${t.sourceLabel}|${t.line}|${t.provenance}`;
1558
+ if (!seen.has(k)) { seen.add(k); out.push({ label: t.sourceLabel, provenance: t.provenance || null, line: t.line, file: t.file || null }); }
1559
+ }
1560
+ return out;
1561
+ }
1562
+
1563
+ function _mergeSummaryFindings(callContext, callerQid, sum, via, callSite) {
1494
1564
  if (!sum || !Array.isArray(sum.findings) || !sum.findings.length) return;
1495
1565
  if (!callContext._findingKeys) callContext._findingKeys = new Set(callContext._findings.map(_findingKey));
1566
+ // A callee's finding was computed under an entry-taint state (which parameters carry taint), not under any
1567
+ // particular caller, so the summary cannot name where that taint came from. THIS call site can: the sources the
1568
+ // caller's own analysis recorded for the arguments it passes. A finding with no source of its own inherits them
1569
+ // (a copy; the shared summary is untouched). Without this an evidence chain begins at the sink with no origin,
1570
+ // and nothing downstream can tell a request value from a command-line argument.
1571
+ let argSources = null;
1572
+ const inherited = () => {
1573
+ if (argSources) return argSources;
1574
+ argSources = [];
1575
+ if (callSite && Array.isArray(callSite.args)) {
1576
+ const seen = new Set();
1577
+ for (const a of callSite.args) {
1578
+ for (const src of _sourcesReachingExpr(a, callSite.state, callContext._taintSources)) {
1579
+ const k = `${src.sourceId}|${src.line}`;
1580
+ if (!seen.has(k)) { seen.add(k); argSources.push(src); }
1581
+ }
1582
+ }
1583
+ }
1584
+ return argSources;
1585
+ };
1496
1586
  for (const f of sum.findings) {
1497
1587
  const key = _findingKey(f);
1498
1588
  if (callContext._findingKeys.has(key)) continue;
1499
1589
  callContext._findingKeys.add(key);
1500
- callContext._findings.push({ ...f, _funcQid: callerQid || null, _via: via });
1590
+ let out = { ...f, _funcQid: callerQid || null, _via: via };
1591
+ const tr = Array.isArray(f.trace) ? f.trace : [];
1592
+ if (tr.some((t) => t && (t.isParam || (t.from && t.from.length)))) {
1593
+ const resolved = _resolveParams(tr, callSite ? inherited() : []).slice(0, 5);
1594
+ out = { ...out, trace: resolved, sourceProvenance: resolved[0] ? (resolved[0].provenance || null) : null };
1595
+ } else if (callSite && tr.length === 0) {
1596
+ const inh = inherited();
1597
+ if (inh.length) out = { ...out, trace: inh.slice(0, 5), sourceProvenance: inh[0].provenance || null };
1598
+ }
1599
+ callContext._findings.push(out);
1501
1600
  }
1502
1601
  }
1503
1602
 
@@ -1541,6 +1640,10 @@ function step(node, stateIn, callContext) {
1541
1640
  node.source.callee, node.source.args, _sinkCat, _sinkArgTaints,
1542
1641
  state, callContext, node.line, node.source.kwargs).findings);
1543
1642
  findings.push(..._nestedSinkFindings(node.source.args, state, callContext, node.line));
1643
+ } else if (node.source && node.source.kind === 'union' && /\.(?:hs|lhs)$/i.test(_currentFile || '')) {
1644
+ // Haskell: a conditional or `case` used as a STATEMENT (not the last one) is bound to a scratch name; every branch action in it runs when its
1645
+ // branch is taken, so each is a sink site (the last statement of a block is handled by the `return` case).
1646
+ findings.push(..._nestedSinkFindings([node.source], state, callContext, node.line));
1544
1647
  }
1545
1648
  // PRD R13(a): the assignment TARGET can itself be a sink shape
1546
1649
  // (el.innerHTML = tainted) — additive to the RHS-call-sink check
@@ -1618,7 +1721,7 @@ function step(node, stateIn, callContext) {
1618
1721
  // callee's internal noise.
1619
1722
  sum = callContext._summaryCache.compute(qid, entry, () => {
1620
1723
  const inner = {
1621
- _findings: [], _taintSources: [], _returnTainted: false,
1724
+ _findings: [], _taintSources: _paramPlaceholders(fn, entry), _returnTainted: false,
1622
1725
  _stack: new Set(), deadlineMs: callContext.deadlineMs,
1623
1726
  _summaryCache: callContext._summaryCache,
1624
1727
  _callGraph: callContext._callGraph,
@@ -1628,6 +1731,7 @@ function step(node, stateIn, callContext) {
1628
1731
  try { analyzeFunction(fn, _unionAnnotationTaint(fn, entry), inner); } catch {}
1629
1732
  return {
1630
1733
  returnTainted: !!inner._returnTainted,
1734
+ returnSources: inner._returnSources || [],
1631
1735
  mutatedParams: inner._mutatedParamsOut || new Set(),
1632
1736
  mutatedThisFields: inner._mutatedThisFieldsOut || new Set(),
1633
1737
  taintedGlobals: new Set(),
@@ -1643,7 +1747,7 @@ function step(node, stateIn, callContext) {
1643
1747
  };
1644
1748
  });
1645
1749
  }
1646
- _mergeSummaryFindings(callContext, callContext._currentFnQid, sum, 'interproc');
1750
+ _mergeSummaryFindings(callContext, callContext._currentFnQid, sum, 'interproc', { args: callArgs, state: callerTainted });
1647
1751
  // Next-gen taint capability #3: `x = bad.getData()` — a single-
1648
1752
  // field getter's return is tainted whenever THIS call's own
1649
1753
  // receiver already carries that field, independent of `sum`
@@ -1651,12 +1755,19 @@ function step(node, stateIn, callContext) {
1651
1755
  const _getterTainted = _calleeGetterFieldTainted(node.source.callee, newState, callContext);
1652
1756
  if ((sum && sum.returnTainted) || _getterTainted) {
1653
1757
  newState = _addPathAliasAware(newState, target, callContext);
1758
+ // `from`: what made the arguments tainted. The return is the callee's, but the taint it carries began wherever the
1759
+ // caller's own arguments got theirs, and a reader (or a trust-boundary check) needs that origin, not the hop.
1760
+ const _from = [];
1761
+ for (const a of callArgs) for (const sr of _sourcesReachingExpr(a, callerTainted, callContext._taintSources)) _from.push(sr);
1762
+ if (sum && Array.isArray(sum.returnSources)) for (const sr of _resolveParams(sum.returnSources, _from.slice())) _from.push(sr);
1654
1763
  callContext._taintSources.push({
1655
1764
  varName: target,
1656
1765
  sourceId: `interproc:${qid}`,
1657
1766
  sourceLabel: `interproc-return:${calleeName}`,
1658
1767
  provenance: 'interproc',
1659
1768
  line: node.line,
1769
+ file: _currentFile || null,
1770
+ ...(_from.length ? { from: _from.slice(0, 5) } : {}),
1660
1771
  });
1661
1772
  }
1662
1773
  // applyAtCallSite — mutated params (and, next-gen capability #3,
@@ -1677,6 +1788,7 @@ function step(node, stateIn, callContext) {
1677
1788
  const _argTainted = (node.source.args || []).some(a => exprTaint(a, newState, callContext));
1678
1789
  if (builtin.returnTainted && _argTainted) {
1679
1790
  newState = _addPathAliasAware(newState, target, callContext);
1791
+ _inheritSources(callContext, target, node.source.args || [], newState);
1680
1792
  } else if (!builtin.returnTainted) {
1681
1793
  // PRD R4b: a builtin summary saying returnTainted:false can mean
1682
1794
  // two very different things — a genuinely non-deriving function
@@ -1738,7 +1850,7 @@ function step(node, stateIn, callContext) {
1738
1850
  newState = _addPathAliasAware(newState, target, callContext);
1739
1851
  const sourcePath = accessPathOf(node.source);
1740
1852
  if (sourcePath) newState = addPath(newState, sourcePath);
1741
- callContext._taintSources.push({ varName: target, sourceId: src.id, sourceLabel: src.label, provenance: src.provenance || null, line: node.line });
1853
+ callContext._taintSources.push({ varName: target, sourceId: src.id, sourceLabel: src.label, provenance: src.provenance || null, line: node.line, file: _currentFile || null });
1742
1854
  } else if (exprTaint(node.source, newState, callContext)) {
1743
1855
  // P1.1: when the source IS a pure access path (e.g., RHS is `obj.foo.bar`),
1744
1856
  // taint the TARGET as well as transitively propagate the source path so
@@ -1748,6 +1860,7 @@ function step(node, stateIn, callContext) {
1748
1860
  newState = _addPathAliasAware(newState, target, callContext);
1749
1861
  const sourcePath = accessPathOf(node.source);
1750
1862
  if (sourcePath && !isCoveredBy(newState, sourcePath)) newState = addPath(newState, sourcePath);
1863
+ _inheritSources(callContext, target, [node.source], newState);
1751
1864
  }
1752
1865
  } else {
1753
1866
  // Re-assigning a previously-tainted var to a clean value clears it
@@ -1786,7 +1899,7 @@ function step(node, stateIn, callContext) {
1786
1899
  if (!sum && entry.size && fn && fn.cfg) {
1787
1900
  sum = callContext._summaryCache.compute(qid, entry, () => {
1788
1901
  const inner = {
1789
- _findings: [], _taintSources: [], _returnTainted: false,
1902
+ _findings: [], _taintSources: _paramPlaceholders(fn, entry), _returnTainted: false,
1790
1903
  _stack: new Set(), deadlineMs: callContext.deadlineMs,
1791
1904
  _summaryCache: callContext._summaryCache,
1792
1905
  _callGraph: callContext._callGraph,
@@ -1796,6 +1909,7 @@ function step(node, stateIn, callContext) {
1796
1909
  try { analyzeFunction(fn, _unionAnnotationTaint(fn, entry), inner); } catch {}
1797
1910
  return {
1798
1911
  returnTainted: !!inner._returnTainted,
1912
+ returnSources: inner._returnSources || [],
1799
1913
  mutatedParams: inner._mutatedParamsOut || new Set(),
1800
1914
  mutatedThisFields: inner._mutatedThisFieldsOut || new Set(),
1801
1915
  taintedGlobals: new Set(),
@@ -1806,7 +1920,7 @@ function step(node, stateIn, callContext) {
1806
1920
  };
1807
1921
  });
1808
1922
  }
1809
- _mergeSummaryFindings(callContext, callContext._currentFnQid, sum, 'interproc');
1923
+ _mergeSummaryFindings(callContext, callContext._currentFnQid, sum, 'interproc', { args: node.args || [], state });
1810
1924
  // Next-gen taint capability #3: the plain-statement-position
1811
1925
  // counterpart of the assign-from-call site above — `bad.setData(
1812
1926
  // tainted);` never assigns anything, but the receiver's OWN field
@@ -2047,6 +2161,12 @@ function step(node, stateIn, callContext) {
2047
2161
  }
2048
2162
  if (exprTaint(node.value, state, callContext)) {
2049
2163
  callContext._returnTainted = true;
2164
+ // Where the returned taint began, for the caller that will name this function's return as a source (`from`, below).
2165
+ const rs = _sourcesReachingExpr(node.value, state, callContext._taintSources);
2166
+ if (rs.length) {
2167
+ const keep = (callContext._returnSources ||= []);
2168
+ for (const r of rs) if (keep.length < 8 && !keep.some((k) => k.sourceId === r.sourceId && k.line === r.line)) keep.push(r);
2169
+ }
2050
2170
  }
2051
2171
  return { state, findings };
2052
2172
  }
@@ -2180,6 +2300,13 @@ function analyzeFunction(fn, entryState, callContext) {
2180
2300
  // unchanged and lets `step` consult `aliasesForVar` when callContext._pointsTo
2181
2301
  // is present.
2182
2302
  if (callContext) callContext._currentFnQid = fn.qid;
2303
+ // A parameter the language front end marked as an input source (a request value handed to a handler, a caller-supplied text) has an
2304
+ // origin like any other source read: record it, so the evidence chain of a finding that rests on it names where the value came from.
2305
+ if (callContext && Array.isArray(callContext._taintSources) && Array.isArray(fn.paramAnnotations) && fn.paramAnnotations.length) {
2306
+ for (const r of matchAnnotationParamSources(fn.paramAnnotations, fn.file)) {
2307
+ if (!callContext._taintSources.some((x) => x.varName === r.name && x.sourceId === r.id)) callContext._taintSources.push({ varName: r.name, sourceId: r.id, sourceLabel: r.label, provenance: r.provenance, line: fn.line || 0, file: fn.file || null, annotation: true });
2308
+ }
2309
+ }
2183
2310
  const deadlineMs = (callContext && typeof callContext.deadlineMs === 'number') ? callContext.deadlineMs : Infinity;
2184
2311
  const visited = 0;
2185
2312
  let iterations = 0;
@@ -2494,6 +2621,7 @@ export function runTaintEngine(perFileIR, callGraph, opts = {}) {
2494
2621
  exitStateByQid.set(fn.qid, ctx._exitState || new Set());
2495
2622
  const next = {
2496
2623
  returnTainted: !!ctx._returnTainted,
2624
+ returnSources: ctx._returnSources || [],
2497
2625
  mutatedParams: ctx._mutatedParamsOut || new Set(),
2498
2626
  mutatedThisFields: ctx._mutatedThisFieldsOut || new Set(),
2499
2627
  taintedGlobals: new Set(),
@@ -2695,6 +2823,7 @@ export function runTaintEngine(perFileIR, callGraph, opts = {}) {
2695
2823
  // _mergeSummaryFindings — dedup in _collectFindings makes this safe.
2696
2824
  const fieldSummary = {
2697
2825
  returnTainted: !!ctx._returnTainted,
2826
+ returnSources: ctx._returnSources || [],
2698
2827
  mutatedParams: ctx._mutatedParamsOut || new Set(),
2699
2828
  mutatedThisFields: ctx._mutatedThisFieldsOut || new Set(),
2700
2829
  taintedGlobals: new Set(),
@@ -2801,6 +2930,7 @@ export function runTaintEngine(perFileIR, callGraph, opts = {}) {
2801
2930
  _collectFindings(fn, ctx._findings);
2802
2931
  summaryCache.set(fn.qid, qualifiedSeeds, {
2803
2932
  returnTainted: !!ctx._returnTainted,
2933
+ returnSources: ctx._returnSources || [],
2804
2934
  mutatedParams: ctx._mutatedParamsOut || new Set(),
2805
2935
  mutatedThisFields: ctx._mutatedThisFieldsOut || new Set(),
2806
2936
  taintedGlobals: new Set(),
@@ -2846,6 +2976,7 @@ export function runTaintEngine(perFileIR, callGraph, opts = {}) {
2846
2976
  // only place that used to merge findings) at all.
2847
2977
  summaryCache.set(fn.qid, taintedEntry, {
2848
2978
  returnTainted: !!ctx._returnTainted,
2979
+ returnSources: ctx._returnSources || [],
2849
2980
  mutatedParams: ctx._mutatedParamsOut || new Set(),
2850
2981
  mutatedThisFields: ctx._mutatedThisFieldsOut || new Set(),
2851
2982
  taintedGlobals: new Set(),
@@ -2922,6 +3053,7 @@ export function runTaintEngine(perFileIR, callGraph, opts = {}) {
2922
3053
  try { analyzeFunction(cbFn, _unionAnnotationTaint(cbFn, cbEntry), inner); } catch {}
2923
3054
  return {
2924
3055
  returnTainted: !!inner._returnTainted,
3056
+ returnSources: inner._returnSources || [],
2925
3057
  mutatedParams: inner._mutatedParamsOut || new Set(),
2926
3058
  mutatedThisFields: inner._mutatedThisFieldsOut || new Set(),
2927
3059
  taintedGlobals: new Set(),
@@ -3064,10 +3196,15 @@ export function runTaintEngine(perFileIR, callGraph, opts = {}) {
3064
3196
  // 'source-from-network' factor permanently dead (it reads
3065
3197
  // t.provenance off chain/trace entries).
3066
3198
  sourceProvenance: f.sourceProvenance || null,
3199
+ // The ORIGINS behind the trace: an `interproc-return` hop is followed back through `from` to the sources that tainted its
3200
+ // arguments. Only a finding whose trace has such a hop gets the field, so every other finding is unchanged.
3201
+ ...(Array.isArray(f.trace) && f.trace.some((t) => t && t.from) ? { sourceRoots: _rootSources(f.trace) } : {}),
3067
3202
  source: f.trace && f.trace.length ? {
3068
3203
  file: fn.file,
3069
3204
  line: f.trace[0].line,
3070
3205
  label: f.trace[0].sourceLabel,
3206
+ // a parameter annotated as a source is not part of a finding's stable identity (posture/stable-id.js): naming it must not rename the finding
3207
+ ...(f.trace[0].annotation ? { kind: 'parameter' } : {}),
3071
3208
  } : null,
3072
3209
  sink: {
3073
3210
  file: fn.file,
@@ -19,7 +19,8 @@ export const HASKELL_PRIVACY_SINKS = Object.freeze([
19
19
  // log: derived from the same table the sensitive-logging rule reads
20
20
  ...HS_LOG_SINKS.map((e) => ({
21
21
  kind: 'sink', id: `privacy-hs-log-${e.module}.${e.name}`.toLowerCase(), language: 'hs', framework: 'haskell', category: 'log',
22
- match: { type: 'call', callee: `${e.module}.${e.name}` }, argIndex: e.argIndex,
22
+ // a variadic log call (printf) carries its data in every argument after the format
23
+ match: { type: 'call', callee: `${e.module}.${e.name}` }, argIndex: e.argFrom !== undefined ? 'all' : e.argIndex,
23
24
  vuln: { name: `Privacy Leak (${e.channel} log)`, severity: 'medium', cwe: 'CWE-359', remediation: 'Do not log regulated data (PII/PHI/PCI). Redact or hash the field before logging.' },
24
25
  })),
25
26
  // response