@clear-capabilities/agentic-security-scanner 0.195.26 → 0.195.28

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 (51) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bin/agentic-security.js +39 -7
  3. package/dist/396.index.js +936 -0
  4. package/dist/{60f58f89664e53fc2f96.json → 3eabd90d6d9c8c8dbbd9.json} +4 -3
  5. package/dist/5856.index.js +634 -0
  6. package/dist/7033.index.js +74 -14
  7. package/dist/7838.index.js +22 -0
  8. package/dist/9220.index.js +24 -1
  9. package/dist/agentic-security.mjs +9 -9
  10. package/dist/agentic-security.mjs.sha256 +1 -1
  11. package/package.json +4 -3
  12. package/src/dataflow/catalog.js +42 -1
  13. package/src/engine.js +90 -10
  14. package/src/language/CLAUDE.md +6 -0
  15. package/src/language/assurance.js +53 -2
  16. package/src/language/haskell-budget-state.js +85 -0
  17. package/src/language/haskell-coverage.js +230 -0
  18. package/src/language/haskell-project-models.js +107 -0
  19. package/src/language/nix-eval-isolation.js +74 -14
  20. package/src/llm-validator/bounded-context.js +87 -0
  21. package/src/llm-validator/claim-groups.js +105 -0
  22. package/src/llm-validator/disposition.js +264 -0
  23. package/src/llm-validator/index.js +52 -26
  24. package/src/pipeline/evidence-record.js +112 -0
  25. package/src/pipeline/scan-health.js +12 -1
  26. package/src/posture/CLAUDE.md +10 -2
  27. package/src/posture/artifact-registry.js +2 -0
  28. package/src/posture/custom-rules.js +7 -2
  29. package/src/posture/drift.js +19 -1
  30. package/src/posture/provenance/coordinator.js +6 -2
  31. package/src/posture/provenance/dag-walk.js +7 -3
  32. package/src/posture/provenance/origin-resolver.js +16 -2
  33. package/src/posture/provenance/providers/github.js +6 -3
  34. package/src/posture/provenance/providers/gitlab.js +5 -3
  35. package/src/posture/reachability-filter.js +3 -3
  36. package/src/posture/rule-compiler/cli.js +76 -21
  37. package/src/posture/rule-compiler/negative-corpus.js +39 -0
  38. package/src/posture/rule-compiler/promotion.js +21 -0
  39. package/src/posture/rule-compiler/regression.js +39 -9
  40. package/src/posture/rule-compiler/store.js +72 -6
  41. package/src/posture/suppressions.js +21 -0
  42. package/src/posture/watch-mode.js +22 -1
  43. package/src/report/disposition-view.js +90 -0
  44. package/src/report/index.js +47 -0
  45. package/src/runScan.js +60 -6
  46. package/src/sast/oidc-trust.js +74 -0
  47. package/src/sast/pipeline.js +14 -1
  48. package/src/sast/workflow-flow.js +272 -0
  49. package/src/sca/basis.js +88 -0
  50. package/src/scope/flags.js +55 -0
  51. package/src/scope/inventory.js +241 -0
@@ -1 +1 @@
1
- ee0c77de1bba97155580cc7647e6723e10bde63e9944dbc5050f1db7b54d46a2 agentic-security.mjs
1
+ d61dba15693817b199249d076741a3dd1e3dff5f70f8c37b6c39fb9b20e2744c agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.195.26",
3
+ "version": "0.195.28",
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",
@@ -82,18 +82,19 @@
82
82
  "test:loop": "node --test --test-concurrency=3 ../scripts/loop-engineering/test/loop-manifest.test.js ../scripts/loop-engineering/test/loop-watchdog.test.js ../scripts/loop-engineering/test/loop-background.test.js ../scripts/loop-engineering/test/loop-dashboard.test.js ../scripts/loop-engineering/test/loop-evidence.test.js ../scripts/loop-engineering/test/loop-recovery.test.js ../scripts/loop-engineering/test/loop-remote.test.js ../scripts/loop-engineering/test/loop-runbook.test.js ../scripts/loop-engineering/test/loop-assurance-profile.test.js ../scripts/loop-engineering/test/loop-assurance-runtime.test.js ../scripts/loop-engineering/test/loop-bounds.test.js ../scripts/loop-engineering/test/loop-spend.test.js ../scripts/loop-engineering/test/loop-ci-attest.test.js",
83
83
  "test:python": "node ../scripts/run-python-tests.mjs",
84
84
  "bench:dag-incremental": "node ../bench/dag-incremental/run.mjs",
85
+ "bench:haskell-core-families": "node ../bench/haskell-core-families/runner.mjs",
85
86
  "test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
86
87
  "test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
87
88
  "test:glob": "node --test test/glob-compat.test.js",
88
89
  "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/todo-marker-words.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/vendor-java-parser-current.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-transport.test.js test/local-preset-callers.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/api-contract-scope.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/accounting.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/gates.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:posture": "node --test test/contracts/finding-provenance.contract.test.js 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-transport.test.js test/local-preset-callers.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/scope/inventory.test.js test/scope/readtree-scope.test.js test/scope/cli-scope.test.js test/scope/watch-scope.test.js test/workflow-flow.test.js test/oidc-trust.test.js test/haskell-coverage/coverage.test.js test/validation-disposition.test.js test/claim-groups.test.js test/validation-bounded-context.test.js test/evidence-record.test.js test/report-disposition-view.test.js test/sca-basis.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/api-contract-scope.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/accounting.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/gates.test.js test/rule-compiler/ci-regression.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
91
  "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
92
  "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
93
  "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
94
  "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/seeding-across-calls.test.js test/lineage/boundary-join.test.js test/contracts/code-boundaries.contract.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
95
  "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
