@clear-capabilities/agentic-security-scanner 0.195.16 → 0.195.18

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.
@@ -1 +1 @@
1
- 10035213d4490ba0c69e465083a4322a6cb5e7717ea62b3e9eaabd048b468ac3 agentic-security.mjs
1
+ f295a45cc4eba5bc870003516d4b69c565ec87e4f54ff9db3310da5e8b9ddf00 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.16",
3
+ "version": "0.195.18",
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",
@@ -85,7 +85,7 @@
85
85
  "test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
86
86
  "test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
87
87
  "test:glob": "node --test test/glob-compat.test.js",
88
- "test:sast": "node --test test/dag-precision.test.js test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/zip-slip-go.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/csharp-cross-file.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/java-structural-cross-file.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/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",
88
+ "test:sast": "node --test test/dag-precision.test.js test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/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
89
  "test:posture": "node --test test/contracts/root-cause-sweep.contract.test.js test/contracts/model-validation.contract.test.js test/contracts/agentic-discovery.contract.test.js test/contracts/local-models.contract.test.js test/contracts/dependency-finding.contract.test.js test/contracts/taint-flow.contract.test.js test/contracts/runtime-corroboration.contract.test.js test/compliance-coverage.test.js test/contracts/blast-radius.contract.test.js test/contracts/federation.contract.test.js test/contracts/eval-lab-report.contract.test.js test/contracts/fleet.contract.test.js test/contracts/rule-compiler.contract.test.js test/contracts/release-assurance.contract.test.js test/contracts/dnr.contract.test.js test/contracts/strategic-metrics.contract.test.js test/contracts/exploit-proof.contract.test.js test/contracts/artifact-provenance.contract.test.js test/dnr/artifact-join.test.js test/rule-pack-signing.test.js test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/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/fleet-org-cli.test.js test/eval-lab/dataset-neutralize.test.js test/eval-lab/arms-score-stats.test.js test/eval-lab/run-report.test.js test/eval-lab/router.test.js test/eval-lab/arm-ollama.test.js test/eval-lab/import-independent.test.js test/cli/eval-lab-cli.test.js test/dnr/ir-and-outputs.test.js test/dnr/logs-and-hunt.test.js test/dnr/window-store-view.test.js test/dnr/exploit-correlation.test.js test/cli/dnr-cli.test.js test/proof-fabric/core.test.js test/proof-fabric/container.test.js test/proof-fabric/generate.test.js test/proof-fabric/siblings.test.js test/proof-fabric/generate-python.test.js test/proof-fabric/generate-haskell.test.js test/proof-fabric/generate-haskell-widened.test.js test/cli/proof-lifecycle-demo.test.js test/mcp-proof.test.js test/proof-fabric/fix-store-runner.test.js test/proof-fabric/fixtures.test.js test/cli/proof-fabric-cli.test.js test/rule-compiler/core.test.js test/rule-compiler/synthesis.test.js test/rule-compiler/structure.test.js test/cli/rule-compiler-cli.test.js test/fleet/fleet-graph.test.js test/fleet/fleet-manifest.test.js test/fleet/fleet-cli.test.js test/artifact/safe-tar.test.js test/artifact/image-archive.test.js test/artifact/filesystem.test.js test/artifact/layer-secrets.test.js test/artifact/inventory.test.js test/artifact/vuln-correlate.test.js test/artifact/signature.test.js test/artifact/provenance-graph.test.js test/artifact/mismatch.test.js test/artifact/vex.test.js test/artifact/registry.test.js test/artifact/sources-cosign-evidence.test.js test/cli/artifact-sources-cli.test.js test/cli/artifact-provenance-cli.test.js test/witness-bundle.test.js",
90
90
  "test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/chrome-probe.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-rust.test.js test/rust-taint-flow.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js test/catalog-ai-model-provider-precision.test.js test/catalog-orm-write.test.js test/dataflow/provenance-pipeline-integration.test.js test/dataflow-progress.test.js test/scan-progress.test.js test/sard-leakage-pipeline.test.js test/sard-split-scoring.test.js test/sard-llm-isolation.test.js test/sard-mutation-adversarial.test.js test/sard-baseline-adaptive-tolerance.test.js test/sard-independent-holdout.test.js test/sard-flow-aware-scoring.test.js test/sard-macro-score-support-floor.test.js test/sard-php-split.test.js test/parser-java-nary-concat.test.js test/sard-naive-baseline.test.js test/field-taint-cross-method.test.js test/field-taint-engine-unit.test.js test/coercion-sanitizer-kill.test.js test/constant-propagation-path-sensitivity.test.js test/parser-java-assignments.test.js test/catalog-java-sard.test.js test/parser-cs-juliet-shapes.test.js test/catalog-cs-sard-p2.test.js test/sard-cwe-key-merge.test.js test/parser-php-backtick.test.js test/parser-php-include.test.js test/php-include-merge.test.js test/parser-php-array-field-taint.test.js test/parser-php-cond-assign.test.js test/parser-rust-redos.test.js test/multi-sink-taint-chain.test.js test/static-field-cross-class-call-string.test.js test/regex-guard-narrowing.test.js test/object-instance-field-taint.test.js test/param-type-inference.test.js test/smt-path-feasibility.test.js test/cross-service-taint.test.js test/dataflow-expr-taint-scaling.test.js test/evaluation/guard-dominance.test.js test/evaluation/engine-mechanisms.test.js test/rx-exec-receiver.test.js test/incremental-dependency.test.js test/incremental-integrity.test.js test/demand-query.test.js test/ir-cache.test.js test/canonicalize-parity.test.js test/dataflow-source-attribution.test.js",
