@clear-capabilities/agentic-security-scanner 0.160.0 → 0.161.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.
- package/CHANGELOG.md +18 -0
- package/dist/1919.index.js +1 -1
- package/dist/2432.index.js +10 -1
- package/dist/4113.index.js +1 -1
- package/dist/7033.index.js +4 -1
- package/dist/7838.index.js +1 -1
- package/dist/8714.index.js +1 -1
- package/dist/8798.index.js +1 -1
- package/dist/9560.index.js +170 -41
- package/dist/agentic-security.mjs +12 -12
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +8 -8
- package/src/dataflow/engine.js +20 -4
- package/src/engine.js +29 -8
- package/src/language/CLAUDE.md +6 -1
- package/src/language/aibom.js +4 -1
- package/src/language/fix-lifecycle.js +9 -0
- package/src/language/haskell-ir.js +47 -7
- package/src/language/haskell-sca.js +87 -7
- package/src/language/haskell-supply.js +175 -6
- package/src/language/haskell-web.js +200 -18
- package/src/language/nix-fix.js +1 -1
- package/src/language/resolved-pass.js +4 -1
- package/src/language/witness.js +1 -1
- package/src/mcp/CLAUDE.md +34 -3
- package/src/mcp/tools.js +168 -39
- package/src/posture/blast-radius.js +15 -1
- package/src/posture/remediation-ledger.js +10 -1
- package/src/sast/pipeline.js +51 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
faee309bc53830c682a4e967147e6dbc6073b046d2220278b30e64f2e19b1675 agentic-security.mjs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clear-capabilities/agentic-security-scanner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.161.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",
|
|
@@ -72,23 +72,23 @@
|
|
|
72
72
|
"test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
|
|
73
73
|
"test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
|
|
74
74
|
"test:glob": "node --test test/glob-compat.test.js",
|
|
75
|
-
"test:sast": "node --test test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/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",
|
|
76
|
-
"test:posture": "node --test 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/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",
|
|
77
|
-
"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",
|
|
78
|
-
"test:mcp": "node --test test/mcp.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js test/mcp-dataflow-tools.test.js test/cli/governance-propose-edit.test.js test/cli/remediation-open-update.test.js test/cli/remediation-verify-reopen.test.js test/cli/dataflow-observations.test.js test/cli/federate-declare-list.test.js",
|
|
75
|
+
"test:sast": "node --test test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/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",
|
|
76
|
+
"test:posture": "node --test 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",
|
|
77
|
+
"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",
|
|
78
|
+
"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",
|
|
79
79
|
"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/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",
|
|
80
80
|
"test:lineage": "node --test test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js",
|
|
81
81
|
"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",
|
|
82
82
|
"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",
|
|
83
|
-
"test:haskell": "node --test 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",
|
|
84
|
-
"test:nix": "node --test 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",
|
|
83
|
+
"test:haskell": "node --test 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",
|
|
84
|
+
"test:nix": "node --test 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",
|
|
85
85
|
"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",
|
|
86
86
|
"test:language-stress": "node --test --test-concurrency=1 test/language/language-stress-offline.test.js",
|
|
87
87
|
"test:language-tools": "node --test test/haskell/haskell-web-auth.test.js test/haskell/haskell-support-gate.test.js",
|
|
88
88
|
"bench:language-support:check": "node ../bench/language-support/check.mjs",
|
|
89
89
|
"bench:live-feed": "node ../bench/live-feed/run.mjs",
|
|
90
90
|
"bench:language-support:perf": "node ../bench/language-support/perf.mjs",
|
|
91
|
-
"test:lifecycle": "node --test test/ci-hang-protection.test.js test/pre-bash-guard.test.js test/lsp-protocol-smoke.test.js test/ide-surfaces.test.js test/detector-liveness.test.js test/bench-watchdog.test.js test/ci-parity.test.js test/dead-code.test.js test/no-dead-modules.test.js test/no-orphan-scripts.test.js test/no-stray-state.test.js test/tree-integrity.test.js test/stop-hook.test.js test/plugin-self-check.test.js test/skills-registry.test.js test/bodyguard.test.js test/cache-invalidator-guard.test.js test/dispatch-pre-tool.test.js test/dispatch-user-prompt.test.js test/session-start-model-capture.test.js test/sandbox.test.js test/sandbox-escape.test.js test/ci-templates.test.js test/agent-mcp-tools.test.js test/triage-command.test.js test/no-orphan-tests.test.js test/supply-command.test.js test/posture-command.test.js test/labs-command.test.js test/compliance-command.test.js test/check-doc-drift.test.js test/dist-chunks-tracked.test.js && node ../scripts/lint-command-descriptions.mjs test/workflow-confinement.test.js test/check-bundle-source.test.js test/ship.test.js",
|
|
91
|
+
"test:lifecycle": "node --test test/ci-hang-protection.test.js test/ci-test-shards.test.js test/pre-bash-guard.test.js test/lsp-protocol-smoke.test.js test/ide-surfaces.test.js test/detector-liveness.test.js test/bench-watchdog.test.js test/ci-parity.test.js test/dead-code.test.js test/no-dead-modules.test.js test/no-orphan-scripts.test.js test/no-stray-state.test.js test/tree-integrity.test.js test/stop-hook.test.js test/plugin-self-check.test.js test/skills-registry.test.js test/bodyguard.test.js test/cache-invalidator-guard.test.js test/dispatch-pre-tool.test.js test/dispatch-user-prompt.test.js test/session-start-model-capture.test.js test/sandbox.test.js test/sandbox-escape.test.js test/ci-templates.test.js test/agent-mcp-tools.test.js test/triage-command.test.js test/no-orphan-tests.test.js test/supply-command.test.js test/posture-command.test.js test/labs-command.test.js test/compliance-command.test.js test/check-doc-drift.test.js test/dist-chunks-tracked.test.js && node ../scripts/lint-command-descriptions.mjs test/workflow-confinement.test.js test/check-bundle-source.test.js test/ship.test.js",
|
|
92
92
|
"test:eval": "node --test test/independent-eval.test.js test/realworld-recall.test.js",
|
|
93
93
|
"test:discovery": "node --test test/discovery-partition.test.js test/discovery-lenses.test.js test/discovery-hunter.test.js test/discovery-confirm.test.js test/discovery-disprove.test.js test/discovery-judge.test.js test/discovery-run.test.js test/discovery-wiring.test.js test/discovery-llm-invoke.test.js test/local-only-mode.test.js",
|
|
94
94
|
"eval:independent": "node ../bench/independent-eval/runner.mjs",
|
package/src/dataflow/engine.js
CHANGED
|
@@ -329,14 +329,14 @@ function _resolveCalleeForSummary(calleeExpr, callContext) {
|
|
|
329
329
|
// call's OWN arguments, silently losing the callee's return-taint. Mirrors
|
|
330
330
|
// the same resolve -> get-or-compute -> merge sequence step()'s two existing
|
|
331
331
|
// call sites use, via the Task 3/4 shared _resolveCalleeForSummary.
|
|
332
|
-
function _nestedCallReturnTainted(calleeExpr, argExprs, state, callContext) {
|
|
332
|
+
function _nestedCallReturnTainted(calleeExpr, argExprs, state, callContext, argTaint) {
|
|
333
333
|
if (!callContext || !callContext._summaryCache) return false;
|
|
334
334
|
const target = _resolveCalleeForSummary(calleeExpr, callContext);
|
|
335
335
|
if (!target) return false;
|
|
336
336
|
const { qid, fn } = target;
|
|
337
337
|
const paramNames = (fn && Array.isArray(fn.params)) ? fn.params : [];
|
|
338
338
|
const entry = paramNames.length
|
|
339
|
-
? entryStateFromCall(paramNames, argExprs || [], state, (a) => exprTaint(a, state, callContext))
|
|
339
|
+
? entryStateFromCall(paramNames, argExprs || [], state, argTaint || ((a) => exprTaint(a, state, callContext)))
|
|
340
340
|
: new Set();
|
|
341
341
|
let sum = callContext._summaryCache.get(qid, entry);
|
|
342
342
|
if (!sum && fn && fn.cfg) {
|
|
@@ -470,7 +470,11 @@ function _calleeGetterFieldTainted(calleeExpr, state, callContext) {
|
|
|
470
470
|
return !!(receiverPath && isCoveredBy(state, `${receiverPath}.${field}`));
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
// Work counter for the scaling guard in test/dataflow-expr-taint-scaling.test.js (a count, not a clock).
|
|
474
|
+
export const _perf = { exprTaintCalls: 0 };
|
|
475
|
+
|
|
473
476
|
function exprTaint(expr, state, callContext) {
|
|
477
|
+
_perf.exprTaintCalls += 1;
|
|
474
478
|
if (expr && (expr.kind === 'member' || expr.kind === 'call' || expr.kind === 'ident') && exprIsSource(expr, callContext)) return true;
|
|
475
479
|
if (!expr) return false;
|
|
476
480
|
// P1.1 — field-sensitive access path: if the expression is a pure
|
|
@@ -518,8 +522,20 @@ function exprTaint(expr, state, callContext) {
|
|
|
518
522
|
// direction) so the merge always runs when a call expression is
|
|
519
523
|
// visited; _resolveCalleeForSummary + SummaryCache make repeat
|
|
520
524
|
// resolution/computation for the same (qid, entry-state) cheap.
|
|
521
|
-
|
|
522
|
-
|
|
525
|
+
// Each argument's taint is computed at most once per visit of this call. The argument loop below and the
|
|
526
|
+
// callee's entry state (built inside _nestedCallReturnTainted) both ask for it, and with the second ask
|
|
527
|
+
// re-walking the whole argument subtree the work doubled at every level of call nesting: a chain of N nested
|
|
528
|
+
// calls cost 2^N exprTaint visits (a deep-mode scan of a large Haskell project did not finish). The value asked
|
|
529
|
+
// for is the same pure function of (argument, state) both times, so reusing it changes no verdict.
|
|
530
|
+
const _argMemo = new Map();
|
|
531
|
+
const _argTaint = (a) => {
|
|
532
|
+
if (_argMemo.has(a)) return _argMemo.get(a);
|
|
533
|
+
const r = exprTaint(a, state, callContext);
|
|
534
|
+
_argMemo.set(a, r);
|
|
535
|
+
return r;
|
|
536
|
+
};
|
|
537
|
+
const argsTainted = (expr.args || []).some((a, i) => !_isSprintfSafeArg(expr, i) && _argTaint(a));
|
|
538
|
+
const nestedTainted = _nestedCallReturnTainted(expr.callee, expr.args, state, callContext, _argTaint);
|
|
523
539
|
// Taint-recall PRD (80%): a call's RECEIVER can itself be tainted
|
|
524
540
|
// independent of its arguments — `tainted.toString()`, `tainted.trim()`,
|
|
525
541
|
// `it.getBytes()` — and neither argsTainted (there are no/unrelated
|
package/src/engine.js
CHANGED
|
@@ -6684,17 +6684,32 @@ function _semverSatisfies(ver,range){
|
|
|
6684
6684
|
function markUsedVulnFunctions(supplyChain,fc){
|
|
6685
6685
|
const used={};
|
|
6686
6686
|
const perFile={};
|
|
6687
|
+
// The per-(package, function) expressions are compiled once, not once per file, and a file is split into lines only when
|
|
6688
|
+
// it contains the text a match needs: `\b(?:pkg|_)\.fn\b` can only match a line holding the substring `.fn`, and the Rust
|
|
6689
|
+
// bare-call expression one holding `fn`. Both prefilters are exact for a plain identifier; any other name skips the
|
|
6690
|
+
// prefilter and is judged line by line, as before. Match order (file, package, function, line) is unchanged.
|
|
6691
|
+
const _plainIdent=/^[A-Za-z_]\w*$/;
|
|
6692
|
+
const _hintRes=new Map();
|
|
6693
|
+
const _hintRe=(pkg,fn)=>{const k=pkg+'\0'+fn;let r=_hintRes.get(k);if(!r){r=new RegExp(`\\b(?:${pkg.replace(/\W/g,'\\$&')}|_)\\.${fn}\\b`,'g');_hintRes.set(k,r);}return r;};
|
|
6694
|
+
const _bareRes=new Map();
|
|
6695
|
+
const _bareRe=(fn)=>{let r=_bareRes.get(fn);if(!r){r=new RegExp(`\\b${fn.replace(/\W/g,'\\$&')}\\s*[(<]`,'g');_bareRes.set(fn,r);}return r;};
|
|
6696
|
+
const _hintEntries=Object.entries(VULN_FUNCTION_HINTS);
|
|
6697
|
+
for(const pkg of Object.keys(VULN_FUNCTION_HINTS))if(!perFile[pkg])perFile[pkg]=[];
|
|
6687
6698
|
for(const[fp,content] of Object.entries(fc)){
|
|
6688
|
-
|
|
6699
|
+
let lines=null;
|
|
6689
6700
|
// Rust import-aware matching: build import map for .rs files
|
|
6690
6701
|
let _rustImports=null;
|
|
6691
6702
|
if(/\.rs$/i.test(fp)){try{_rustImports=extractRustImportMap(content);}catch(_){}}
|
|
6692
|
-
for(const[pkg,fns] of
|
|
6693
|
-
if(!perFile[pkg])perFile[pkg]=[];
|
|
6703
|
+
for(const[pkg,fns] of _hintEntries){
|
|
6694
6704
|
for(const fn of fns){
|
|
6695
|
-
const
|
|
6705
|
+
const plain=_plainIdent.test(fn);
|
|
6706
|
+
const dotPossible=!plain||content.includes('.'+fn);
|
|
6707
|
+
const barePossible=_rustImports&&(!plain||content.includes(fn));
|
|
6708
|
+
if(!dotPossible&&!barePossible)continue;
|
|
6709
|
+
const re=_hintRe(pkg,fn);
|
|
6696
6710
|
// Rust: also match bare function calls if import map traces them to this package
|
|
6697
|
-
const rustBareRe=_rustImports?
|
|
6711
|
+
const rustBareRe=_rustImports?_bareRe(fn):null;
|
|
6712
|
+
if(lines===null)lines=content.split('\n');
|
|
6698
6713
|
for(let li=0;li<lines.length;li++){
|
|
6699
6714
|
let matched=re.test(lines[li]);
|
|
6700
6715
|
re.lastIndex=0;
|
|
@@ -6733,9 +6748,12 @@ function markUsedVulnFunctions(supplyChain,fc){
|
|
|
6733
6748
|
// Search codebase for these functions (if not already searched via VULN_FUNCTION_HINTS)
|
|
6734
6749
|
if(osvFns.length&&!hardcoded.length){
|
|
6735
6750
|
for(const[fp,content] of Object.entries(fc)){
|
|
6736
|
-
|
|
6751
|
+
let lines=null;
|
|
6737
6752
|
for(const fn of osvFns){
|
|
6738
6753
|
const shortFn=fn.lastIndexOf('.')>0?fn.slice(fn.lastIndexOf('.')+1):fn;
|
|
6754
|
+
// the expression escapes every non-word character, so a match needs the literal text on its line
|
|
6755
|
+
if(!content.includes(shortFn))continue;
|
|
6756
|
+
if(lines===null)lines=content.split('\n');
|
|
6739
6757
|
const re=new RegExp(`\\b${shortFn.replace(/\W/g,'\\$&')}\\b`,'g');
|
|
6740
6758
|
for(let li=0;li<lines.length;li++){
|
|
6741
6759
|
if(re.test(lines[li])){
|
|
@@ -9086,6 +9104,11 @@ function _deterministicFileTimings(timings) {
|
|
|
9086
9104
|
setProgress({current:i,total:files.length,file:"Config file cross-ref...",phase:"Linking"});aLogic.push(...(runDetector(_detectorErrors,'<project>','scanConfigFiles',()=>scanConfigFiles(fc))||[]));
|
|
9087
9105
|
setProgress({current:i,total:files.length,file:"OSV vulnerability database...",phase:"SCA"});
|
|
9088
9106
|
const allFileContents={...fc, ...depFileContents};
|
|
9107
|
+
// The opt-in live advisory feeds are fetched HERE, as soon as the manifests are in hand, not after the expensive passes: a scan that is
|
|
9108
|
+
// killed or times out later still has its feed data on disk (the lookup used to run ~1447 s into a 1460 s scan, so an early kill looked like
|
|
9109
|
+
// "feed unavailable"). The inputs are the file contents only, so the result is the same as when this ran at the end.
|
|
9110
|
+
try{if(process.env.AGENTIC_SECURITY_HACKAGE_ADVISORIES_LIVE==='1'){const{prefetchHackageFeed}=await import('./language/haskell-supply.js');const{resolvedHackageComponents,nixHaskellPackageNames}=await import('./language/resolved-pass.js');let _rc=[],_nx=[];try{_rc=resolvedHackageComponents(allFileContents).components||[];}catch(_){}try{_nx=nixHaskellPackageNames(allFileContents);}catch(_){}await prefetchHackageFeed(allFileContents,{resolvedComponents:_rc,extraNames:_nx});}}catch(_){/* the live feed is optional: a failure leaves the scan on whatever snapshot exists */}
|
|
9111
|
+
try{if(process.env.AGENTIC_SECURITY_NIX_ADVISORIES_LIVE==='1'){const{prefetchNixAdvisoryFeed}=await import('./language/resolved-pass.js');await prefetchNixAdvisoryFeed(allFileContents);}}catch(_){/* the live feed is optional: a failure leaves the scan on whatever snapshot exists */}
|
|
9089
9112
|
// PRD F11.4 — malicious install hooks, scanned where package.json actually
|
|
9090
9113
|
// lives. `scanInstallScripts` was wired into the per-file SAST dispatch, but
|
|
9091
9114
|
// that loop iterates `files`, which comes from `fileContents` — and
|
|
@@ -9231,8 +9254,6 @@ function _deterministicFileTimings(timings) {
|
|
|
9231
9254
|
const annotatedComponents=components.map(c=>{const key=`${c.ecosystem}:${c.name}:${c.version}`;const vulns=vulnsByKey[key]||[];const riKey=c.ecosystem==='maven'&&c.group?`maven:${c.group}/${c.name}`:`${c.ecosystem}:${c.name}`;const ri=registryInfo.get(riKey)||{};const latestVersion=ri.latestVersion||'';const vd=(ri.versions||{})[c.version]||{};const isDeprecated=typeof vd.deprecated==='string'&&vd.deprecated.length>0;const deprecationMessage=isDeprecated?vd.deprecated:'';const isOutdated=!isDeprecated&&typeof vd.outdated==='string'&&vd.outdated.length>0;const outdatedMessage=isOutdated?vd.outdated:'';const license=ri.license||vd.license||'';return{...c,vulns,hasVulns:vulns.length>0,hasAttackPath:attackResult.flagged.has(key),attackPaths:attackResult.pathsByKey.get(key)||[],latestVersion,isDeprecated,deprecationMessage,isOutdated,outdatedMessage,license};});
|
|
9232
9255
|
// X-010: Hackage and Nix components, dependency edges and target provenance. Kept beside (not inside) the ordinary
|
|
9233
9256
|
// component list so the existing consumers are untouched; the BOM emitters and the SBOM diff read it explicitly.
|
|
9234
|
-
try{if(process.env.AGENTIC_SECURITY_HACKAGE_ADVISORIES_LIVE==='1'){const{prefetchHackageFeed}=await import('./language/haskell-supply.js');const{resolvedHackageComponents,nixHaskellPackageNames}=await import('./language/resolved-pass.js');let _rc=[],_nx=[];try{_rc=resolvedHackageComponents(allFileContents).components||[];}catch(_){}try{_nx=nixHaskellPackageNames(allFileContents);}catch(_){}await prefetchHackageFeed(allFileContents,{resolvedComponents:_rc,extraNames:_nx});}}catch(_){/* the live feed is optional: a failure leaves the scan on whatever snapshot exists */}
|
|
9235
|
-
try{if(process.env.AGENTIC_SECURITY_NIX_ADVISORIES_LIVE==='1'){const{prefetchNixAdvisoryFeed}=await import('./language/resolved-pass.js');await prefetchNixAdvisoryFeed(allFileContents);}}catch(_){/* the live feed is optional: a failure leaves the scan on whatever snapshot exists */}
|
|
9236
9257
|
let _languageBom=null;try{if(Object.keys(allFileContents).some(f=>/\.(?:l?hs|nix)$|\.cabal$|(?:^|\/)(?:cabal\.project(?:\.freeze)?|package\.yaml|stack\.yaml(?:\.lock)?|flake\.lock)$/i.test(f))){const{languageBom}=await import('./language/bom.js');const{resolvedHaskellGraph,nixClosureOf}=await import('./language/resolved-pass.js');const _rg=resolvedHaskellGraph(allFileContents);const _nc=nixClosureOf(allFileContents);_languageBom=languageBom(allFileContents,{...(_rg?{resolved:_rg.graph}:{}),...(_nc&&!_nc.refused.length?{closure:_nc.closure}:{})});}}catch(_){_languageBom=null;}
|
|
9237
9258
|
aF.push(...(runDetector(_detectorErrors,'<project>','scanDbTaintCrossFile',()=>scanDbTaintCrossFile(fc))||[]));
|
|
9238
9259
|
aF.push(...(runDetector(_detectorErrors,'<project>','scanCsharpCrossFile',()=>scanCsharpCrossFile(fc))||[]));
|
package/src/language/CLAUDE.md
CHANGED
|
@@ -31,6 +31,9 @@ or a capability.** Product-facing guides: `docs/guides/haskell.md`, `docs/guides
|
|
|
31
31
|
queried (`covered`); a package it did not cover is `feed-incomplete` and a stale one `feed-stale`, in BOTH matchers
|
|
32
32
|
(`evaluateComponents` and `nix-sca.js`). Any new consumer of an `AdvisoryDb` must check `db.coverage(name)` before saying "no advisory".
|
|
33
33
|
The snapshot lives in the operator configuration directory, never the project (`trusted-inputs.js`).
|
|
34
|
+
- **Auth guards must stop the handler.** In `haskell-web.js` a Scotty `status 40x` only sets the code, so both the named-guard path (`guardSummary`) and the inline path (`handlerFacts`) need a halting call (`finish`, `raise`, `raiseStatus`, `redirect`; `finish` is an IR identifier with no line of its own). WAI, Servant and Yesod inline guards cannot be ordered by line (the IR reports a tail-position call at the enclosing line), so after the IR finds a candidate (credential read plus a rejection that ends the handler), `inlineOrder` decides the ORDER from the handler's syntax tree: do-block statements in sequence, a scrutinee/condition before its branches, `when`/`unless`/`if`/`case` as forks (a branch that always rejects after a read guards its siblings and everything after the fork). Leaves are classified by lowering them alone and reusing `credentialReadsOf`/`isRejection`/`stopsHandler`/`isSensitiveCall`, so there is no second definition of those. Verdicts: `before` (authenticated), `after` (read, write, then reject: `late`, CWE-306 "runs after"), `unknown` (order not provable: `late` with `evidence[0].reason`, never authenticated). `throwError`/`permissionDenied`/`notAuthenticated` stop the handler anywhere; a WAI `respond`/`return` of a 401 stops it only in TAIL position (a mid-block `respond` is followed by the next statement). Remaining limits, all failing toward `late`/missing and never toward authenticated: a rejection and a sensitive call inside one opaque expression (`maybe`/`either` with both), a handler whose source structure is unavailable, and a sensitive call the walk cannot find; also unchanged, the guarding condition is not required to mention the credential. Pinned both ways in `test/haskell/haskell-web-guards.test.js`. A WAI `responseLBS` rejection counts only when handed back (`respond`/`return`).
|
|
35
|
+
- **Declared uses are never collapsed to the first one, and project bounds are disclosed.** `hackageComponents` returns `components` (one per package, scope and resolved version, for inventories) AND `uses` (every distinct declared use, identical ones merged with all declaring components in `carriers`); the evaluator runs over `uses` and `collapseToWorst` reports ONE finding per package, scope and advisory carrying the WORST status, `carriedBy` naming the components that carry it and `otherUses` the milder ones. cabal.project `constraints:` are intersected with a declared range and `allow-newer`/`allow-older` widen it (`effectiveDeclared`), only for a use with no resolved version and only from a project file whose `packages:` governs the manifest; a constraint under an `if` is not applied and is disclosed as a gap. Widening is the safe direction: never narrow on a relaxation you cannot match.
|
|
36
|
+
- **Compiler-provided packages are one finding per advisory.** GHC boot-library findings are grouped (`grouped: 'compiler'`, `members`, `memberCount`) and counted in `counts.compilerAdvisories`, not `counts.dependencyFindings`; every use keeps its `ghc-component:*` status row. Which advisory applies is decided against `projectCompiler` (`with-compiler`, Stack compiler, a fresh plan's compiler-id, or a `tested-with` naming exactly one version; conflicting or several versions is `unknown compiler`, never a guess). `base` takes the base series of a known GHC release (`GHC_BASE_SERIES`, series only, so the result is a range); a plan's boot unit gives the exact version. A series not in the table is not guessed.
|
|
34
37
|
- **Auth guards must stop the handler.** In `haskell-web.js` a Scotty `status 40x` only sets the code, so both the named-guard path (`guardSummary`) and the inline path (`handlerFacts`) need a halting call (`finish`, `raise`, `raiseStatus`, `redirect`; `finish` is an IR identifier with no line of its own). WAI, Servant and Yesod inline guards order the credential READ against the first sensitive call, because a tail-position rejection reports the enclosing line (a disclosed gap, pinned in `test/haskell/haskell-web-guards.test.js`). A WAI `responseLBS` rejection counts only when handed back (`respond`/`return`).
|
|
35
38
|
- **Severity is the advisory's own** (`cvss.js`): CVSS v3.x base score, else a named `database_specific.severity`, else `medium` with the basis stated. Never guess from an unparseable or v4 vector.
|
|
36
39
|
- **Option fixes work on the effective configuration, across files.** `nix-fix.js` edits EVERY contributing definition of an option
|
|
@@ -41,7 +44,8 @@ or a capability.** Product-facing guides: `docs/guides/haskell.md`, `docs/guides
|
|
|
41
44
|
first write, rolls back on a failed write) and records one history entry per file sharing `languageGroupId`; `undoFix` and
|
|
42
45
|
`posture/fix-history.js` `undoLast`/`revertEntryById` restore a group as a unit. A winner outside the root or not a single
|
|
43
46
|
literal is refused with an `override` suggestion (`overrideSuggestion`), never an automatic edit. Any new consumer of a fix
|
|
44
|
-
plan (LSP, MCP, CLI) must honour `plan.edits
|
|
47
|
+
plan (LSP, MCP, CLI) must honour `plan.edits`. MCP `apply_fix` does so through `plan_digest` (the server recomputes the plan and
|
|
48
|
+
compares a digest; see `mcp/CLAUDE.md`), using the lifecycle's optional `preWrite` hook, which runs after every gate and before the first write.
|
|
45
49
|
- **Boundaries are narrowed only by sound, closed rules.** `haskell-cpp.js` decides a CPP conditional only from the file, a project-stated
|
|
46
50
|
compiler (`with-compiler` pin; `tested-with` only when every item is an exact version and all agree, recorded as an assumption) or the hull
|
|
47
51
|
of cabal `build-depends` bounds; an unknown macro is undecided, never 0, and an undecided conditional keeps every branch. `haskell-boundaries.js`
|
|
@@ -57,6 +61,7 @@ or a capability.** Product-facing guides: `docs/guides/haskell.md`, `docs/guides
|
|
|
57
61
|
will use; metadata comes only from the operator (`AGENTIC_SECURITY_NIX_META`). NVD is rate limited (5/30 s keyless): never raise
|
|
58
62
|
the request caps or lower the spacing in `nix-advisory-feed.js` without re-reading the NVD limits.
|
|
59
63
|
- **Pragma** (`pragma.js`): comment-aware, line-scoped, exact rule-id match, logged. A line-less finding cannot be suppressed.
|
|
64
|
+
- **Scale: no per-use scan of the project.** Pandoc and cabal-install (hundreds to thousands of modules) did not finish a scan because of four superlinear costs, now fixed; do not reintroduce the shapes. (1) `haskell-ir.js`: anything a lowering step needs "from every module" (a constructor by name, the record types, a definition by qid, a name's resolution) is an index on `Project` / a per-module memo (`findCon`, `recordTypes`, `defByQid`, `RESOLVE_MEMO`), built once, never a loop over `proj.modules` per use. Only a top-level resolution (depth 0) is memoised: a resolution inside a re-export chain depends on the path taken. (2) `aibom.js` looks a file's imports up in a map, not `Object.values(perFile).find`. (3) `sast/pipeline.js`: the workflow-wide `env:` secret rule is a one-pass matcher (`scanEnvSecrets`), because the single regular expression it replaced backtracked catastrophically on a comment-blanked workflow (blanking leaves runs of blank lines). A detector expression that runs on comment-blanked text must not let `\s*`, `\n` and a lazy line loop claim the same blank lines. (4) `dataflow/engine.js` `exprTaint`: a call's argument taint is computed once per visit (`_argTaint`); computing it twice re-walked the subtree and cost 2^N for N nested calls. Guards: `test/pipeline-env-secret.test.js`, `test/dataflow-expr-taint-scaling.test.js`, `test/haskell/haskell-ir-scaling.test.js`, `test/mark-used-vuln-functions.test.js` compare work counters between two sizes, never a clock.
|
|
60
65
|
|
|
61
66
|
## Module map
|
|
62
67
|
|
package/src/language/aibom.js
CHANGED
|
@@ -80,7 +80,10 @@ export function extractHaskellAI(files) {
|
|
|
80
80
|
const declared = new Map(comps.filter((c) => HS_AI_PACKAGES[c.name]).map((c) => [c.name, c]));
|
|
81
81
|
let ir = null;
|
|
82
82
|
try { ir = buildHaskellIR(Object.fromEntries(sources)); } catch (e) { gaps.push({ kind: 'haskell-ir-failed', detail: e.message }); }
|
|
83
|
-
|
|
83
|
+
// file -> its imports, first entry wins; built once (a per-call scan of every file made this quadratic in project size)
|
|
84
|
+
const importsByFile = new Map();
|
|
85
|
+
if (ir && ir.perFile) for (const x of Object.values(ir.perFile)) if (!importsByFile.has(x.file)) importsByFile.set(x.file, x.imports || []);
|
|
86
|
+
const importsOf = (f) => importsByFile.get(f) || [];
|
|
84
87
|
|
|
85
88
|
for (const [file, text] of sources) {
|
|
86
89
|
const toks = tokenizeHaskell(text);
|
|
@@ -191,6 +191,15 @@ export async function runFixLifecycle(o) {
|
|
|
191
191
|
const res = { status: 'verified', applied: false, plan, gates, label: plan.label, verified, preview: edits.map((e) => unifiedDiff(e.file, e.before, e.after)).join('\n'), files: edits.map((e) => e.file) };
|
|
192
192
|
if (o.apply) {
|
|
193
193
|
if (!o.root) return blocked('apply requires a root directory');
|
|
194
|
+
// Optional last check, after every gate has passed and before the first byte is written, so a caller that bound this plan
|
|
195
|
+
// to something outside the lifecycle (a digest, a confinement policy) refuses atomically with the write it guards. A hook
|
|
196
|
+
// that throws or answers ok:false blocks the apply; nothing has been written at this point.
|
|
197
|
+
if (o.preWrite) {
|
|
198
|
+
let pre;
|
|
199
|
+
try { pre = o.preWrite(plan, edits); } catch (err) { pre = { ok: false, detail: `pre-write check failed: ${err.message}` }; }
|
|
200
|
+
gates.preWrite = pre || { ok: false, detail: 'no pre-write result' };
|
|
201
|
+
if (gates.preWrite.ok !== true) return blocked(`pre-write: ${gates.preWrite.detail || 'refused'} (no file was written)`);
|
|
202
|
+
}
|
|
194
203
|
const base = { scanRoot: o.root, findingId: finding.id || `${finding.file}:${finding.line}`, ruleId: finding.rule || null, vuln: finding.vuln || null, stableId: finding.stableId || null, fixLabel: plan.label || null, verification: verified, findingProvenance: finding.findingProvenance || null };
|
|
195
204
|
if (edits.length === 1) {
|
|
196
205
|
res.backup = writeWithBackup(o.root, plan.file, plan.before, plan.after);
|
|
@@ -175,7 +175,23 @@ function exportedLookup(proj, target, name, depth, seen) {
|
|
|
175
175
|
return null;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
// A top-level resolution (depth 0, nothing seen yet) is a pure function of the module table, which is complete before any function
|
|
179
|
+
// is lowered, so its answer is kept per module: the same name is resolved again at every use site, and each resolution walks the
|
|
180
|
+
// module's imports and the re-export chains of every module it imports (hub modules re-export hundreds of names). Resolutions
|
|
181
|
+
// made inside a re-export chain (depth > 0) depend on the path taken, so they are never kept. Callers treat the result as
|
|
182
|
+
// read-only.
|
|
183
|
+
const RESOLVE_MEMO = new WeakMap(); // module info -> (qualifier, name) -> resolution; a module's memo dies with the module
|
|
184
|
+
function resolveGlobal(proj, mod, name, qual, depth = 0, seen = null) {
|
|
185
|
+
if (depth !== 0 || seen) return resolveGlobalUncached(proj, mod, name, qual, depth, seen || new Set());
|
|
186
|
+
let memo = RESOLVE_MEMO.get(mod);
|
|
187
|
+
if (!memo) { memo = new Map(); RESOLVE_MEMO.set(mod, memo); }
|
|
188
|
+
const key = qual ? `${qual}\0${name}` : name;
|
|
189
|
+
let r = memo.get(key);
|
|
190
|
+
if (r === undefined) { r = resolveGlobalUncached(proj, mod, name, qual, 0, new Set()); memo.set(key, r); }
|
|
191
|
+
return r;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function resolveGlobalUncached(proj, mod, name, qual, depth, seen) {
|
|
179
195
|
if ((!qual || qual === mod.name) && mod.defs.has(name)) return defResult(mod, name);
|
|
180
196
|
if (depth > HS_IR_LIMITS.maxResolveDepth) return { kind: 'unknown', reason: 'resolution-depth-cap' };
|
|
181
197
|
const hits = [];
|
|
@@ -649,8 +665,7 @@ class FnLowerer {
|
|
|
649
665
|
// call to it, not a mere reference: it runs when sequenced.
|
|
650
666
|
executeRef(v, ln) {
|
|
651
667
|
if (!v || v.kind !== 'ident' || !v.hs || !v.hs.functionRef || v.hs.closure) return v;
|
|
652
|
-
|
|
653
|
-
for (const m of this.proj.modules.values()) for (const d of m.defs.values()) if (d.qid === v.hs.functionRef) def = d;
|
|
668
|
+
const def = this.proj.defByQid(v.hs.functionRef);
|
|
654
669
|
if (!def || def.kind !== 'fun' || def.arity !== 0) return v;
|
|
655
670
|
return { kind: 'call', callee: v.name, args: [], line: ln, hs: { status: 'resolved', target: v.hs.functionRef, effect: 'io', action: true } };
|
|
656
671
|
}
|
|
@@ -1103,9 +1118,35 @@ class Project {
|
|
|
1103
1118
|
this.byFile.get(fn.file).push(fn);
|
|
1104
1119
|
}
|
|
1105
1120
|
|
|
1121
|
+
// The three lookups below used to walk every module on every call (a constructor, a record type, a def by qid), which is
|
|
1122
|
+
// quadratic in project size. They are built once, lazily, on first use: the module table is complete before any function
|
|
1123
|
+
// is lowered, and nothing adds a module, a constructor or a definition afterwards. First-wins / last-wins orders match the
|
|
1124
|
+
// original scans exactly.
|
|
1106
1125
|
findCon(name) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1126
|
+
if (!this._conIndex) {
|
|
1127
|
+
this._conIndex = new Map();
|
|
1128
|
+
for (const m of this.modules.values()) for (const [n, c] of m.cons) if (!this._conIndex.has(n)) this._conIndex.set(n, c);
|
|
1129
|
+
}
|
|
1130
|
+
return this._conIndex.get(name) || null;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Record types declared in the project: type name -> field names (a later declaration of the same type name wins).
|
|
1134
|
+
recordTypes() {
|
|
1135
|
+
if (!this._recTypes) {
|
|
1136
|
+
const recTypes = new Map();
|
|
1137
|
+
for (const m of this.modules.values()) for (const d of (m.parse && m.parse.data) || []) if (d.constructors && d.constructors.some((c) => c.fields && c.fields.length)) recTypes.set(d.name, d.constructors.flatMap((c) => c.fields || []));
|
|
1138
|
+
this._recTypes = recTypes;
|
|
1139
|
+
}
|
|
1140
|
+
return this._recTypes;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// A definition by its qid (the last one in module order wins, as the linear scan it replaces did).
|
|
1144
|
+
defByQid(qid) {
|
|
1145
|
+
if (!this._defIndex) {
|
|
1146
|
+
this._defIndex = new Map();
|
|
1147
|
+
for (const m of this.modules.values()) for (const d of m.defs.values()) this._defIndex.set(d.qid, d);
|
|
1148
|
+
}
|
|
1149
|
+
return this._defIndex.get(qid) || null;
|
|
1109
1150
|
}
|
|
1110
1151
|
|
|
1111
1152
|
instancesOf(cls, method) { return [...(this.instanceIndex.get(`${cls}.${method}`) || [])]; }
|
|
@@ -1123,8 +1164,7 @@ function lowerTopFunction(proj, mod, fun, def) {
|
|
|
1123
1164
|
fn.hs.textParams = ann.map((a) => a.name);
|
|
1124
1165
|
// Parameters of an exported function whose type is a record DECLARED IN THE PROJECT: the lineage view treats each such
|
|
1125
1166
|
// parameter as a source of that record's fields (a customer record handed to an entry point), field by field.
|
|
1126
|
-
const recTypes =
|
|
1127
|
-
for (const m of proj.modules.values()) for (const d of (m.parse && m.parse.data) || []) if (d.constructors && d.constructors.some((c) => c.fields && c.fields.length)) recTypes.set(d.name, d.constructors.flatMap((c) => c.fields || []));
|
|
1167
|
+
const recTypes = proj.recordTypes();
|
|
1128
1168
|
fn.hs.recordParams = [];
|
|
1129
1169
|
if (exported && sig && Array.isArray(sig.argTypes) && Array.isArray(fn.params)) {
|
|
1130
1170
|
sig.argTypes.forEach((t, i) => { const m = /^\(?\s*([A-Z][A-Za-z0-9_']*)\s*\)?$/.exec(t); if (m && recTypes.has(m[1]) && fn.params[i]) fn.hs.recordParams.push({ name: fn.params[i], type: m[1], fields: [...new Set(recTypes.get(m[1]))] }); });
|
|
@@ -32,7 +32,24 @@ export const GHC_BOOT_PACKAGES = Object.freeze(new Set([
|
|
|
32
32
|
'process', 'stm', 'text', 'time', 'transformers', 'unix', 'xhtml', 'terminfo', 'Cabal', 'Cabal-syntax',
|
|
33
33
|
]));
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* The `base` series each GHC release series ships (the series only: patch releases of one GHC can ship different `base` patch levels, so
|
|
37
|
+
* the answer is a range, never a single version). A release series absent from this table is not guessed.
|
|
38
|
+
*/
|
|
39
|
+
export const GHC_BASE_SERIES = Object.freeze({
|
|
40
|
+
'8.8': '4.13', '8.10': '4.14', '9.0': '4.15', '9.2': '4.16', '9.4': '4.17', '9.6': '4.18', '9.8': '4.19', '9.10': '4.20', '9.12': '4.21', '9.14': '4.22',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** `>=4.18 && <4.19` for a GHC version such as "9.6.4", or null when the series is not in the table. */
|
|
44
|
+
export function baseRangeForGhc(ghcVersion) {
|
|
45
|
+
const m = /^(\d+)\.(\d+)(?:\.|$)/.exec(String(ghcVersion || ''));
|
|
46
|
+
const series = m && GHC_BASE_SERIES[`${m[1]}.${m[2]}`];
|
|
47
|
+
if (!series) return null;
|
|
48
|
+
const [a, b] = series.split('.').map(Number);
|
|
49
|
+
return `>=${a}.${b} && <${a}.${b + 1}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const hackagePurl =(name, version) => `pkg:hackage/${encodeURIComponent(name).replace(/%2D/g, '-')}${version ? `@${encodeURIComponent(version)}` : ''}`;
|
|
36
53
|
|
|
37
54
|
/** The exact JSON body an OSV `/v1/query` takes for one component. */
|
|
38
55
|
export function osvWireQuery(name, version) {
|
|
@@ -234,17 +251,46 @@ export function matchComponent(aff, comp) {
|
|
|
234
251
|
}
|
|
235
252
|
// `unbounded` = the manifest declared the dependency with NO version bound (`build-depends: aeson`), which in Cabal means "any version".
|
|
236
253
|
// That is a declared range like any other (it overlaps an advisory, so possibly-affected), not an absent one.
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
const projectConstraints = Array.isArray(comp.projectConstraints) ? comp.projectConstraints : [];
|
|
255
|
+
if (comp.declaredRange || comp.unbounded || projectConstraints.length) {
|
|
256
|
+
const eff = effectiveDeclared(comp);
|
|
257
|
+
const declared = eff.intervals;
|
|
239
258
|
if (declared === null) return { status: 'unknown', reason: 'the declared range could not be parsed' };
|
|
240
|
-
if (!declared.length) return { status: 'unknown', reason: 'the declared range admits no version' };
|
|
259
|
+
if (!declared.length) return { status: 'unknown', reason: projectConstraints.length ? `the declared range and the project constraints (${projectConstraints.map((c) => c.text).join(', ')}) admit no common version` : 'the declared range admits no version' };
|
|
241
260
|
const overlap = intervalsOverlap(declared, aff.intervals);
|
|
242
|
-
if (!overlap) return { status: 'not-affected', reason:
|
|
261
|
+
if (!overlap) return { status: 'not-affected', reason: `no version allowed by the declared range is affected${eff.note}` };
|
|
243
262
|
const subset = declared.every((d) => aff.intervals.some((a) => !isEmpty(intersectIntervals(d, a)) && containsInterval(a, d)));
|
|
244
|
-
return subset ? { status: 'affected', reason:
|
|
263
|
+
return subset ? { status: 'affected', reason: `every version allowed by the declared range is affected${eff.note}` } : { status: 'possibly-affected', reason: `${comp.declaredRange ? 'the declared range allows both affected and unaffected versions; no resolved version is known' : 'the dependency is declared with no version bound, so any version may be selected; no resolved version is known'}${eff.note}` };
|
|
245
264
|
}
|
|
246
265
|
return { status: 'unknown', reason: 'neither a resolved version nor a declared range is known' };
|
|
247
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The versions a component may be built with: its declared range, widened by `allow-newer` / `allow-older` (which only ever ADD
|
|
270
|
+
* versions: an upper or lower bound dropped), then narrowed by project `constraints:` (hard solver constraints, so they intersect).
|
|
271
|
+
* Widening comes first because a relaxation applies to the package's own bounds and a constraint to whatever remains.
|
|
272
|
+
* @returns {{intervals: object[]|null, note: string}} null intervals = the declared range could not be parsed
|
|
273
|
+
*/
|
|
274
|
+
export function effectiveDeclared(comp) {
|
|
275
|
+
let intervals = comp.declaredRange ? rangeToIntervals(comp.declaredRange) : [iv(null, false, null, false)];
|
|
276
|
+
if (intervals === null) return { intervals: null, note: '' };
|
|
277
|
+
const notes = [];
|
|
278
|
+
const rel = Array.isArray(comp.relaxation) && comp.relaxation.length ? new Set(comp.relaxation.map((r) => r.direction)) : null;
|
|
279
|
+
if (rel && comp.declaredRange) {
|
|
280
|
+
intervals = intervals.map((x) => iv(rel.has('older') ? ZERO : x.lo, rel.has('older') ? true : x.loInc, rel.has('newer') ? null : x.hi, rel.has('newer') ? false : x.hiInc));
|
|
281
|
+
notes.push(`widened by ${[...rel].map((d) => `allow-${d}`).join(' and ')}, so the declared bounds are not authoritative`);
|
|
282
|
+
}
|
|
283
|
+
const cons = Array.isArray(comp.projectConstraints) ? comp.projectConstraints : [];
|
|
284
|
+
for (const c of cons) {
|
|
285
|
+
const ci = rangeToIntervals(c.text);
|
|
286
|
+
if (ci === null) continue;
|
|
287
|
+
const out = [];
|
|
288
|
+
for (const a of intervals) for (const b of ci) out.push(intersectIntervals(a, b));
|
|
289
|
+
intervals = normalizeIntervals(out);
|
|
290
|
+
}
|
|
291
|
+
if (cons.length) notes.push(`narrowed by project constraints: ${cons.map((c) => c.text).join(', ')}`);
|
|
292
|
+
return { intervals, note: notes.length ? ` (${notes.join('; ')})` : '' };
|
|
293
|
+
}
|
|
248
294
|
function containsInterval(outer, inner) {
|
|
249
295
|
const loOk = outer.lo === null || (inner.lo !== null && (cmp(inner.lo, outer.lo) > 0 || (cmp(inner.lo, outer.lo) === 0 && (outer.loInc || !inner.loInc))));
|
|
250
296
|
const hiOk = outer.hi === null || (inner.hi !== null && (cmp(inner.hi, outer.hi) < 0 || (cmp(inner.hi, outer.hi) === 0 && (outer.hiInc || !inner.hiInc))));
|
|
@@ -291,6 +337,10 @@ export function evaluateComponents(components, db, opts = {}) {
|
|
|
291
337
|
...(ghc ? { remediation: `${comp.name} is provided by the compiler: upgrade GHC (the fix is in ${comp.name} ${aff.fixedIn.join(', ') || '(no fixed version published)'}), not a Cabal dependency bound.` } : { remediation: aff.fixedIn.length ? `Upgrade ${comp.name} to ${aff.fixedIn.join(' or ')}.` : `No fixed version of ${comp.name} is published; remove or replace the dependency.` }),
|
|
292
338
|
kev: kevHit, epss: epssVals.length ? Math.max(...epssVals) : 'unknown',
|
|
293
339
|
file: comp.manifest || null, line: comp.line || null, versionSource: comp.versionSource || null,
|
|
340
|
+
carriedBy: (comp.carriers && comp.carriers.length ? comp.carriers : [{ file: comp.manifest || null, line: comp.line || null, target: comp.target || null, componentKind: comp.componentKind || null, declaredRange: comp.declaredRange || null }]),
|
|
341
|
+
...(comp.projectConstraints && comp.projectConstraints.length ? { projectConstraints: comp.projectConstraints } : {}),
|
|
342
|
+
...(comp.relaxation && comp.relaxation.length ? { relaxation: comp.relaxation } : {}),
|
|
343
|
+
...(comp.compilerDerived ? { compilerDerived: comp.compilerDerived } : {}),
|
|
294
344
|
dataSource: { feed: db.source, generatedAt: db.generatedAt, integrity: db.integrity, feedStatus: feed.status },
|
|
295
345
|
language: 'haskell', capability: 'sca', analysisKind: 'application', evidenceKind: 'manifest',
|
|
296
346
|
confidence: m.status === 'affected' ? 0.9 : 0.5,
|
|
@@ -299,7 +349,37 @@ export function evaluateComponents(components, db, opts = {}) {
|
|
|
299
349
|
}
|
|
300
350
|
if (!any && !statuses.some((s) => s.name === comp.name && s.status !== 'not-affected')) statuses.push({ ...base, status: 'not-affected' });
|
|
301
351
|
}
|
|
302
|
-
return { findings, statuses, feed };
|
|
352
|
+
return { findings: collapseToWorst(findings), statuses, feed };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const STATUS_RANK = { affected: 3, 'possibly-affected': 2, unknown: 1 };
|
|
356
|
+
/**
|
|
357
|
+
* One finding per (package, scope, advisory) carrying the WORST status among the declared uses of that package, with every component that
|
|
358
|
+
* carries that worst status listed in `carriedBy`. Distinct declared ranges used to be collapsed by name and scope with the first one
|
|
359
|
+
* winning, which hid a worse use behind a milder one (found by the live Hackage feed bench). The milder uses are not dropped from the
|
|
360
|
+
* record: `otherUses` lists them, and every use keeps its own row in `statuses`.
|
|
361
|
+
*/
|
|
362
|
+
export function collapseToWorst(findings) {
|
|
363
|
+
const groups = new Map();
|
|
364
|
+
for (const f of findings) {
|
|
365
|
+
const k = `${f.name}\u0000${f.scope || ''}\u0000${f.osvId}\u0000${(f.ids && f.ids[0]) || ''}`; // two records for one advisory stay two findings
|
|
366
|
+
if (!groups.has(k)) groups.set(k, []);
|
|
367
|
+
groups.get(k).push(f);
|
|
368
|
+
}
|
|
369
|
+
const out = [];
|
|
370
|
+
for (const members of groups.values()) {
|
|
371
|
+
if (members.length === 1) { out.push(members[0]); continue; }
|
|
372
|
+
const rank = (f) => STATUS_RANK[f.matchStatus] || 0;
|
|
373
|
+
const worst = Math.max(...members.map(rank));
|
|
374
|
+
const top = members.filter((f) => rank(f) === worst);
|
|
375
|
+
const rest = members.filter((f) => rank(f) !== worst);
|
|
376
|
+
const carried = [];
|
|
377
|
+
for (const f of top) for (const c of f.carriedBy || []) if (!carried.some((x) => x.file === c.file && x.line === c.line && x.target === c.target && x.declaredRange === c.declaredRange)) carried.push(c);
|
|
378
|
+
const lead = { ...top[0], carriedBy: carried };
|
|
379
|
+
if (rest.length) lead.otherUses = rest.map((f) => ({ matchStatus: f.matchStatus, declaredRange: f.declaredRange, version: f.version, carriedBy: f.carriedBy }));
|
|
380
|
+
out.push(lead);
|
|
381
|
+
}
|
|
382
|
+
return out;
|
|
303
383
|
}
|
|
304
384
|
|
|
305
385
|
// ── reachability (imports) ───────────────────────────────────────────────────
|