96
  "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-reachability.test.js test/haskell/haskell-real-code-shapes.test.js test/haskell/haskell-unseen-v5-shapes.test.js test/haskell/haskell-unseen-v4-shapes.test.js 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-remediation-0190.test.js test/haskell/haskell-remediation-kdf.test.js test/haskell/haskell-remediation-html-text.test.js test/haskell/haskell-remediation-scotty-body.test.js test/haskell/haskell-remediation-entropy.test.js test/haskell/haskell-remediation-text-command.test.js test/haskell/haskell-remediation-path-contain.test.js test/haskell/haskell-remediation-0195-16.test.js test/haskell/haskell-taint-shapes.test.js test/haskell/haskell-real-advisory-shapes.test.js test/haskell/haskell-discovery-shapes.test.js test/haskell/haskell-quasiquote-interp.test.js test/haskell/haskell-record-dot.test.js test/haskell/haskell-global-cell.test.js test/haskell/haskell-syntax-extensions.test.js test/haskell/haskell-rule-precision.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-trust-fixed-file.test.js test/haskell/haskell-predictable-random-flow.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
+ "test:haskell": "node --test test/haskell/haskell-core-families-runner.test.js test/haskell/haskell-project-models.test.js test/haskell/haskell-reachability.test.js test/haskell/haskell-real-code-shapes.test.js test/haskell/haskell-unseen-v5-shapes.test.js test/haskell/haskell-unseen-v4-shapes.test.js 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-remediation-0190.test.js test/haskell/haskell-remediation-kdf.test.js test/haskell/haskell-remediation-html-text.test.js test/haskell/haskell-remediation-scotty-body.test.js test/haskell/haskell-remediation-entropy.test.js test/haskell/haskell-remediation-text-command.test.js test/haskell/haskell-remediation-path-contain.test.js test/haskell/haskell-remediation-0195-16.test.js test/haskell/haskell-taint-shapes.test.js test/haskell/haskell-real-advisory-shapes.test.js test/haskell/haskell-discovery-shapes.test.js test/haskell/haskell-quasiquote-interp.test.js test/haskell/haskell-record-dot.test.js test/haskell/haskell-global-cell.test.js test/haskell/haskell-syntax-extensions.test.js test/haskell/haskell-rule-precision.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-trust-fixed-file.test.js test/haskell/haskell-predictable-random-flow.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
98
  "test:nix": "node --test test/nix/nix-option-types.test.js test/nix/nix-development-shapes.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
99
  "test:language": "node --test test/bench-haskell-precision.test.js test/bench-independent-haskell-panel.test.js 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/frozen-splits.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
100
  "test:language-stress": "node --test --test-concurrency=1 test/language/language-stress-offline.test.js",