91
91
  "test:mcp": "node --test test/mcp.test.js test/mcp-apply-fix-multifile.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js test/mcp-dataflow-tools.test.js test/cli/governance-propose-edit.test.js test/cli/remediation-open-update.test.js test/cli/remediation-verify-reopen.test.js test/cli/dataflow-observations.test.js test/cli/federate-declare-list.test.js test/capabilities/tools.test.js",
@@ -93,7 +93,7 @@
93
93
  "test:lineage": "node --test test/lineage/analysis-completion.test.js test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js test/lineage/runtime-assurance.test.js test/lineage/runtime-edge-states.test.js test/lineage/runtime-explorer-view.test.js test/cli/runtime-explorer-cli.test.js test/cli/runtime-identity-cli.test.js test/cli/runtime-twin-cli.test.js test/lineage/cut-planner.test.js test/lineage/scc.test.js test/lineage/scc-solver.test.js test/graph/graph-views.test.js test/lineage/dag07-completion.test.js test/lineage/owned-build.test.js test/dataflow/boundary-propagation.test.js test/lineage/boundary-contract.test.js test/lineage/correlation-v2.test.js test/lineage/path-control-evidence.test.js test/lineage/smt-z3-backend.test.js test/cli/dag-commands.test.js",
94
94
  "test:bench-modules": "node --test test/phase4-harness.test.js test/pipeline.test.js test/proof-corpus-lib.test.js test/proof-corpus-runner.test.js",
95
95
  "test:server": "node --test test/server/graph-loader.test.js test/server/security.test.js test/server/routes.test.js test/server/http-server.test.js test/server/cmd-explore.test.js test/server/cmd-dataflow-export.test.js test/server/static-assets.test.js test/server/dataflow-scenario-cli.test.js test/server/dataflow-impact-cli.test.js",
96
- "test:haskell": "node --test test/haskell/haskell-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-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",
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
97
  "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
98
  "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
99
  "test:language-stress": "node --test --test-concurrency=1 test/language/language-stress-offline.test.js",
package/src/engine.js CHANGED
@@ -6300,6 +6300,13 @@ function scanEntropySecrets(fp,raw,_suppOut){
6300
6300
  }
6301
6301
 
6302
6302
  // ─── TODO/FIXME adjacent to auth/crypto ──────────────────────────────────────
