@clear-capabilities/agentic-security-scanner 0.137.1 → 0.139.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/dist/113.index.js +2 -2
  3. package/dist/178.index.js +1 -1
  4. package/dist/384.index.js +1 -1
  5. package/dist/435.index.js +29 -1
  6. package/dist/526.index.js +2 -2
  7. package/dist/637.index.js +1 -1
  8. package/dist/agentic-security.mjs +14 -14
  9. package/dist/agentic-security.mjs.sha256 +1 -1
  10. package/package.json +10 -6
  11. package/src/dataflow/CLAUDE.md +30 -0
  12. package/src/dataflow/catalog.js +512 -14
  13. package/src/dataflow/engine.js +275 -27
  14. package/src/dataflow/summaries.js +30 -5
  15. package/src/engine.js +512 -120
  16. package/src/ir/CLAUDE.md +20 -5
  17. package/src/ir/balanced-call.js +11 -1
  18. package/src/ir/callgraph.js +34 -0
  19. package/src/ir/parser-cs.js +55 -6
  20. package/src/ir/parser-go.js +106 -2
  21. package/src/ir/parser-java.js +111 -10
  22. package/src/ir/parser-js.js +40 -0
  23. package/src/ir/parser-kt.js +194 -10
  24. package/src/ir/parser-php.js +108 -6
  25. package/src/ir/parser-py.helper.py +199 -10
  26. package/src/ir/parser-rb.js +405 -31
  27. package/src/mcp/tools.js +29 -1
  28. package/src/posture/accuracy-scorecard.js +103 -0
  29. package/src/runScan.js +5 -2
  30. package/src/sast/CLAUDE.md +1 -1
  31. package/src/sast/_auth-signals.js +141 -0
  32. package/src/sast/_comment-strip.js +80 -13
  33. package/src/sast/codegen-sink.js +110 -0
  34. package/src/sast/convention-deviation.js +235 -0
  35. package/src/sast/fastapi-hardening.js +45 -6
  36. package/src/sast/file-upload.js +29 -1
  37. package/src/sast/ownership-authz.js +245 -0
  38. package/src/sast/php.js +12 -2
  39. package/src/sast/rate-limit.js +2 -0
  40. package/src/sast/rbac-consistency.js +1 -1
  41. package/src/sast/redirect-toctou.js +167 -0
  42. package/src/sast/resource-exhaustion.js +217 -0
  43. package/src/sast/sibling-guard.js +176 -0
  44. package/src/sast/zip-slip.js +53 -2
@@ -1 +1 @@
1
- c8726408d88159f1954448e223340231893809e7cfeaa72d0fce196a39a1395c agentic-security.mjs
1
+ 6644269053578a17693b6dbb13795c1195ddf4faf6816063d8fb5e33c78bdbae agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.137.1",
3
+ "version": "0.139.0",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin — SAST, SCA (function-level reachability + CISA KEV), secrets, IaC, prompt-injection, MCP/agent-tool audit, auth/authZ deep analysis, attack chains, PoC generation, business logic, toxic-combinations scoring, SBOM, pipeline integrity, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -63,13 +63,13 @@
63
63
  "test:python": "node ../scripts/run-python-tests.mjs",
64
64
  "test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
65
65
  "test:glob": "node --test test/glob-compat.test.js",
66
- "test:sast": "node --test 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/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/reachability-missing-callgraph-data.test.js",
67
- "test:posture": "node --test 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/independent-population.test.js test/evidence-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/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.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",
68
- "test:dataflow": "node --test test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.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",
66
+ "test:sast": "node --test test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/reachability-missing-callgraph-data.test.js test/rate-limit.test.js test/auth-signals.test.js test/convention-deviation.test.js test/resource-exhaustion.test.js test/resource-exhaustion-realworld.test.js test/redirect-toctou.test.js test/redirect-toctou-realworld.test.js test/codegen-sink.test.js test/ownership-authz.test.js test/ownership-authz-realworld.test.js test/t21-precision-audit.test.js",
67
+ "test:posture": "node --test 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/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-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/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.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",
68
+ "test:dataflow": "node --test test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js",
69
69
  "test:mcp": "node --test test/mcp.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",