@@ -2194,6 +2194,7 @@ export const CATALOG = [
2194
2194
  // ~450 additional entries across JS / Python / Java / Ruby / PHP / Go.
2195
2195
  // Lives in catalog-expanded.js to keep the diff reviewable. Merged into
2196
2196
  // the main CATALOG below so the indexer treats them identically.
2197
+ import { AsyncLocalStorage } from 'node:async_hooks';
2197
2198
  import { EXPANDED_SANITIZERS } from './catalog-expanded.js';
2198
2199
  import { HASKELL_CATALOG } from './catalog-haskell.js';
2199
2200
  import { cppExtRe } from '../ir/parser-cpp.js';
@@ -2490,7 +2491,12 @@ let _lastMode = null;
2490
2491
  const _filterCache = new WeakMap();
2491
2492
  function filterByProvenance(entries) {
2492
2493
  const mode = isOfficialOnlyMode();
2493
- if (!mode) return entries;
2494
+ if (!mode) {
2495
+ // Project-declared entries belong to the scan that registered them (see scanOwner); another scan in the same process never matches them.
2496
+ if (_PROJECT_ENTRIES.size === 0) return entries;
2497
+ const me = scanOwner.getStore();
2498
+ return entries.some((e) => e.source === 'project' && e.owner !== me) ? entries.filter((e) => e.source !== 'project' || e.owner === me) : entries;
2499
+ }
2494
2500
  if (mode !== _lastMode) {
2495
2501
  _modeGeneration++;
2496
2502
  _lastMode = mode;
@@ -2759,3 +2765,38 @@ export function matchAnnotationParamSources(paramAnnotations, file) {
2759
2765
 
2760
2766
  // For tests and reflection.
2761
2767
  export function _catalogSize() { return CATALOG.length; }
2768
+
2769
+ // Project-declared models (PRD FR 03). Registered for the duration of one scan and removed afterwards, so a project's own wrapper
2770
+ // declarations never become part of the shipped catalog. Entries are tagged `source: 'project'`, which the official-only mode excludes.
2771
+ const _PROJECT_ENTRIES = new Map();
2772
+ export const scanOwner = new AsyncLocalStorage();
2773
+ export function registerProjectCatalogEntries(owner, entries) {
2774
+ unregisterProjectCatalogEntries(owner);
2775
+ const added = [];
2776
+ for (const e of entries || []) {
2777
+ if (!e || !e.match || e.match.type !== 'call' || !e.match.callee) continue;
2778
+ const entry = { ...e, source: 'project', owner };
2779
+ CATALOG.push(entry);
2780
+ const k = entry.match.callee;
2781
+ if (!CALLEE_INDEX.has(k)) CALLEE_INDEX.set(k, []);
2782
+ CALLEE_INDEX.get(k).push(entry);
2783
+ added.push(entry);
2784
+ }
2785
+ _PROJECT_ENTRIES.set(owner, added);
2786
+ return added.length;
2787
+ }
2788
+ export function unregisterProjectCatalogEntries(owner) {
2789
+ const added = _PROJECT_ENTRIES.get(owner);
2790
+ if (!added) return;
2791
+ for (const e of added) {
2792
+ const i = CATALOG.indexOf(e);
2793
+ if (i >= 0) CATALOG.splice(i, 1);
2794
+ const list = CALLEE_INDEX.get(e.match.callee);
2795
+ if (list) {
2796
+ const j = list.indexOf(e);
2797
+ if (j >= 0) list.splice(j, 1);
2798
+ if (!list.length) CALLEE_INDEX.delete(e.match.callee);
2799
+ }
2800
+ }
2801
+ _PROJECT_ENTRIES.delete(owner);
2802
+ }
package/src/engine.js CHANGED
@@ -199,6 +199,13 @@ import { demoteUnreachable } from './posture/reachability-filter.js';
199
199
  import { annotateExploitability, detectProjectContext } from './posture/exploitability.js';
200
200
  import { applyFeedback as applyLearnedFeedback } from './posture/learning.js';
201
201
  import { validateMany as llmValidateMany, applyValidatorVerdicts } from './llm-validator/index.js';
202
+ import { applyDispositions, assignDisposition } from './llm-validator/disposition.js';
203
+ import { groupEquivalentClaims, reconcileCounts } from './llm-validator/claim-groups.js';
204
+ import { annotateScaBasis, dedupeAdvisoryAliases } from './sca/basis.js';
205
+ import { applyOidcTrust, resolveOwnRepository } from './sast/oidc-trust.js';
206
+ import { readProjectModelFile, loadProjectModels, projectFileExists } from './language/haskell-project-models.js';
207
+ import { parseBudgetOverrides as parseHaskellBudgetOverrides } from './language/haskell-budget-state.js';
208
+ import { registerProjectCatalogEntries, unregisterProjectCatalogEntries, scanOwner } from './dataflow/catalog.js';
202
209
  import { MODEL_STATUS, stageSummaryFromModelStatus } from './llm-validator/model-status.js';
203
210
  import { resolveProvider as resolveLlmProvider, otherRemoteRoles as llmOtherRemoteRoles, NO_CLOUD_FALLBACK } from './llm-validator/providers.js';
204
211
  import { scanCrossLangOpenAPI } from './posture/cross-lang-openapi.js';
@@ -784,6 +791,7 @@ function _isIaCFile(p){
784
791
  // `isKubernetesManifest` below, because a path predicate cannot see content.
785
792
  if (/(?:^|\/)k8s(?:\/|$)/.test(p) && /\.ya?ml$/i.test(base)) return true;
786
793
  if (/(?:^|\/)\.github\/workflows\/.*\.ya?ml$/.test(p)) return true;
794
+ if (/(?:^|\/)\.github\/actions\/.*\.ya?ml$/.test(p)) return true; // composite actions kept in the repository
787
795
  if (/(?:^|\/)\.gitlab-ci\.ya?ml$/.test(p)) return true;
788
796
  if (/(?:^|\/)values\.ya?ml$/.test(p)) return true;
789
797
  // MCP server config files (Claude Code agent host configuration)
@@ -846,7 +854,9 @@ export function isInstructionFile(relPath) {
846
854
  return typeof relPath === 'string' && _INSTRUCTION_FILE_ADMIT_RE.test(relPath);
847
855
  }
848
856
 
849
- function shouldScan(p){if(/\.(test|spec|mock)\./i.test(p))return false;if(/_test\.go$/i.test(p))return false;if(/_spec\.rb$/i.test(p))return false;if(/Test\.(?:java|cs|kt|scala)$/i.test(p))return false;if(/\.min\.[mc]?js$/i.test(p))return false;for(const x of p.split("/"))if(IGNORE_DIRS.has(x))return false;
857
+ // `scoped` (PRD FR 01): the Git-aware inventory already decided eligibility, so a tracked file is not dropped merely because its
858
+ // folder is named test, bin or vendor, or because its name looks like a test. Minified bundles are still not source.
859
+ function shouldScan(p,scoped=false){if(/\.min\.[mc]?js$/i.test(p))return false;if(!scoped){if(/\.(test|spec|mock)\./i.test(p))return false;if(/_test\.go$/i.test(p))return false;if(/_spec\.rb$/i.test(p))return false;if(/Test\.(?:java|cs|kt|scala)$/i.test(p))return false;for(const x of p.split("/"))if(IGNORE_DIRS.has(x))return false;}else{for(const x of p.split("/"))if(x==="node_modules"||x===".git"||x===".agentic-security")return false;}
850
860
  // Mobile + framework manifest files needed by the v4 detectors.
851
861
  const base=p.split('/').pop();
852
862
  if (/(?:^|[\\/])AndroidManifest\.xml$/i.test(p)) return true;
@@ -6308,17 +6318,27 @@ const _SECURITY_WORD_RE=/\b(?:auth|authn|authz|authenticat(?:e|ed|es|ion|or)|aut
6308
6318
  function _splitIdentifierWords(text){
6309
6319
  return text.replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/[_\-.]+/g," ");
6310
6320
  }
6321
+ // PRD FR 07: a marker is a developer note only when it sits in comment text, either as the first word of the comment or written as a
6322
+ // label (`XXX:`, `FIXME(name)`). A Roman numeral or prose word that happens to read XXX, or a marker inside a string, is not a note.
6323
+ function _isMarkerComment(ln){
6324
+ const m=/(?:\/\/+|#+|--+|\/\*+|^\s*\*+|;+|%+|<!--)\s*(.*)$/.exec(ln);
6325
+ if(!m)return false;
6326
+ const text=m[1];
6327
+ return /^(?:TODO|FIXME|HACK|XXX|BUG|TEMP)\b/.test(text)||/\b(?:TODO|FIXME|HACK|XXX|BUG|TEMP)(?::|\()/.test(text);
6328
+ }
6311
6329
  function scanTodosNearSecurity(fp,raw){
6312
6330
  const lines=raw.split("\n");
6313
6331
  const out=[];
6314
6332
  for(let i=0;i<lines.length;i++){
6315
6333
  const ln=lines[i];
6316
- if(!/\b(?:TODO|FIXME|HACK|XXX|BUG|TEMP)\b/.test(ln))continue;
6334
+ if(!_isMarkerComment(ln))continue;
6317
6335
  const window=lines.slice(Math.max(0,i-3),Math.min(lines.length,i+4)).join(" ");
6318
6336
  if(!_SECURITY_WORD_RE.test(_splitIdentifierWords(window)))continue;
6319
6337
  out.push({
6320
6338
  vuln:"Known-Broken Code Marker Near Security-Sensitive Logic",
6321
- severity:"medium",cwe:"CWE-1077",stride:"Tampering",
6339
+ // A developer note is a code-quality note, not a vulnerability, unless it is tied to a present weakness (PRD FR 07).
6340
+ kind:"hardening",hardeningAdvice:true,
6341
+ severity:"low",cwe:"CWE-1077",stride:"Tampering",
6322
6342
  fix:"Review the TODO/FIXME. Known-broken comments next to auth/crypto code often indicate unfinished hardening.",
6323
6343
  code:`// File: ${fp}:${i+1}\n// ${ln.trim()}`,
6324
6344
  file:fp,line:i+1,snippet:ln.trim()
@@ -8562,6 +8582,9 @@ async function queryOSV(components,allFileContents){
8562
8582
  fixedVersions: [...fixedVersions].sort(),
8563
8583
  aliases: (d.aliases || []).filter(a => a.startsWith('CVE-')),
8564
8584
  osvVulnFunctions: [...new Set(osvVulnFunctions)],
8585
+ // PRD FR 08: the advisory's own timestamps and affected ranges, so a finding can say what it matched and when that was published.
8586
+ advisoryModified: d.modified || null, advisoryPublished: d.published || null,
8587
+ affectedRanges: (d.affected || []).flatMap((aff) => (aff.ranges || []).map((r) => ({ type: r.type, events: (r.events || []).map((ev) => ({ ...ev })) }))),
8565
8588
  severity, cvssVector, hasKnownAttackRef };
8566
8589
  _osvCacheSet('vuln:' + vid, vuln);
8567
8590
  return [vid, vuln];
@@ -8582,6 +8605,7 @@ async function queryOSV(components,allFileContents){
8582
8605
  results.push({ type: 'vulnerable_dep', name: comp.name, version: comp.version, ecosystem: comp.ecosystem,
8583
8606
  purl: comp.purl, osvId: vid, cveAliases: vuln.aliases, description: vuln.description,
8584
8607
  fixedVersions: vuln.fixedVersions, severity: vuln.severity, cvssVector: vuln.cvssVector,
8608
+ advisoryModified: vuln.advisoryModified || null, advisoryPublished: vuln.advisoryPublished || null, affectedRanges: vuln.affectedRanges || null,
8585
8609
  hasKnownAttackRef: vuln.hasKnownAttackRef, osvVulnFunctions: vuln.osvVulnFunctions || [], reachable: comp.reachable, scope: comp.scope,
8586
8610
  file: comp.filePath,
8587
8611
  // `isDirect` is backfilled onto every component just above the queryOSV
@@ -8971,7 +8995,7 @@ function _gcHint(minHeapMiB) {
8971
8995
  _gcFn();
8972
8996
  }
8973
8997
 
8974
- async function runFullScan({fileContents={}, depFileContents={}, scanRoot=null, resume=undefined, deep=undefined, deepInCi=undefined, provenance=true, completeScan=true, skipAnnotators=false, policyExcluded=[], sizeSkipped=[]}, setProgress=()=>{}){
8998
+ async function _runFullScanBody({fileContents={}, depFileContents={}, scanRoot=null, resume=undefined, deep=undefined, deepInCi=undefined, provenance=true, completeScan=true, skipAnnotators=false, policyExcluded=[], sizeSkipped=[], haskellSourceMap=null, gitScope=false}, setProgress=()=>{}){
8975
8999
  // E6-01: local, aggregate telemetry for this scan (see pipeline/telemetry.js). Nothing is sent anywhere; the snapshot is a field of the result.
8976
9000
  const _tel = createTelemetry({ deterministic: isDeterministic() });
8977
9001
  setDetectorTimingSink((name, ms) => _tel.detector(name, ms));
@@ -8979,6 +9003,14 @@ async function runFullScan({fileContents={}, depFileContents={}, scanRoot=null,
8979
9003
  // The feed metadata is module state: reset it so one scan cannot inherit another's catalog freshness in the same process (0.195.22).
8980
9004
  _kevCatalogMeta = { source: 'not-loaded', fetchedAt: null, ageDays: null, stale: null, entries: 0 };
8981
9005
  _epssLiveMeta = { source: 'not-loaded', ageDays: null, stale: null, cvesChecked: 0 };
9006
+ // PRD FR 03: reviewed project models for Haskell wrappers. Registered for this scan only; a model without provenance or paired tests is rejected and reported.
9007
+ let _projectModels = { present: false, accepted: [], rejected: [], catalog: [] };
9008
+ try {
9009
+ if (scanRoot) {
9010
+ _projectModels = loadProjectModels(readProjectModelFile(scanRoot), projectFileExists(scanRoot));
9011
+ if (_projectModels.catalog.length) registerProjectCatalogEntries(scanRoot, _projectModels.catalog);
9012
+ }
9013
+ } catch (e) { _projectModels = { present: true, accepted: [], rejected: [{ id: '(file)', reason: String((e && e.message) || e) }], catalog: [] }; }
8982
9014
  _resetSuppressions();_tel.time('index', () => _buildProjectIndex(fileContents));await _tel.timeAsync('custom-rules', () => _loadCustomRules(scanRoot));
8983
9015
  // Pre-pass: build cross-file Java tainted-method index so per-file taint
8984
9016
  // analysis can recognize calls to user-input-returning helper methods
@@ -9006,7 +9038,7 @@ function _deterministicFileTimings(timings) {
9006
9038
 
9007
9039
  const _fileTimings = [];
9008
9040
  let _filesSkipped = 0, _filesTimedOut = 0, _filesDenseSkipped = 0, _filesOversizeSkipped = 0;
9009
- const files=Object.keys(fileContents).filter(f=>(shouldScan(f) || isKubernetesManifest(f, fileContents[f]) || isCloudFormationTemplate(f, fileContents[f]) || isInstructionFile(f)) && !_isPathIgnored(f));const fc={},pfr={};const aR=[],aF=[],aSrc=[],aSink=[],aSan=[],aLogic=[],aSupply=[],aSecrets=[],aCiphersRest=[],aCiphersTransit=[];const _detectorErrors=[];
9041
+ const files=Object.keys(fileContents).filter(f=>(shouldScan(f,gitScope) || isKubernetesManifest(f, fileContents[f]) || isCloudFormationTemplate(f, fileContents[f]) || isInstructionFile(f)) && !_isPathIgnored(f));const fc={},pfr={};const aR=[],aF=[],aSrc=[],aSink=[],aSan=[],aLogic=[],aSupply=[],aSecrets=[],aCiphersRest=[],aCiphersTransit=[];const _detectorErrors=[];
9010
9042
  // ---- R8: opt-in per-file checkpointing (AGENTIC_SECURITY_RESUME=1, or
9011
9043
  // runScan({resume:true})). Default OFF, so existing behaviour is untouched.
9012
9044
  // Only this loop is checkpointed; every cross-file pass below re-runs, so
@@ -9405,7 +9437,6 @@ function _deterministicFileTimings(timings) {
9405
9437
  } else {
9406
9438
  sc.reachabilityTier = 'transitive-only';
9407
9439
  }
9408
- if (sc.reachabilityTier === 'transitive-only') { sc.unreachable = true; sc._reachabilityDemoted = true; }
9409
9440
  // Mark as the strongest "vulnerable function actually invoked" signal so
9410
9441
  // posture/reachability-filter can preserve full severity on this tier
9411
9442
  // while demoting lower tiers.
@@ -9413,6 +9444,8 @@ function _deterministicFileTimings(timings) {
9413
9444
  sc.routeReachableViaFunction = true;
9414
9445
  }
9415
9446
  }
9447
+ // PRD FR 08: record the basis of every dependency finding, kept apart from reachability.
9448
+ try { const _merged = dedupeAdvisoryAliases(supplyChain); supplyChain.length = 0; supplyChain.push(..._merged); annotateScaBasis(supplyChain); } catch (_) {}
9416
9449
  // Early dedup: collapse duplicates BEFORE the annotation pipeline to reduce work.
9417
9450
  try{const _earlyMap=new Map();for(const f of aF){const k=`${f.file||''}:${f.line||0}:${f.vuln||''}`;if(!_earlyMap.has(k))_earlyMap.set(k,f);}aF.length=0;aF.push(..._earlyMap.values());}catch(_){}
9418
9451
  // Sort findings: critical first, then structural patterns last within same severity
@@ -9881,12 +9914,14 @@ function _deterministicFileTimings(timings) {
9881
9914
  // An `unsafePerformIO` / `unsafeCoerce` / `unsafeInterleaveIO` boundary note says (in its own text) that it claims no vulnerability class: it marks where the
9882
9915
  // type system is stepped around. In a scan it is information, not a finding that gates a release; the rules module keeps emitting it at `low` (its contract),
9883
9916
  // and the severity is lowered here with the original kept.
9884
- try{aF.push(...analyzeHaskellRules(fc,{reachability:_hsReachCtx()}).findings.map((f)=>f&&f.rule==='hs-unsafe-io-boundary'?{...f,originalSeverity:f.severity,severity:'info'}:f));}catch(_){}
9885
- try{aF.push(...analyzeHaskellLlmRules(fc));}catch(_){}
9917
+ // FR 02: which Haskell rule families really executed (a thrown family is recorded as failed, never as clean).
9918
+ const _hsRuleRuns={};
9919
+ try{const _r=analyzeHaskellRules(fc,{reachability:_hsReachCtx()}).findings.map((f)=>f&&f.rule==='hs-unsafe-io-boundary'?{...f,originalSeverity:f.severity,severity:'info'}:f);aF.push(..._r);_hsRuleRuns['hs-security-rules']={ran:true,findings:_r.length};}catch(e){_hsRuleRuns['hs-security-rules']={ran:false,failed:String((e&&e.message)||e).slice(0,120)};}
9920
+ try{const _r=analyzeHaskellLlmRules(fc);aF.push(..._r);_hsRuleRuns['hs-llm']={ran:true,findings:_r.length};}catch(e){_hsRuleRuns['hs-llm']={ran:false,failed:String((e&&e.message)||e).slice(0,120)};}
9886
9921
  try{const{analyzeLanguageFindings}=await import('./language/engine-pass.js');aF.push(...analyzeLanguageFindings(allFileContents).findings);}catch(_){}
9887
9922
  let _languageBridges=null;try{if(Object.keys(allFileContents).some(f=>/\.(?:l?hs|nix)$/i.test(f))){const{analyzePolyglotBridges}=await import('./language/bridges.js');_languageBridges=analyzePolyglotBridges(allFileContents);}}catch(_){}
9888
9923
  try{for(const[_p,_t]of Object.entries(allFileContents))if(typeof _t==='string')aF.push(...scanDependencyUrlCredentials(_p,_t));}catch(_){}
9889
- try{const _hw=analyzeHaskellWeb(fc,{reachability:_hsReachCtx()});aF.push(..._hw.findings);aR.push(...toEngineRoutes(_hw));}catch(_){}
9924
+ try{const _hw=analyzeHaskellWeb(fc,{reachability:_hsReachCtx()});aF.push(..._hw.findings);aR.push(...toEngineRoutes(_hw));_hsRuleRuns['hs-web']={ran:true,findings:_hw.findings.length};}catch(e){_hsRuleRuns['hs-web']={ran:false,failed:String((e&&e.message)||e).slice(0,120)};}
9890
9925
  let finalFindings;try{finalFindings=dedupeFindingsWithEvidence(aF);}catch(_){finalFindings=dd(aF,f=>f.id);}
9891
9926
  // Inline `agentic-security-ignore` pragmas, pass 1 of 2. This covers every
9892
9927
  // finding that exists BY THIS POINT — the pattern detectors, the cross-file
@@ -10037,6 +10072,9 @@ function _deterministicFileTimings(timings) {
10037
10072
  _licenseGraph = null, _attributions = null, _taxonomySummary = null;
10038
10073
  let _scanMeta = null;
10039
10074
  let _aiAssistance = null;
10075
+ let _dispositionCounts = null;
10076
+ let _groupedClaims = [];
10077
+ let _candidateReconciliation = null;
10040
10078
  let _entrypointInventory = {};
10041
10079
  let _rootCauseSweep = null;
10042
10080
  let _proofCoverage = null;
@@ -10520,6 +10558,31 @@ function _deterministicFileTimings(timings) {
10520
10558
  // upstream of it still need to run before validation, same as before).
10521
10559
  try { annotateVerifierVerdicts(finalFindings, { fileContents: fc }); } catch (_) {}
10522
10560
  } catch(_) {}
10561
+ // PRD FR 06: trust-policy evidence kept in the repository adjusts OIDC findings; absence there never closes one.
10562
+ try { const _own = finalFindings.some((x) => x && /^Pipeline: OIDC id-token: write/.test(String(x.vuln || ''))) ? resolveOwnRepository(scanRoot) : null; applyOidcTrust(finalFindings, depFileContents && Object.keys(depFileContents).length ? { ...allFileContents, ...depFileContents } : allFileContents, _own ? { ownRepository: _own.name, ownRepositoryBasis: _own.basis } : {}); } catch (e) { _annotatorErrors.push({ phase: 'applyOidcTrust', err: String((e && e.message) || e) }); }
10563
+ // PRD FR 05: final disposition. Runs whether or not a model was configured,
10564
+ // so every actionable finding says why it is confirmed, needs review,
10565
+ // hardening, refuted or unvalidated. Only an evidence-backed refutation or a
10566
+ // reviewed suppression leaves the list, and it is retained in the ledger.
10567
+ try {
10568
+ // Equivalent detector claims are grouped first (one weakness, one actionable result naming every detector); the grouped
10569
+ // claims are kept for the raw export, never deleted.
10570
+ const _suppressedBefore = _suppressionLog.length;
10571
+ const _raw = finalFindings.filter((x) => x && x.kind !== 'sca').length;
10572
+ const cg = groupEquivalentClaims(finalFindings);
10573
+ finalFindings = cg.kept;
10574
+ for (const g of cg.grouped) { try { assignDisposition(g, { fileContents: fc }); } catch (_) {} }
10575
+ _groupedClaims = cg.grouped;
10576
+ const dr = applyDispositions(finalFindings, { fileContents: fc });
10577
+ finalFindings = dr.kept;
10578
+ _dispositionCounts = dr.counts;
10579
+ _candidateReconciliation = reconcileCounts({ raw: _raw, grouped: cg.grouped.length, refuted: dr.refuted.length, dispositionCounts: dr.counts, suppressed: _suppressedBefore });
10580
+ for (const d of dr.refuted) _suppressionLog.push({
10581
+ vuln: d.vuln, file: d.file, line: d.line, snippet: d.snippet,
10582
+ reason: 'refuted:' + (d.dispositionDetail?.reason || '').slice(0, 80),
10583
+ disposition: d.disposition, dispositionDetail: d.dispositionDetail,
10584
+ });
10585
+ } catch (e) { _annotatorErrors.push({ phase: 'applyDispositions', err: String((e && e.message) || e) }); }
10523
10586
  // Same ordering fix as annotateVerifierVerdicts above: annotateConfidence
10524
10587
  // (~8111) computes f.confidence before f.unvalidated exists on any
10525
10588
  // finding, so its 0.85x "LLM validator unavailable" penalty could never
@@ -11376,6 +11439,12 @@ function _deterministicFileTimings(timings) {
11376
11439
  ...(() => { const a = configuredAdvisoryDb(scanRoot); return { advisoryDb: a.db, advisoryReason: a.db ? null : a.reason }; })(),
11377
11440
  policyExcluded,
11378
11441
  sizeSkipped,
11442
+ deepEnabled: _deepEnabled,
11443
+ scanRoot,
11444
+ ...(() => { const o = parseHaskellBudgetOverrides(env.AGENTIC_SECURITY_HASKELL_PARSE_BUDGETS); return { parseBudgets: o.budgets, parseBudgetRejected: o.rejected }; })(),
11445
+ projectModels: _projectModels,
11446
+ sourceMap: haskellSourceMap,
11447
+ ruleRuns: { ..._hsRuleRuns, 'ir-taint': { ran: _deepEnabled, findings: finalFindings.filter((f) => f && f.parser === 'IR-TAINT' && /\.l?hs$/i.test(f.file || '')).length }, 'hs-sca': { ran: true } },
11379
11448
  supplyGaps: _languageSupplyGaps,
11380
11449
  licenseUnavailable: ((_languageBom && _languageBom.components) || []).filter((c) => c && !c.license && !(Array.isArray(c.licenses) && c.licenses.length)).length,
11381
11450
  optional: { 'nix-eval': { selected: env.AGENTIC_SECURITY_NIX_EVAL === '1', result: _nixEvalResult }, 'cabal-plan': { selected: false }, 'hackage-live': liveFeedOptionalState(env)},
@@ -11410,7 +11479,18 @@ function _deterministicFileTimings(timings) {
11410
11479
  setDetectorTimingSink(null);
11411
11480
  attributeErrors(_tel, { annotatorErrors: _annotatorErrors, detectorErrors: (typeof _detectorErrors !== 'undefined' ? _detectorErrors : []), deepFailure: _deepFailure, filesTimedOut: (_scanMeta && _scanMeta.filesTimedOut) || 0 });
11412
11481
  _tel.set('files.scanned', (_scanMeta && _scanMeta.filesScanned) || 0); _tel.set('files.skipped', (_scanMeta && _scanMeta.filesSkipped) || 0);
11413
- return{telemetry:_tel.snapshot(),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,linesScanned:Object.values(fc).reduce((_n,_c)=>_n+(typeof _c==='string'?_c.split("\n").length:0),0),crossFileCount:cf.length,logicVulns:aLogic,supplyChain,components:annotatedComponents,secrets:aSecrets,ciphers:{atRest:aCiphersRest,inTransit:aCiphersTransit},pfr,fc,depFileContents,suppressions:_getSuppressions(),...(_stageEvidenceOn()?{stageEvidence:{dedupe:[..._stageEvidence.dedupe],guard:[..._stageEvidence.guard]}}:{}),_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,languageBridges:_languageBridges,languageBom:_languageBom,aiAssistance:_aiAssistance};}
11482
+ try { unregisterProjectCatalogEntries(scanRoot); } catch (_) {}
11483
+ return{telemetry:_tel.snapshot(),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,linesScanned:Object.values(fc).reduce((_n,_c)=>_n+(typeof _c==='string'?_c.split("\n").length:0),0),crossFileCount:cf.length,logicVulns:aLogic,supplyChain,components:annotatedComponents,secrets:aSecrets,ciphers:{atRest:aCiphersRest,inTransit:aCiphersTransit},pfr,fc,depFileContents,suppressions:_getSuppressions(),...(_stageEvidenceOn()?{stageEvidence:{dedupe:[..._stageEvidence.dedupe],guard:[..._stageEvidence.guard]}}:{}),_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,languageBridges:_languageBridges,languageBom:_languageBom,aiAssistance:_aiAssistance,dispositionCounts:_dispositionCounts,groupedClaims:_groupedClaims,candidateReconciliation:_candidateReconciliation};}
11484
+
11485
+ // A scan runs inside its own owner scope, so project-declared catalog entries registered for one root are matched only by the scan of that
11486
+ // root, even when scans overlap in one process, and are removed on every exit, including a thrown error.
11487
+ async function runFullScan(args = {}, setProgress) {
11488
+ const owner = args.scanRoot ?? null;
11489
+ return scanOwner.run(owner, async () => {
11490
+ try { return await _runFullScanBody(args, setProgress); }
11491
+ finally { try { unregisterProjectCatalogEntries(owner); } catch (_) {} }
11492
+ });
11493
+ }
11414
11494
 
11415
11495
  // Post-aggregation classification: every source becomes "unsafe"|"safe"; every sink becomes "confirmed"|"safe".
11416
11496
  // Orphans (no finding linkage) are bucketed by file-local heuristic so the UI shows binary states only.
@@ -203,3 +203,9 @@ at the use site that shares one of the names disables the expansion (it stays th
203
203
  the alias's call-graph edge is kept via `inlinedCalls`; arguments are lowered in the CALLER's module. Pinned both ways in
204
204
  `test/haskell/haskell-alias-taint.test.js`. Haskell verdict-flip cases live in `bench/mutation/cases-haskell.mjs`; the 8
205
205
  `bench/cve-replay/deep/hs-*-shape` entries are synthetic (self-authored, not from advisories).
206
+
207
+ ## Project models for Haskell wrappers (PRD FR 03)
208
+
209
+ `haskell-project-models.js` loads `.agentic-security/haskell-models.json` (schema `haskell-project-models/1`). A model declares a source, sink or sanitizer by module and function name and is accepted only with provenance (reviewer, date, reason), an argument position for a sink, an explicit family list for a sanitizer (`*` is refused), and a paired positive and negative test file that exists inside the project. A rejected model is a scan-health condition and has no effect. Accepted models are registered into the taint catalog for one scan (`registerProjectCatalogEntries`, tagged `source: 'project'`) and removed when `runFullScan` returns or throws. Each scan runs in its own owner scope (`scanOwner`, an AsyncLocalStorage in `dataflow/catalog.js`), so overlapping scans of different roots in one process never match each other's project entries.
210
+
211
+ `haskell-budget-state.js` reads operator parse budgets from `AGENTIC_SECURITY_HASKELL_PARSE_BUDGETS` (`name=value,...`, parser budget names only; anything else is refused as a condition), and writes `.agentic-security/haskell-budget.json` with the effective budgets and every remaining file plus a content digest. The next scan reports `budget.sinceLast` (completed, still remaining on the same inputs, still remaining after a change, gone, newly remaining). It records work left; it does not resume, because the assurance pass parses every eligible file on every scan.
@@ -23,6 +23,10 @@ import { deriveCppContext } from './haskell-cpp.js';
23
23
  import { createNixAdapter } from './nix-adapter.js';
24
24
  import { analyzeHaskellManifests } from './haskell-manifests.js';
25
25
  import { packageOfModule } from './haskell-models.js';
26
+ import { buildHaskellCoverage } from './haskell-coverage.js';
27
+ import { budgetRecord, loadBudgetState, saveBudgetState } from './haskell-budget-state.js';
28
+ import { DEFAULT_PARSE_BUDGETS } from './haskell-parser.js';
29
+ import { buildHaskellIR } from './haskell-ir.js';
26
30
 
27
31
  export const ASSURANCE_VERSION = 'language-assurance/1';
28
32
 
@@ -75,7 +79,9 @@ export async function assessLanguageAssurance(input = {}) {
75
79
  const DISCLOSED_KINDS = new Set(['cpp', 'ffi', 'th-safe-splice', 'quasiquote-inert']);
76
80
  const disclosed = new Map(); // boundary kind -> {count, files:Set}
77
81
  const projectModules = new Set(); const importedModules = new Map(); // module -> files
82
+ const parsesByFile = new Map();
78
83
  const onParse = (file, parse) => {
84
+ parsesByFile.set(file, parse);
79
85
  if (parse && parse.module && parse.module.name) projectModules.add(parse.module.name);
80
86
  for (const i of (parse && parse.imports) || []) if (i && i.module) { const e = importedModules.get(i.module) || new Set(); e.add(file); importedModules.set(i.module, e); }
81
87
  for (const b of (parse && parse.boundaries) || []) {
@@ -86,7 +92,7 @@ export async function assessLanguageAssurance(input = {}) {
86
92
  disclosed.set(b.kind, e);
87
93
  }
88
94
  };
89
- const adapters = input.adapters || [createHaskellAdapter({ onParse, cpp: deriveCppContext(files) }), createNixAdapter()];
95
+ const adapters = input.adapters || [createHaskellAdapter({ onParse, cpp: deriveCppContext(files), budgets: input.parseBudgets || undefined }), createNixAdapter()];
90
96
  const run = await runLanguageAnalysis({ files, adapters, timeoutMs: input.timeoutMs || 0 });
91
97
  const reconcile = reconcileLanguageLedger(run.ledger, files);
92
98
  const base = languageHealth({ ledger: run.ledger, outcomes: run.outcomes });
@@ -136,6 +142,18 @@ export async function assessLanguageAssurance(input = {}) {
136
142
  if (ociFiles.length) limitations.push({ kind: 'container-image-scan', files: ociFiles.sort(), note: 'Nix-built images and declared OCI services are analysed as configuration only: image layers and base-image vulnerabilities are NOT scanned' });
137
143
  if (!reconcile.ok) conditions.push(`language ledger does not reconcile with the inputs: ${reconcile.errors[0]}`);
138
144
 
145
+ // Buck-built Haskell: its dependency graph is not resolved by this scanner, so version-based advisory matching has nothing to match.
146
+ // Stated as a coverage gap (PRD FR 08) rather than left looking like "no vulnerable dependencies".
147
+ const buckHaskell = Object.entries(files).filter(([f, t]) => /(?:^|\/)(?:BUCK|TARGETS)(?:\.v2)?$/.test(f) && typeof t === 'string' && /\bhaskell_(?:library|binary|test|ghci)\b|\bprebuilt_haskell_library\b/.test(t)).map(([f]) => f).sort();
148
+ if (present.has('haskell') && buckHaskell.length) conditions.push(`${buckHaskell.length} Buck target file(s) declare Haskell dependencies this scan does not resolve (${buckHaskell.slice(0, 3).join(', ')}): dependency advisories were not matched for them`);
149
+
150
+ // Declared project models (PRD FR 03): a rejected declaration means a wrapper the project meant to model is still opaque.
151
+ const pm = input.projectModels;
152
+ if (pm && pm.present) {
153
+ if (pm.rejected.length) conditions.push(`${pm.rejected.length} Haskell project model(s) were rejected and have no effect: ${pm.rejected.slice(0, 3).map((r) => `${r.id} (${r.reason})`).join('; ')}`);
154
+ if (pm.accepted.length) limitations.push({ kind: 'project-models', count: pm.accepted.length, ids: pm.accepted.map((m) => m.id).sort(), note: 'wrappers modelled by reviewed project declarations (.agentic-security/haskell-models.json); each carries provenance and a paired positive and negative test' });
155
+ }
156
+
139
157
  // 2. manifests
140
158
  if (manifests.length) {
141
159
  let m = null;
@@ -188,6 +206,39 @@ export async function assessLanguageAssurance(input = {}) {
188
206
  }
189
207
  }
190
208
 
209
+ // 5b. Haskell coverage accounting (FR 02): per-file stages, rates against the eligible denominator, rule families executed.
210
+ let haskellCoverage = null;
211
+ if (present.has('haskell')) {
212
+ try {
213
+ let ir = null;
214
+ if (input.deepEnabled !== false) {
215
+ try { const hs = Object.fromEntries(Object.entries(files).filter(([f]) => /\.hs$/i.test(f))); if (Object.keys(hs).length) ir = buildHaskellIR(hs); } catch { ir = null; }
216
+ }
217
+ haskellCoverage = buildHaskellCoverage({
218
+ files, parses: parsesByFile, ir, deepEnabled: input.deepEnabled !== false && !disabled.has('haskell:taint'),
219
+ ruleRuns: input.ruleRuns || null, sizeSkipped: input.sizeSkipped, policyExcluded: input.policyExcluded,
220
+ excludedPaths: run.ledger.excludedFiles, sourceMap: input.sourceMap || null,
221
+ });
222
+ // Effective parse budgets, operator overrides that were refused, and the persisted record of work left (haskell-budget-state.js).
223
+ const b = haskellCoverage.budget;
224
+ b.budgets = { ...DEFAULT_PARSE_BUDGETS, ...(input.parseBudgets || {}) };
225
+ if (Array.isArray(input.parseBudgetRejected) && input.parseBudgetRejected.length) {
226
+ b.rejectedOverrides = input.parseBudgetRejected;
227
+ conditions.push(`Haskell parse budget override refused: ${input.parseBudgetRejected.map((r) => `${r.entry} (${r.reason})`).join('; ')}`);
228
+ }
229
+ const budgetOf = (file) => { const p = parsesByFile.get(file); return p && p.budget ? { budgetName: p.budget.name } : {}; };
230
+ const remainingAll = (b.remainingAll || []).map((r) => ({ ...r, ...budgetOf(r.file) }));
231
+ const previous = loadBudgetState(input.scanRoot);
232
+ const { record, sinceLast } = budgetRecord({ remaining: remainingAll, completedFiles: b.completedFiles, budgets: b.budgets, files, previous });
233
+ b.remaining = b.remaining.map((r) => ({ ...r, ...budgetOf(r.file) }));
234
+ b.sinceLast = sinceLast;
235
+ if (record.remainingFiles || previous) b.recorded = saveBudgetState(input.scanRoot, record);
236
+ // Only a measured shortfall is a condition. A parse-only form is a stated limitation, not a gap in what was asked.
237
+ for (const r of haskellCoverage.capability.reasons) if (!/deep analysis is disabled/.test(r)) conditions.push(`Haskell coverage: ${r}`);
238
+ for (const l of haskellCoverage.capability.limitations) limitations.push({ kind: 'haskell-parse-only', note: l });
239
+ } catch (e) { conditions.push(`Haskell coverage accounting failed: ${String((e && e.message) || e).slice(0, 160)}`); }
240
+ }
241
+
191
242
  // 6. optional modes
192
243
  const optionalModes = {};
193
244
  for (const name of OPTIONAL_MODES) {
@@ -198,7 +249,7 @@ export async function assessLanguageAssurance(input = {}) {
198
249
 
199
250
  return {
200
251
  version: ASSURANCE_VERSION,
201
- languageCoverage: { totals: base.totals, byKind, conditions, limitations, capabilities, optionalModes },
252
+ languageCoverage: { totals: base.totals, byKind, conditions, limitations, capabilities, optionalModes, ...(haskellCoverage ? { haskell: haskellCoverage } : {}) },
202
253
  ledger: { reconciled: reconcile.ok, errors: reconcile.errors, byAnalyzer: run.ledger.byAnalyzer },
203
254
  outcomes: run.outcomes,
204
255
  retained: { findings: findings.length, findingsRef: findings },