6303
+ // The security vocabulary is matched as WHOLE WORDS after camelCase and snake_case identifiers are split into their words, so `getAuthToken`
6304
+ // and `check_password` still count, while `author` (not `auth`), `Hashable` (not `hash`) and `rolesetting`-style run-ons do not: a substring
6305
+ // match flagged a marker near any identifier that merely contained one of these letters in a row.
6306
+ const _SECURITY_WORD_RE=/\b(?:auth|authn|authz|authenticat(?:e|ed|es|ion|or)|authoriz(?:e|ed|es|ation|er)|jwts?|tokens?|passwords?|passwd|verif(?:y|ies|ied|ication)|hash(?:ed|es|ing)?|crypto|cryptograph\w*|sessions?|cookies?|login|logins|permissions?|roles?|rbac|admins?|administrator)\b/i;
6307
+ function _splitIdentifierWords(text){
6308
+ return text.replace(/([a-z0-9])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/[_\-.]+/g," ");
6309
+ }
6303
6310
  function scanTodosNearSecurity(fp,raw){
6304
6311
  const lines=raw.split("\n");
6305
6312
  const out=[];
@@ -6307,7 +6314,7 @@ function scanTodosNearSecurity(fp,raw){
6307
6314
  const ln=lines[i];
6308
6315
  if(!/\b(?:TODO|FIXME|HACK|XXX|BUG|TEMP)\b/.test(ln))continue;
6309
6316
  const window=lines.slice(Math.max(0,i-3),Math.min(lines.length,i+4)).join(" ");
6310
- if(!/(?:auth|jwt|token|password|verify|hash|crypto|session|cookie|login|permission|role|rbac|admin)/i.test(window))continue;
6317
+ if(!_SECURITY_WORD_RE.test(_splitIdentifierWords(window)))continue;
6311
6318
  out.push({
6312
6319
  vuln:"Known-Broken Code Marker Near Security-Sensitive Logic",
6313
6320
  severity:"medium",cwe:"CWE-1077",stride:"Tampering",
@@ -66,6 +66,7 @@ or a capability.** Product-facing guides: `docs/guides/haskell.md`, `docs/guides
66
66
  - **Guards are recognised by what they do.** `haskell-guards.js`: a project function is an HTML escaper when it (or its local helpers) spells out the entities for `<`, `>` and `&` (`htmlEscapers`; never by name), and it neutralises a CWE-79 flow only when EVERY use of the value in the sink argument is under such a call (`allUsesEscaped`); a host allow-list may be a named constant and may sit in an `&&`; `sh -c SCRIPT arg0 arg1` is shell injection only when the SCRIPT is built from the value, and tainted positional parameters are data when the constant script expands them only inside double quotes after `--` (`quotedPositionals`, which decodes the Haskell string escapes the IR keeps raw).
67
67
  - **Handlers named by the framework's convention are routes.** `haskell-web.js` adds `getFooR`/`postFooR`/`handleFooR` functions whose signature mentions `Handler` in a module importing Yesod when no route table in an analysed file names them (the table usually lives in `config/routes`). Scotty's rejection order is judged by `inlineOrder` as well as by line: a `case` lists the success branch first, so `status 40x >> finish` in the LAST alternative guards it (a bare `status` with no halting call does not).
68
68
  - **A project definition of a modelled name keeps the registry's meaning (since 0.195.11).** `haskell-ir.js` `applyVar`/`lowerVarValue`: a resolution to a project `def` whose module-qualified name `isKnownApi` (the project IS the framework, or vendors it) is lowered as the LIBRARY call (`hs.status: 'external'`, `hs.modelledDefinition: qid`) and the definition is still referenced (`noteRef(..., 'modelled-definition')`, so it is never an uncalled entry point). Before this, IHP's own `IHP.Controller.Param.param` shadowed the catalog when IHP scanned itself, and a request parameter reaching `Process.system` through a helper was not reported (four lost true positives on the fresh set). A test stub named like a catalog module gets the same treatment, which is why the html-text remediation test keeps its scotty stub out of the scan. Pinned both ways in `test/haskell/haskell-taint-shapes.test.js`, with the three shapes below.
69
+ - **Graph and SCA items examined and NOT built (0.195.17; each premise of the plan turned out false or a contract change).** (1) CWE-78/88/94 sink nodes stay `unsupported` in `lineage/sink-registry.js`: `SINK_CATEGORIES` is FR-201's EGRESS taxonomy, process and code execution are in-process computation (82 of 194 sink entries, `lineage/DESIGN_REGISTRIES.md` section 9), and a new category is a signed cross-language contract change touching the schema, 50 references and the frontend parity fixture, not a Haskell improvement. (2) `findingRefs` is empty for every language, and a sink NODE has `location: null` by design (category-granular); a finding could attach to a FLOW through the sites of its path group, but the Haskell rule findings are produced after the graph is built, so it needs a post-pass over a signed artifact and flows that retain their sites. (3) `haskell-sca.js` `PACKAGE_MODULES` stays a nine-package table: a cabal `plan.json` carries units and dependencies, not module names; module lists live in the package database of the user's cabal store, outside the project, and this layer reads only explicitly supplied exact paths. Deriving them would need an opt-in package-database path. (4) `docs/capability-ledger.json` entries are all `proposed` by rule (`scripts/capability-ledger/check.mjs` fails any other status for an applicable entry): implemented status is tracked by the support registry, so CAP-009 and CAP-112 are not moved.
69
70
  - **Fixers for the real true positives (0.195.16; the count in the older "Ten fixers" bullet is now fifteen: `hs-path-contain` and `hs-xml-no-entity-substitution` are new).** `hs-process-argv` reads a `Text` command (`system (cs t)` / `(T.unpack t)` of a `<>` concatenation, on the line or in a one-line `let` of the same function, which is removed; `concatParts` splits on `<>` when no `++` is present) and keeps the unpack per argument; `spawnCommand` is recognised; `CLASS_REASONS['CWE-78']` (new) names the decision when the program is a variable (IHP's editor from an environment variable stays refused: `code --goto` is not one program name). `hs-path-contain` (`fixPathContain`, `signatureAt`): a guard equation before the function's first equation, `| ".." \`elem\` splitDirectories p || isAbsolute p = pure Nothing`, only for exactly one String/FilePath parameter and a `Maybe` result (bare or under one monad); the shape is the one `haskell-guards.js` already classifies as a traversal check, so the rescan passes on semantics; effective kind `path-contain` (one added equation, the guard present). `hs-tls-validate` gains `fixOpenSslVerify`: `contextSetVerificationMode ctx VerifyNone` as a do-block statement becomes `contextSetDefaultVerifyPaths ctx` + `(VerifyPeer True True Nothing)` (HsOpenSSL 0.11.7.11), MITIGATION because the host name is not checked (`enableHostnameValidation` needs the connection object); effective kind `tls-openssl`. `hs-log-redact` no longer rewrites inside a string literal (the guide's captured example had shown `" "[REDACTED]"="`). `hs-xml-no-entity-substitution` (`fixXmlEntities`): `withSubstDTDEntities yes|True` to `no|False`, FULL, effective kind `hxt-entities`. `hs-pbkdf2-low-iterations` findings are routed to `hs-password-kdf-migrate` (the old count becomes the legacy verifier). Tests: `haskell-remediation-text-command.test.js`, `haskell-remediation-path-contain.test.js`, `haskell-remediation-0195-16.test.js`.
70
71
  - **`hs-random-os-entropy` v2 (since 0.195.16; `test/haskell/haskell-remediation-entropy.test.js`).** `haskell-fix.js` `fixRandomLine`: three shapes from the real-code true positives, `replicateM n (randomRIO (c1, c2))` (n bytes mapped into the character range), `randomRIO (lo, hi)` with integer literals (8 bytes decoded big-endian, reduced into the range), `(take n . map f . randomRs (lo, hi)) <$> newStdGen` (n bytes reduced and mapped). The reduction is a modulus (bias stated in the consequences). `System.Entropy` is imported when missing, so the patch is a MITIGATION (the operator declares `entropy`) and FULL only when the file already imported it; the same relaxation applies to the older `name = randomIO` shape. The unused System.Random names are dropped from the import. Effective-check kind `random-line` (the edited line, located by content after the import removal may have moved it, reads `getEntropy n` and calls no System.Random name). Verified on the three real sites (scotty, gitit, hackage-server) with `entropy` installed for GHC 9.4.8 on the measuring host.
71
72
  - **Rule precision from semantics (since 0.195.15; `test/haskell/haskell-rule-precision.test.js`).** `haskell-security-rules.js`: a partial list function (`NONEMPTY_TOTAL`) on a list that is a cons, a non-empty literal, or a concatenation with one of those (`nonEmptyList`) is total and not reported; the conditions a function tests include the first argument of `when`/`unless`/`guard` (`COND_COMBINATOR`), not only `if` nodes; a comparison of a list's LENGTH with a number (`case length xs of 3`, `length xs == 2`; `LENGTH_CALL`, `lengthVars` for the scratch variable the IR puts a case scrutinee in) tests the list. `hash x :: Digest MD5` / `hashlazy x :: Digest SHA1`: `haskell-syntax.js` keeps a `Digest <algo>` annotation as a `typed` node (`DIGEST_TYPE`), `haskell-ir.js` returns the inner call stamped `hs.digest`, and `hs-weak-hash` treats it like `hashWith MD5`. `hs-unsafe-io-boundary` is already lowered to `info` for every site by `engine.js` (`originalSeverity` kept), so no shape rule was added for it.
@@ -75,6 +76,7 @@ or a capability.** Product-facing guides: `docs/guides/haskell.md`, `docs/guides
75
76
  - **Shapes from the real code behind published advisories (since 0.195.12; `bench/cve-replay/real-haskell-advisories.md`, pinned in `test/haskell/haskell-real-advisory-shapes.test.js`).** `haskell-syntax.js`: a record pattern accepts whitespace before its brace (`Con {..}`; a `{` after a constructor inside a pattern is always a record pattern), a top-level `|` after an `of`, a `\case` or a multiway `if` inside a list literal is that construct's guard and not a comprehension bar, a `$(..)`/`$$(..)`/`$name` in prefix position is one `splice` token when the module's LANGUAGE pragma enables TemplateHaskell (`TH_PRAGMA`; a cabal `default-extensions` entry is not seen, a stated limit) and `aexp` reads it as an opaque `{t:'splice'}` atom (the IR lowers it to an unknown value without a lowering-gap diagnostic, the parser's `th-splice` boundary already discloses it), and `var@Pat = ..` in a declaration is a pattern binding, not a function named by the variable. `haskell-parser.js`: the Hamlet replacement closes its parenthesis at the `|]` (it used to go after the last `}`, a newline when the interpolation ended its line, so the paren was lost and the function failed to parse). `haskell-ir.js`: `fieldFunctionCandidates(con, field)` (the project class) indexes the function values every construction of `con` stores in `field` (`Con { f = g }`, `Con{..}` with `f <- pure g` / `f = g` in the building clause, `case`/`if` branches, `pure`/`return` peeled, `const <$> e` / `fmap const e` / `const e` as `ignores`; a lambda or anything else is `opaque`); a record-pattern-bound variable remembers its field (`{kind:'local', field:{con,name}}`, set in `bindPattern`) and `applyFieldCandidates` lowers the application as the union over the candidates (names applied in the module of the construction, `ignores` as an unknown value not derived from the arguments), only when NO candidate is opaque. `dataflow/engine.js` `_nestedSinkFindings` walks the elements of a Haskell tuple or constructor `array` (`hs.tuple`/`hs.con`) like arguments, so a sink built inside `return (a, Con (sink ..))` is a site. `haskell-guards.js` `waiHtmlContext` returns `redirect` for a header list with `Location` and no content type and the finding is lowered to info (`redirect-response-body`). `haskell-models.js`: blaze-builder's HTML escapers are xss sanitizers (names read from the Hackage index, `tested: ['0.4.4']`). `posture/corpus-match.js` scores neither a `proven-*` finding nor an `info` finding with a `guardNote` (see `bench/cve-replay/CONTRIBUTING.md`).
76
77
  - **Mutable cells, instance dispatch, functions in record fields (since 0.195.11).** `haskell-ir.js`: `CELL_OPS` lowers `writeIORef ref v` (MVar, TVar, STRef alike) to an assignment of the cell's local variable, a read to that variable, `modifyIORef ref f` to `ref = f ref` (`lowerCellOp`; only a cell held in a local variable, never across functions, disclosed in the guide). `dispatchInstance`: a class-method call with a constructor argument of a type that has exactly one visible instance resolves to that instance method (`hs.status: 'resolved'`, `hs.dispatchedOn`); instance functions are now named `Mod.$inst.Class.Type.method` (`instanceMeta`, `instanceTypeOf` reads the instance head) so two instances of one class resolve to different functions by name, which the dataflow engine needs because it resolves callees by name. `heldFieldFunction`: `exec ops x` with `ops` a top-level constant record literal applies the field's expression (lambda or name), lowered in the constant's module with the arguments lowered in the caller's. The cell and STM names are in `HS_RULE_APIS` so wildcard imports qualify them.
77
78
  - **IR idioms that must keep the action.** `m >>= \_ -> k` and `f <$> m` RUN `m` even when the lambda ignores its result (`betaReduce`, `via`); `maybe d f m` / `either f g e` are a two-way branch on the scrutinee (so the scrutinee is still examined); `\case` applied by `>>=` is a case run in place; `foldM`, `zipWithM_`, `uncurry`, `fst`/`snd`/`const` of a written-out tuple, `filter isAlphaNum`, and a let-bound lambda (`let go = \x -> ..`, treated as `go x = ..`) are lowered so their sinks are reached. The taint engine (`dataflow/engine.js`) is Haskell-scoped for three of these: a `union` handed to a combinator or bound as a statement is a sink site in each branch (`_nestedSinkFindings`, the `assign` case), and a `member` whose object is a call (the elements of `words s`) carries its object's taint.
79
+ - **Fixed-path file content, cookie randomness by flow, whole-word marker vocabulary (0.195.18).** `haskell-trust.js` lowers a `data-input` finding to `low` (never `info`) when every origin is a file read whose first argument is a string literal, read from the call at the origin's recorded line; a path held in a name, a parameter or standard input is not judged and keeps its severity, and a library parameter spliced under a fixed directory stays `entry-parameter` (the function builds the path and lets its caller leave the directory: probed, kept). `hs-predictable-random` also fires by flow: a System.Random value that becomes the value of a cookie (the scotty-cookie setter, the web-cookie value field, or a Set-Cookie header) is reported under any function and variable names; name-based detection is unchanged. The generic known-broken-marker detector in `engine.js` matches its security vocabulary as whole words after camelCase and snake_case identifiers are split (so an author name or a hashing-typeclass import no longer counts), for every language; the self-scan, CVE-replay, mutation and layer-recall baselines did not move. Pins: `test/haskell/haskell-trust-fixed-file.test.js`, `haskell-predictable-random-flow.test.js`, `test/todo-marker-words.test.js`.
78
80
  - **A Haskell taint finding says who controls its input.** `haskell-trust.js` `applyTrustBoundary` (run in `engine.js` right after `applyHaskellGuards`) reads the origins the engine recorded (`sourceRoots`, or the evidence chain) and stamps `trustBoundary {verdict, reason, role, roots}`. Verdicts: `attacker-reachable` (request, socket, header, cookie, remote response; never lowered), `operator-controlled` (cli/env roots, and file-read roots in a command-line program; also an EMPTY origin in a command-line program, marked `inferred: true`), `api-contract` (a bare/whole-handoff parameter of an uncalled exported function reaching a process or, since 0.195.9, a path operation: CWE-78/88/22), `entry-parameter`, `data-input`, `unattributed`. `operator-controlled` and `api-contract` are lowered to `info` (never dropped; `originalSeverity`, `controls`, `guardNote` kept), the same convention `haskell-guards.js` uses. **Standard input is covered by the command-line rule only for a path operation** (since 0.195.9: a command-line program reading a file NAME from its standard input is the operator naming a file, 14 of the 29 flagged path sites on the real-code sets; `getLine >>= callCommand` in `main` stays reported, pinned in `test/haskell/haskell-trust-boundary.test.js`). **Path wrappers became `api-contract` in 0.195.9**: `withFile name WriteMode` on a caller-named file is the vulnerable shape of three synthetic development splits and of no real-code true positive, while `renderFile path = responseFile .. path` was a flagged false positive on every real project that had it; the parameter under a fixed directory (`"/srv/" ++ name`) is still `entry-parameter` and still a finding. That is a decision against the synthetic labels, taken on the real-code sets; the development-set numbers that moved are recorded in the changelog. The role is `fn.hs.entryRole` from `assignEntryRoles` in `haskell-ir.js`: `cli` (reached only from `main`s whose reachable modules import no server framework or socket library), `service`, or `library` (no `main` reaches it; an incomplete call graph therefore yields `library`, never `cli`). Pinned both ways in `test/haskell/haskell-trust-boundary.test.js`.
79
81
  - **Origins are recorded, not guessed** (`dataflow/engine.js`). The engine records a source only against the variable a source call was assigned to; three additions make an evidence chain begin at the real origin: a value derived from a source inherits its records (`_inheritSources`), a callee analysed under an entry-taint state names its tainted parameters with placeholder records (`_paramPlaceholders`) that `_mergeSummaryFindings` resolves to the sources of the arguments at the merging call site (`_resolveParams`), and a function return keeps the sources behind it (`returnSources`, followed through `from` into `sourceRoots`). A parameter annotated as a source (`hs:caller-controlled`, `hs:handler-param:*`) is recorded too (`matchAnnotationParamSources`, `source.kind: 'parameter'` so stable ids do not change). This is language-neutral (`test/dataflow-source-attribution.test.js` pins it on JavaScript). `System.IO.hGetContents`/`hGetLine`/`hGetChar` (and the Text/ByteString forms) are modelled as `file-read`, not `stdin`: they read whatever handle they are given.
80
82
  - **Reachability must survive platform variants.** An import two modules both satisfy (the Posix and Windows variants behind an undecided CPP conditional) resolves to `unknown: ambiguous-import` with its `candidates`; `haskell-ir.js` records each candidate as a reference of the caller, so neither is an "uncalled entry point" whose parameters are caller-controlled. Without it every function of the platform module that was not chosen was reported.
@@ -123,9 +123,11 @@ const exprChildren = (e) => {
123
123
  // Find the sink call at `line` with callee `callee`; returns [{ nodeId, facts, call }].
124
124
  export function findSites(fn, callee, line) {
125
125
  const sites = [];
126
+ // `callee` is a name, or a pattern when several spellings of one operation are wanted (`readFile`, `T.readFile`, `B.readFile`)
127
+ const named = typeof callee === 'string' ? (c) => c === callee : (c) => typeof c === 'string' && callee.test(c);
126
128
  const visit = (e, nodeId, facts, depth) => {
127
129
  if (!e || typeof e !== 'object' || depth > 60) return;
128
- if (e.kind === 'call' && e.callee === callee && (e.line === line || nodeLine(nodeId) === line)) sites.push({ nodeId, facts, call: e });
130
+ if (e.kind === 'call' && named(e.callee) && (e.line === line || nodeLine(nodeId) === line)) sites.push({ nodeId, facts, call: e });
129
131
  if (e.kind === 'union') {
130
132
  const bc = e.hs && e.hs.branchConds;
131
133
  e.branches.forEach((b, i) => visit(b, nodeId, bc && bc[i] ? [...facts, bc[i]] : facts, depth + 1));
@@ -135,7 +137,7 @@ export function findSites(fn, callee, line) {
135
137
  };
136
138
  const nodeLine = (id) => (fn.cfg.nodes[id] ? fn.cfg.nodes[id].line : -1);
137
139
  for (const n of nodeList(fn)) {
138
- if (n.kind === 'call' && n.callee === callee && n.line === line) sites.push({ nodeId: n.id, facts: [], call: { kind: 'call', callee: n.callee, args: n.args || [] } });
140
+ if (n.kind === 'call' && named(n.callee) && n.line === line) sites.push({ nodeId: n.id, facts: [], call: { kind: 'call', callee: n.callee, args: n.args || [] } });
139
141
  for (const k of ['source', 'value', 'cond']) if (n[k]) visit(n[k], n.id, [], 0);
140
142
  if (Array.isArray(n.args) && n.kind === 'call') for (const a of n.args) visit(a, n.id, [], 0);
141
143
  }
@@ -883,17 +883,50 @@ export function analyzeHaskellRules(files, opts = {}) {
883
883
  // A security-shaped function (`generateApiKey`, `newResetCode`) whose RESULT is built from a System.Random value: the generator was bound
884
884
  // to an ordinary name (`g <- newStdGen`, `let bytes = take 16 (randoms g)`), so neither the binding rule nor the "feeds the function" rule
885
885
  // above saw it; the value reaching the function's return is what makes it the token.
886
+ // The bindings that hold a System.Random value (`g <- newStdGen`, `let bytes = take 16 (randoms g)`), followed through the function's own assignments.
887
+ const hasRandom = (e) => { for (const x of walk(e)) if ((x.kind === 'call' && RANDOM_CALLS.has(x.callee)) || (x.kind === 'ident' && RANDOM_CALLS.has(x.name) && !(x.hs && x.hs.functionRef))) return true; return false; };
888
+ const carriers = new Set(); let seedLine = null;
889
+ for (let round = 0; round < 8; round++) {
890
+ let grew = false;
891
+ for (const n of fnNodes(fn)) if (n.kind === 'assign' && n.target && n.source && !carriers.has(n.target) && (hasRandom(n.source) || [...names(n.source)].some((x) => carriers.has(x)))) { carriers.add(n.target); if (seedLine === null || (Number.isFinite(n.line) && n.line < seedLine)) seedLine = n.line; grew = true; }
892
+ if (!grew) break;
893
+ }
894
+ const carriesRandom = (e) => !!e && (hasRandom(e) || [...names(e)].some((x) => carriers.has(x)));
895
+ // A security-shaped function (`generateApiKey`, `newResetCode`) whose RESULT is built from a System.Random value: the generator was bound
896
+ // to an ordinary name, so neither the binding rule nor the "feeds the function" rule above saw it; the value reaching the function's
897
+ // return is what makes it the token.
886
898
  if (!randomEmitted && SECURITY_FN.test(fnBase)) {
887
- const hasRandom = (e) => { for (const x of walk(e)) if ((x.kind === 'call' && RANDOM_CALLS.has(x.callee)) || (x.kind === 'ident' && RANDOM_CALLS.has(x.name) && !(x.hs && x.hs.functionRef))) return true; return false; };
888
- const carriers = new Set(); let seedLine = null;
889
- for (let round = 0; round < 8; round++) {
890
- let grew = false;
891
- for (const n of fnNodes(fn)) if (n.kind === 'assign' && n.target && n.source && !carriers.has(n.target) && (hasRandom(n.source) || [...names(n.source)].some((x) => carriers.has(x)))) { carriers.add(n.target); if (seedLine === null || (Number.isFinite(n.line) && n.line < seedLine)) seedLine = n.line; grew = true; }
892
- if (!grew) break;
893
- }
894
- const ret = fnReturns.find((n) => hasRandom(n.value) || [...names(n.value)].some((x) => carriers.has(x)));
899
+ const ret = fnReturns.find((n) => carriesRandom(n.value));
895
900
  if (ret) emitRandom(ret.line || seedLine || fn.line, { detail: `a System.Random value reaches the result of \`${fnBase}\``, evidence: { function: fnBase, via: [...carriers].slice(0, 4) } });
896
901
  }
902
+ // By flow, whatever the names are: a System.Random value that becomes the VALUE of a cookie (a Set-Cookie header written by hand, a
903
+ // header setter, scotty-cookie's `setSimpleCookie`, or web-cookie's `setCookieValue` field) is a session credential a client can predict.
904
+ // The cookie's value is the sink; a random value in a cookie's other attributes (an expiry jitter) is not.
905
+ if (!randomEmitted && (carriers.size || fnNodes(fn).some((n) => [n.value, n.source, ...(n.args || [])].some((ex) => ex && hasRandom(ex))))) {
906
+ for (const n of fnNodes(fn)) {
907
+ if (randomEmitted) break;
908
+ const roots = [];
909
+ if (n.kind === 'assign' && n.source) roots.push(n.source);
910
+ if (n.kind === 'return' && n.value) roots.push(n.value);
911
+ if (n.kind === 'call') roots.push({ kind: 'call', callee: n.callee, args: n.args || [], line: n.line });
912
+ for (const root of roots) {
913
+ for (const e of walk(root)) {
914
+ if (randomEmitted) break;
915
+ let valueExpr = null; let sink = null;
916
+ if (e.kind === 'call' && typeof e.callee === 'string' && /(?:^|\.)setSimpleCookie$/.test(e.callee) && (e.args || []).length >= 2) { valueExpr = e.args[1]; sink = 'setSimpleCookie'; }
917
+ else if (e.kind === 'object' && e.hs && e.hs.update && Array.isArray(e.props)) { const p = e.props.find((x) => x && x.key === 'setCookieValue'); if (p) { valueExpr = p.value; sink = 'setCookieValue'; } }
918
+ else if (e.kind === 'call' && typeof e.callee === 'string' && /(?:^|\.)(?:setHeader|addHeader|setResponseHeader|modifyHeader)$/.test(e.callee) && (e.args || []).length >= 2) {
919
+ const k = e.args[0];
920
+ if (k && ((k.kind === 'literal' && /^set-cookie$/i.test(String(k.value))) || (k.kind === 'ident' && SET_COOKIE_HEADER.has(k.name)))) { valueExpr = e.args[1]; sink = 'Set-Cookie header'; }
921
+ } else if (e.kind === 'array' && e.hs && e.hs.tuple && e.elements.length === 2) {
922
+ const k = e.elements[0];
923
+ if (k && ((k.kind === 'ident' && SET_COOKIE_HEADER.has(k.name)) || (k.kind === 'literal' && /^set-cookie$/i.test(String(k.value))))) { valueExpr = e.elements[1]; sink = 'Set-Cookie header'; }
924
+ }
925
+ if (valueExpr && carriesRandom(valueExpr)) emitRandom(e.line || n.line, { detail: `a System.Random value becomes the value of a cookie (${sink})`, evidence: { function: fnBase, sink, via: [...carriers].slice(0, 4) } });
926
+ }
927
+ }
928
+ }
929
+ }
897
930
  }