70
70
  "test:report": "node --test test/junit.test.js test/ci.test.js test/poc-generator.test.js test/verifier.test.js test/verifier-target.test.js test/annotator-errors.test.js test/grader-calibration.test.js test/pr-delta-gate.test.js test/vex.test.js test/report-render.test.js",
71
71
  "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",
72
- "test:lifecycle": "node --test 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",
72
+ "test:lifecycle": "node --test test/lsp-protocol-smoke.test.js test/detector-liveness.test.js test/bench-watchdog.test.js test/ci-parity.test.js test/dead-code.test.js test/no-dead-modules.test.js test/no-orphan-scripts.test.js test/no-stray-state.test.js test/tree-integrity.test.js test/stop-hook.test.js test/plugin-self-check.test.js test/skills-registry.test.js test/bodyguard.test.js test/cache-invalidator-guard.test.js test/dispatch-pre-tool.test.js test/dispatch-user-prompt.test.js test/session-start-model-capture.test.js test/sandbox.test.js test/sandbox-escape.test.js test/ci-templates.test.js test/agent-mcp-tools.test.js test/triage-command.test.js test/no-orphan-tests.test.js test/supply-command.test.js test/posture-command.test.js test/labs-command.test.js test/compliance-command.test.js test/check-doc-drift.test.js test/dist-chunks-tracked.test.js && node ../scripts/lint-command-descriptions.mjs",
73
73
  "test:eval": "node --test test/independent-eval.test.js test/realworld-recall.test.js",
74
74
  "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",
75
75
  "eval:independent": "node ../bench/independent-eval/runner.mjs",
@@ -120,7 +120,11 @@
120
120
  "bench:independent": "node ../bench/independent/runner.mjs",
121
121
  "bench:independent:fetch": "node ../bench/independent/fetch.mjs",
122
122
  "bench:independent:mine": "node ../bench/independent/mine.mjs",
123
- "bench:independent:materialise": "node ../bench/independent/materialise-cli.mjs"
123
+ "bench:independent:materialise": "node ../bench/independent/materialise-cli.mjs",
124
+ "bench:independent:why-missed": "node ../bench/independent/why-missed.mjs",
125
+ "bench:independent:materialise-new": "node ../bench/independent/materialise-new.mjs",
126
+ "bench:independent:deep": "node ../bench/independent/runner.mjs --deep",
127
+ "test:ci-parity": "CI=true GITHUB_ACTIONS=true node --test test/backward-default.test.js test/ci-parity.test.js test/container-taint.test.js test/deep-taint.test.js test/engine-recall.test.js test/engine-reconnect.test.js test/gate-verdict-cache.test.js test/ignore-pragma.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/py-annotation-sources.test.js test/runscan-deep-option.test.js test/sanitizer-typed-flow.test.js"
124
128
  },
125
129
  "author": "Ross Young <ross@clearcapabilities.com>",
126
130
  "license": "PolyForm-Internal-Use-1.0.0"
@@ -15,6 +15,7 @@ Layer-2 taint engine. Walks the Layer-1 IR (`../ir/`) with field-sensitive forwa
15
15
  - **The gate runs twice**, for the same reason `_applyIgnorePragmas` does: pass 1 in `engine.js` runs ~2300 lines before deep-mode IR findings are appended, so pass 2 runs immediately after `finalFindings.push(...irFindings)` and is scoped to `irFindings` only (re-running `annotateProofGate` over already-gated findings would demote them twice).
16
16
  - Guarded by `bench/mutation/runner.mjs` (`npm run bench:mutation:check`), which scores verdict-flip correctness over metamorphic rewrites (verdict must hold) and adversarial near-misses (verdict must flip). Removing the family check makes it exit non-zero.
17
17
  - **`match.receiver`** (optional, regex source string) constrains a call to call sites whose receiver chain has a matching segment. Callee matching is by *bare name*, so `callee: 'write'` otherwise matches `process.stdout.write` and `fh.write` as readily as `document.write`. Works for both sinks (via `matchSinkOrSanitizer`) and sources (via `matchSource`'s CALL-source path). `match.receiverBase` is an optional, independent second pattern (both `_receiverAllowed` checks must pass) — use it when `receiver` alone would still match an unrelated same-named local, e.g. `py-flask-args-get` requires *both* a property segment (`args`/`form`/`headers`/…) *and* a `request`/`req` segment, so `args = parse(); args.get(x)` doesn't fire just because some catalog entry happens to be named `get`.
18
+ - **`match.receiverExclude`** (optional, regex source string) is the only NEGATIVE form — `receiver`/`receiverBase` can only ever REQUIRE a match, which cannot express "fire on a bare call, but not on THIS receiver." `py-compile` needs exactly that: Python's dangerous `compile()` builtin (`compile(source, filename, mode)`, equivalent to `eval` when followed by `exec`) is called BARE, but the catalog's bare-name last-segment fallback (`_calleeIndexHits`) makes the same key also match `re.compile(pattern)` — an ordinary regex compiler with no code-execution capability. `receiverExclude: '^re$'` fixes it. Checked FIRST, independently of `receiver`/`receiverBase`, and a bare call (no receiver segments) can never match an exclude pattern — so setting only `receiverExclude` never blocks the no-receiver case it exists to preserve. Found via this project's own self-scan once `_calleeReceiverTainted` (below) started correctly propagating taint through `.get()`-style calls on a tainted dict/file-read chain — the sink was always mismatched; the engine fix just gave taint a path to reach it. Guarded by `test/catalog-code-injection.test.js`.
18
19
  - **`language`** is enforced by `_languageAllowed` (Phase 2) via a table (`_LANG_EXT` in `catalog.js`) covering all nine catalog languages — `js`, `py`, `cs`, `kt`, `go`, `php`, `rb`, `java` by literal regex kept byte-identical to `ir/index.js`'s own dispatch regexes (pinned by `test/phase2-scoping.test.js`, which compares `.source` directly, not just "does this regex literal appear somewhere in the file"), and `cpp` by delegating to `ir/parser-cpp.js`'s `cppExtRe()` so it can't drift from the parser. A language with no table entry stays permissive (matches any file) — additive by design, so it can't regress a language before its entry exists.
19
20
  - **Path feasibility.** Constant-folds `if` conditions to prune unreachable branches.
20
21
  - **Per-flow source attribution.** Sources reported on a finding are the ones actually reaching the sink argument (via free-var matching in the sink expression), NOT the first source the worklist happened to see. Premortem-derived.
@@ -24,6 +25,17 @@ Layer-2 taint engine. Walks the Layer-1 IR (`../ir/`) with field-sensitive forwa
24
25
  - **Mutated-parameter taint at call sites.** `engine.js` consults `SummaryCache.applyAtCallSite` at both assign-from-call and plain-call sites: a callee that mutates a param (e.g. `Object.assign(target, tainted)`, `_.merge`) taints the caller's argument variable. Covered by `test/interproc-k2.test.js`.
25
26
  - **Higher-order taint flow.** `_higherOrderInvocations` recorded during `analyzeFunction` are consumed in `runTaintEngine`: the callback is resolved, analyzed with a tainted parameter, and its findings merged back into the caller (`_via: 'higher-order'`, capped at `HO_CAP`). Covered by `test/closure-capture.test.js` and `test/phase6-taint.test.js`.
26
27
  - **Recursion via fixed point.** `runTaintEngine` runs a multi-pass fixed-point loop (`MAX_FP_ITERS=3`) until the summary cache stabilizes, so recursive cycles and call chains converge instead of under-approximating on a single pass.
28
+ - **Receiver taint on a call, independent of its arguments (`_calleeReceiverTainted`).** `exprTaint`'s `case 'call'` used to only look at `argsTainted` (the call's own args) and `nestedTainted` (the resolved callee's OWN return-taint summary, via the call graph) — neither sees `tainted.toString()`, `tainted.getBytes()`, `tainted.trim()`: no relevant args, and a bare method name like `toString` never resolves as a function in the call graph. `_calleeReceiverTainted` handles the two distinct `callee` shapes this codebase's IR frontends produce: `parser-js.js` (Babel) emits a structured `{kind:'member', object, prop}` node, so the receiver is `callee.object`, checked via a normal `exprTaint` recursion; the five hand-rolled parsers (`parser-cs.js`/`parser-go.js`/`parser-kt.js`/`parser-php.js`/`parser-rb.js`) plus `parser-java.js` and `parser-py.js` instead emit a flat, dot-joined STRING callee (`"cmd.toString"`) — there is no sub-expression to recurse into, so the receiver is recovered by slicing off everything after the last `.` and checking that prefix directly against the access-path lattice via `isCoveredBy` (this also handles a longer chain correctly, e.g. `"req.session.user.toString"` → receiver `"req.session.user"`). Deliberately propagates for ANY call on a tainted receiver, not just no-arg ones (`tainted.replace(a,b)`'s receiver taint matters as much as `tainted.toString()`'s) — already OR'd with `argsTainted`/`nestedTainted`, so it only widens recall. Guarded by `test/callee-receiver-taint.test.js`.
29
+ - **`literalSkeletonMatchesFamily`'s CWE-78 branch no longer requires a shell metacharacter in the STATIC portion of a tainted concat.** The SQL (CWE-89/943) and HTML (CWE-79) branches make sense — their sinks (a generic `.raw()`/`.query()` call, an `innerHTML` assignment) can legitimately carry non-SQL, non-HTML strings, so requiring the static skeleton to look SQL-/HTML-shaped is a real precision gain. Command injection's sink catalog has no such ambiguity: every CWE-78 entry (`os.system`, `subprocess.call`, `Runtime.exec`, `child_process.exec`, …) is an unambiguous shell-execution API, so requiring a metacharacter in the STATIC prefix gets the vulnerability backwards — the textbook shape `exec("ping " + host)` has the attacker supply the metacharacter via the TAINTED value, not the template, so the static prefix is legitimately metacharacter-free in the overwhelmingly common case. Confirmed this was blocking `CVE-2016-10033-java-cmdi`'s exact real-world shape and, per this PRD's Tier 3 command-injection audit, a large fraction of the whole command-injection family across every language. `_SHELL_META` was removed entirely (the CWE-78 branch now always returns `true`).
30
+ - **`match.requireLiteralArg` (catalog.js) — a precision gate for sinks whose danger depends on a SIBLING argument's literal value, not the tainted one.** `argIndex: 'all'`/`argIndex: N` alone can only ask "is ANY/THIS arg tainted", not "is the invocation FORM itself dangerous". Go's `exec.Command(path, args...)` and C#'s `Process.Start(fileName, arguments)` are only actually shell-interpreted (so a metacharacter in a tainted arg matters) when the first arg is a literal shell interpreter (`/bin/sh`, `bash`, `cmd.exe`) — the array-execve / `ProcessStartInfo.ArgumentList` forms never invoke a shell at all, so a tainted arg there is safe regardless of content. `{index, pattern}` requires `argExprs[index]` to be a LITERAL matching `pattern`; fails CLOSED on precision (a non-literal arg — we can't statically know its value — does NOT satisfy the requirement), same direction every other precision annotator in this codebase takes when evidence is unavailable. Used by `go-os-exec-command`/`go-exec-command-{output,run,combinedoutput,start}` and `cs-process-start`.
31
+ - **`_nestedSinkFindings(argExprs, state, callContext, line)` (engine.js) — sinks nested inside another call's own argument.** Every other sink check in this file operates at CFG-NODE granularity: `case 'call'`/`'assign'`/`'return'` each check the node's own top-level call expression against the catalog, but a sink written as an ARGUMENT to a different call — `render(File.read(tainted))`, `response.send(URI.open(tainted).read())` — was invisible, because the outer call (`render`, `.send`) is what the CFG node names, and the inner call never gets its own independent catalog check. This was a real, previously-documented limitation (see the Kotlin/Ruby/C#/Go rows in `../ir/CLAUDE.md` — found independently via several languages' corpus work before being fixed here, in the engine, not per-language). `_nestedSinkFindings` recursively walks every argument list looking for nested `call`-kind expressions and runs the SAME catalog match (`_matchCallCatalog` + `_sinkFindingsForCall`) against each one independently, then recurses into ITS args too (so a triple-nested call is still covered). Wired into all three call-bearing CFG node kinds: `case 'call'` (its own top-level args), `case 'assign'` (when the RHS is itself a call), and `case 'return'` (when the returned value is a call). Purely additive — findings from the outer call's own top-level check are unaffected; this only adds findings the engine previously missed entirely. Guarded by `test/catalog-path-ssrf-p3.test.js` (2 dedicated cases: fire + precision).
32
+
33
+ - **Container / collection-element taint (PRD T3.3).** A tainted value written INTO a container makes the container tainted, so a later read of any element reaches the sink. Three distinct mechanisms, each added only after a probe showed it was genuinely missing (JS arrays already worked via the R4 rule — `push`/index-write/`concat`/`join` — and `arr.map(cb)` via the higher-order path):
34
+ - **Unknown-key writes widen to the container.** `parser-js.js` lowers a computed write with a non-literal key (`bag[k] = tainted`) to access path `bag.*`, and as that parser's own comment states, `'*'` is a LITERAL property name, not a match-anything token. Since `isCoveredBy` only propagates DOWN from a prefix, `bag.*` covered nothing and a later read of `bag.anything` was unreachable from the write. The key is statically unknown, so the write may have landed on any property and the sound abstraction is the container itself. Implemented once in `_addPathAliasAware` (not at the seven `addPath(…, target)` call sites), scoped to a TRAILING `.*` — an interior wildcard (`bag.*.inner`) still names a definite final property and does not justify tainting the root.
35
+ - **Keyed collections.** `Map.set` / `Set.add` join the mutator list; they are writes exactly as `Array.push` is. The matching read (`m.get(k)`) already propagated via `_calleeReceiverTainted` once the receiver was tainted.
36
+ - **`__setitem__` is in the mutator list, and is not a typo.** `parser-py.js` lowers a Python subscript ASSIGNMENT (`bag['k'] = v`, `arr[i] = v`) to a CALL node with the flat callee `bag.__setitem__` — not to an assign node with a member target — so it arrives at the mutator rule rather than the assign path. Verified by dumping the IR rather than inferred. The matching read `bag['k']` lowers to access path `bag.[]`, which the tainted receiver prefix already covers. Anyone extending container support for a new language should dump the CFG first: the frontends disagree about which construct is a call and which is an assignment.
37
+ - **Flat-string callees — the one that mattered most.** The mutator rule tested `callee.kind === 'member'`, a shape ONLY `parser-js.js` (Babel) emits. The seven hand-rolled parsers emit a flat dot-joined string (`"items.append"`), so container writes in Python/Ruby/PHP/Go/Java/C#/Kotlin never matched at all. Same frontend duality `_calleeReceiverTainted` documents. This was invisible from the outside because a PY-SAST pattern rule caught the same sink, so the taint-layer miss looked like a detection.
38
+ - **Measured honestly:** `bench:layer-recall` is UNCHANGED at 116/215 after this landed — the corpus holds no container-shaped entries, which is exactly why `TAINT_RECALL_80PCT_PRD.md` closed at 83.9% "without Tier 5". This is capability proven by targeted tests (`test/container-taint.test.js`, including two precision cases that must NOT fire), not a measured recall gain. Do not quote it as one. Self-scan held at 427 with zero new findings across ~700 files.
27
39
 
28
40
  ## Scope — what we still do NOT model (today)
29
41
 
@@ -33,6 +45,23 @@ Layer-2 taint engine. Walks the Layer-1 IR (`../ir/`) with field-sensitive forwa
33
45
 
34
46
  ## Precision: centralized SSRF/path guard recognition
35
47
 
48
+ > **A finding whose CLAIM is the guard's presence must be exempted, or the filter
49
+ > reads the evidence as the refutation.** `dropGuardedFindings` drops a CWE-22
50
+ > finding when the window around it contains a containment guard. For the
51
+ > `sibling-guard-omission` family — "this function applies `checkRelativePath` to
52
+ > `x.NewName` and not to its sibling `x.SrcName`" — that window ALWAYS contains a
53
+ > guard, because the guard's presence on the sibling is the entire finding. The
54
+ > filter therefore deleted 100% of that family, structurally, and no amount of
55
+ > detector work could ever have surfaced one.
56
+ >
57
+ > Found while writing the rule: it fired correctly when called directly and
58
+ > produced nothing through `runScan`, the same signature as the `rate-limit.js`
59
+ > defect. The exemption is keyed on `f.family`, deliberately narrow — the window
60
+ > heuristic is doing its job for every other CWE-22 emitter and was not loosened.
61
+ > Any future rule that asserts "the control exists but is not applied HERE" needs
62
+ > the same exemption, and should be added to that list rather than by widening the
63
+ > guard patterns.
64
+
36
65
  `engine.js` `dropGuardedFindings(findings, fc)` runs after all detectors and drops a CWE-918 (SSRF) finding when the sink window has a host allow/deny check (deny/allow-list, `getHost`/`hostname` comparison, RFC1918/`169.254.169.254` prefix check, `ipaddress`/`getaddrinfo`/`ssrf-req-filter`), or a CWE-22 (path) finding when the window has a containment guard (`basename`/`GetFileName`/`secure_filename`/`send_from_directory`, or canonicalize+`startsWith`). It's the single source of truth so every emitter (regex, structural, per-language flow, PY-SAST, CSHARP, GO) is treated uniformly. The window is **comment-stripped** (a "no allow-list / 169.254…" vuln comment must not read as a guard). Opt out: `AGENTIC_SECURITY_NO_GUARD_RECOGNITION=1`.
37
66
 
38
67
  ## Entry points
@@ -54,6 +83,7 @@ Layer-2 taint engine. Walks the Layer-1 IR (`../ir/`) with field-sensitive forwa
54
83
  ## Gotchas
55
84
 
56
85
  - **Path attribution.** If you're adding a sink to the catalog, set `argIndex` carefully. `'all'` means "any tainted arg fires"; a numeric index pinpoints THE arg whose taint matters. Wrong here → noisy findings with confused traces.
86
+ - **A member-WRITE sink (`x.prop = tainted`, `argIndex: 'rhs'`) MUST use `match.object: '_any_'`.** `matchMemberWriteSink` (catalog.js) indexes every write-sink entry under a fixed `_any_.<prop>` key and applies object-specificity AFTERWARD via the optional `match.receiver` regex (checked against the dotted-prefix segments before the final prop) — the same shape `react-dangerouslySetInnerHTML`/`js-koa-ctx-body` use. An entry written with a literal object name (`object: 'ctx'`) is indexed under a completely different key and is silently never found — confirmed by writing `js-koa-ctx-body` with `object: 'ctx'` first, which never fired, with no error anywhere. This is the write-sink analog of `match.type: 'call'` entries always keying off bare callee name.
57
87
  - **Cache invalidation.** `SummaryCache` is in-memory per-scan. Cross-scan persistence lives in `incremental.js` (FR-incremental) but it's behind a separate flag. Don't conflate the two.
58
88
  - **Recursion.** The cache returns a bottom summary (`_recursive: true`) when it hits a function already on the stack. The engine relies on fixed-point iteration to refine, and `runTaintEngine` does run a multi-pass loop (`MAX_FP_ITERS = 3`, `engine.js`) — this gotcha previously claimed a single pass, which contradicted the "Recursion via fixed point" bullet above and was stale. Cycles that have not converged within 3 iterations still under-approximate.
59
89
  - **`AGENTIC_SECURITY_BLIND_BENCH=1` does NOT disable the deep engine.** It gates the bench-shape detectors; `_deepRequested` in `engine.js` keys on `AGENTIC_SECURITY_DEEP === '1'` alone, so deep mode runs regardless. (This bullet previously claimed the opposite — verified false while building `bench/layer-recall/`, where setting `BLIND_BENCH=1` left taint recall unchanged at 20/210.) To turn the taint engine off, set `AGENTIC_SECURITY_DEEP=0`.