898
931
  }
899
932
 
@@ -16,7 +16,9 @@
16
16
  // positive had that shape; a parameter spliced UNDER a fixed directory (`"/srv/" ++ name`) is still kept,
17
17
  // because the function chose the directory and lets the caller leave it
18
18
  // entry-parameter a parameter of such a function used inside a larger value the function builds: its own construction, kept
19
- // data-input standard input or a file's content in a service or a library: data the program processes, kept
19
+ // data-input standard input or a file's content in a service or a library: data the program processes, kept. When every
20
+ // origin is a read of a file at a literal path (`readFile "config/schema.dhall"`) the finding is lowered to `low`
21
+ // with the reason, since the location is the program's own; other file reads keep their severity
20
22
  // unattributed the engine recorded no origin: kept
21
23
  //
22
24
  // A command-line program is one reached only from `main`s whose reachable modules import no server framework or socket library
@@ -250,6 +252,29 @@ export function originClasses(f) {
250
252
  return { roots, classes: new Set(roots.map(classOf)) };
251
253
  }
252
254
 
255
+ const FILE_READ_CALL = /(?:^|\.)(?:readFile|readFile'|readFileUtf8|readBinaryFile|decodeFileThrow|decodeFileEither|decodeFile)$/;
256
+ const isFixedPath = (e) => !!e && e.kind === 'literal' && typeof e.value === 'string' && e.value.length > 0 && !/^[a-z][a-z0-9+.-]*:\/\//i.test(e.value);
257
+
258
+ /**
259
+ * Is every file-read origin of this finding a read of a file whose location the program text fixes (`readFile "config/schema.dhall"`)? Judged from
260
+ * the call at the origin's recorded line: its first argument must be a string literal. A path held in a name, built from parts or taken from
261
+ * a parameter is not judged (the read stays as reported). Content at a fixed project location is the program's own data, not something a
262
+ * request chose; it can still be written by another part of the program, so the finding is lowered to `low`, never to `info`.
263
+ */
264
+ function fixedFileReads(perFile, f, roots) {
265
+ const reads = roots.filter((r) => r.provenance === 'file-read');
266
+ if (!reads.length || reads.length !== roots.length) return false;
267
+ const chain = (f.chain || []).filter((c) => c && c.provenance === 'file-read');
268
+ const where = reads.map((r, i) => ({ file: r.file || (chain[i] && chain[i].file) || (f.source && f.source.file), line: Number.isInteger(r.line) ? r.line : (chain[i] && chain[i].line) || (f.source && f.source.line) }));
269
+ return where.every((w) => {
270
+ const ir = perFile && perFile[w.file];
271
+ if (!ir || !Number.isInteger(w.line)) return false;
272
+ let seen = false;
273
+ for (const fn of ir.functions) for (const s of findSites(fn, FILE_READ_CALL, w.line)) { seen = true; if (!isFixedPath((s.call.args || [])[0])) return false; }
274
+ return seen;
275
+ });
276
+ }
277
+
253
278
  /**
254
279
  * Stamps `trustBoundary` on every Haskell IR-TAINT finding and lowers to `info` the verdicts that name no boundary crossed
255
280
  * (`operator-controlled`, `api-contract`). Mutates the findings; returns counts.
@@ -264,6 +289,7 @@ export function applyTrustBoundary(findings, perFile) {
264
289
  let verdict;
265
290
  let reason;
266
291
  let inferred = false;
292
+ let lowToLow = false;
267
293
  if (classes.has('external')) { verdict = 'attacker-reachable'; reason = 'a request, socket, header, cookie or remote response supplies the value'; }
268
294
  else if (roots.length && [...classes].every((c) => c === 'operator')) {
269
295
  verdict = 'operator-controlled';
@@ -276,7 +302,13 @@ export function applyTrustBoundary(findings, perFile) {
276
302
  reason = f.cwe === 'CWE-22' && roots.some((r) => r.provenance === 'stdin')
277
303
  ? 'the function belongs to a command-line program (reached from a main that imports no server framework) and the file it works on is named by the person running it: on its standard input, in files it was pointed at, or in its arguments and environment'
278
304
  : 'the function belongs to a command-line program (reached from a main that imports no server framework) and the value comes from files the person running it pointed the program at, or from its arguments and environment; standard input is covered by this rule only for a path operation';
279
- } else if (classes.has('data')) { verdict = 'data-input'; reason = 'standard input or file content supplies the value'; }
305
+ } else if (classes.has('data')) {
306
+ verdict = 'data-input'; reason = 'standard input or file content supplies the value';
307
+ if (!classes.has('operator') && !classes.has('weak-param') && !classes.has('other') && fixedFileReads(perFile, f, roots)) {
308
+ reason = 'the value is the content of a file at a location the program text fixes (a literal path), not a request or a caller-supplied name; another part of the program could still write that file, so the finding is lowered to low and kept';
309
+ lowToLow = true;
310
+ }
311
+ }
280
312
  else if (classes.has('weak-param')) {
281
313
  verdict = 'entry-parameter';
282
314
  reason = 'a parameter of an exported function that nothing in the analysed code calls';
@@ -294,6 +326,13 @@ export function applyTrustBoundary(findings, perFile) {
294
326
  f.trustBoundary = { verdict, reason, role, ...(inferred ? { inferred: true } : {}), roots: roots.slice(0, 5) };
295
327
  const key = verdict.replace(/-(\w)/g, (_, c) => c.toUpperCase());
296
328
  stats[key] = (stats[key] || 0) + 1;
329
+ if (lowToLow && f.severity !== 'info' && f.severity !== 'low' && !f._trustLowered) {
330
+ f._trustLowered = true;
331
+ f.originalSeverity = f.originalSeverity || f.severity;
332
+ f.severity = 'low';
333
+ f.controls = [...(f.controls || []), { kind: 'fixed-file-source', evidenceKind: 'source' }];
334
+ f.guardNote = { kind: 'data-input', reason };
335
+ }
297
336
  if (verdict === 'operator-controlled' || verdict === 'api-contract') {
298
337
  lowerFinding(f, { kind: verdict, reason, controlKind: verdict === 'operator-controlled' ? 'operator-controlled-input' : 'api-contract-wrapper', guard: '_trustLowered' });
299
338
  }
@@ -1321,8 +1321,9 @@ Limits: value versions come from per-function reaching definitions; mutation thr
1321
1321
  `haskell-view.js` rewrites a clone of each Haskell function so a monadic source read becomes a seedable member read on the
1322
1322
  synthetic root `$hs`, seeds record-parameter sources, and treats `length`/`null` as non-content. `nix-view.js` builds
1323
1323
  field-to-store flows from `language/nix-privacy.js` (a personal-data `config.<module>.<field>` interpolated into another option;
1324
- hashed, measured or compared references are protected and make no flow). Coverage reports `unknown` for both languages: no
1325
- taint-recall tier is borrowed. See `scanner/src/language/CLAUDE.md`.
1324
+ hashed, measured or compared references are protected and make no flow). Coverage reports tier `partial` for both languages (since 0.195.17; it was `unknown`), with NO recall number: the
1325
+ only measured Haskell figure is the nine self-authored entries of `docs/METRICS.md`, and showing it as "100% measured recall" in the impact
1326
+ report would read as a real-code measurement. No taint-recall tier is borrowed. See `scanner/src/language/CLAUDE.md`.
1326
1327
 
1327
1328
  ## Runtime twin additions (PRD 2 gap closure)
1328
1329
 
@@ -38,6 +38,14 @@ export const LANGUAGE_COVERAGE_TIERS = Object.freeze([
38
38
  Object.freeze({ language: 'php', tier: 'partial', irTaintRecallPct: 52, measuredAt: '2026-08-19', source: 'docs/METRICS.md' }),
39
39
  Object.freeze({ language: 'kotlin', tier: 'partial', irTaintRecallPct: 48, measuredAt: '2026-08-19', source: 'docs/METRICS.md' }),
40
40
  Object.freeze({ language: 'cpp', tier: 'partial', irTaintRecallPct: 18, measuredAt: '2026-08-19', source: 'docs/METRICS.md' }),
41
+ // Haskell and Nix are wired into the lineage graph (haskell-view.js seeds request/record sources through the taint engine; nix-view.js
42
+ // builds field-to-store flows from the NixOS option analysis), so they are not "unknown". `partial` is the honest categorical claim: some
43
+ // flows are seen, the stated limits apply (docs/guides/haskell.md), and neither reaches `full`. NO recall number is attached, on purpose:
44
+ // docs/METRICS.md's Haskell row (9 of 9 by IR-TAINT) is a sample of nine self-authored entries, and shown as "100% measured recall" in the
45
+ // impact report it would read as a measurement of real code, which it is not (the real-code figure is precision on a fresh set, 21 of 51
46
+ // flagged sites true, bench/dag/dev-haskell). Nix has no taint recall at all: it is configuration reasoning, not a flow engine.
47
+ Object.freeze({ language: 'haskell', tier: 'partial', irTaintRecallPct: null, measuredAt: null, source: null }),
48
+ Object.freeze({ language: 'nix', tier: 'partial', irTaintRecallPct: null, measuredAt: null, source: null }),
41
49
  Object.freeze({ language: 'rust', tier: 'pattern-only', irTaintRecallPct: null, measuredAt: null, source: null }),
42
50
  Object.freeze({ language: 'solidity', tier: 'pattern-only', irTaintRecallPct: null, measuredAt: null, source: null }),
43
51
  Object.freeze({ language: 'swift', tier: 'pattern-only', irTaintRecallPct: null, measuredAt: null, source: null }),
@@ -118,7 +118,8 @@ export const CWE_MAP = Object.freeze({
118
118
  'CWE-918': Object.freeze({ category: 'external-api', status: 'modeled', why: 'SSRF sinks are outbound HTTP client calls' }),
119
119
  // X-004 / haskell-models.js: untrusted text placed in a model request BODY. The call is an ordinary outbound HTTP
120
120
  // body setter, so it is an external-api destination; whether the destination is a model provider is decided by
121
- // the AI-evidence overlay (lineage/haskell-ai.js), never by this table.
121
+ // the technical provider catalog in recipient-registry.js (the destination host, e.g. an anthropic.com or openai.com
122
+ // base URL), never by this table.
122
123
  'CWE-1427': Object.freeze({ category: 'external-api', status: 'modeled', why: 'a model request body is an outbound HTTP request body' }),
123
124
  'CWE-601': Object.freeze({ category: 'http-response', status: 'modeled', why: 'a redirect is written as a response header' }),
124
125
  'CWE-113': Object.freeze({ category: 'http-response', status: 'modeled', why: 'response splitting — the sink IS the response header writer' }),