@clear-capabilities/agentic-security-scanner 0.150.1 → 0.151.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 (89) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/bin/agentic-security.js +73 -10
  3. package/dist/1122.index.js +16 -16
  4. package/dist/1208.index.js +23 -0
  5. package/dist/1301.index.js +3 -3
  6. package/dist/1379.index.js +3 -3
  7. package/dist/1444.index.js +3 -3
  8. package/dist/1660.index.js +1 -1
  9. package/dist/1700.index.js +5 -5
  10. package/dist/1905.index.js +12 -12
  11. package/dist/1920.index.js +10 -10
  12. package/dist/2238.index.js +4 -4
  13. package/dist/2271.index.js +3 -3
  14. package/dist/2432.index.js +8 -8
  15. package/dist/2659.index.js +1 -1
  16. package/dist/2826.index.js +2 -2
  17. package/dist/2830.index.js +3 -3
  18. package/dist/2923.index.js +1 -1
  19. package/dist/3001.index.js +4 -4
  20. package/dist/3117.index.js +4 -4
  21. package/dist/3180.index.js +9 -9
  22. package/dist/3276.index.js +1 -1
  23. package/dist/3415.index.js +1 -1
  24. package/dist/3499.index.js +1 -1
  25. package/dist/3518.index.js +4 -4
  26. package/dist/3736.index.js +7 -7
  27. package/dist/3839.index.js +4 -4
  28. package/dist/4113.index.js +441 -418
  29. package/dist/4265.index.js +4 -4
  30. package/dist/4384.index.js +8 -3
  31. package/dist/4399.index.js +29 -3
  32. package/dist/4547.index.js +2 -2
  33. package/dist/4863.index.js +6 -6
  34. package/dist/4970.index.js +2 -2
  35. package/dist/5051.index.js +15 -15
  36. package/dist/5144.index.js +4 -4
  37. package/dist/5333.index.js +8 -8
  38. package/dist/5343.index.js +2 -2
  39. package/dist/5350.index.js +6 -6
  40. package/dist/5561.index.js +1 -1
  41. package/dist/5637.index.js +10 -5
  42. package/dist/5724.index.js +21642 -0
  43. package/dist/5756.index.js +99 -489
  44. package/dist/5830.index.js +3 -3
  45. package/dist/6257.index.js +21 -8
  46. package/dist/6626.index.js +5 -5
  47. package/dist/6662.index.js +1 -1
  48. package/dist/6675.index.js +4 -4
  49. package/dist/6730.index.js +10 -10
  50. package/dist/6829.index.js +4 -4
  51. package/dist/6944.index.js +2 -2
  52. package/dist/6994.index.js +5 -5
  53. package/dist/7039.index.js +83 -19
  54. package/dist/7178.index.js +11 -6
  55. package/dist/7227.index.js +3 -3
  56. package/dist/7552.index.js +2 -2
  57. package/dist/7709.index.js +2 -2
  58. package/dist/7838.index.js +1037 -0
  59. package/dist/8218.index.js +4 -4
  60. package/dist/8476.index.js +4 -4
  61. package/dist/8513.index.js +8 -8
  62. package/dist/8520.index.js +1 -1
  63. package/dist/8752.index.js +4 -4
  64. package/dist/9207.index.js +2 -2
  65. package/dist/9220.index.js +5 -5
  66. package/dist/9390.index.js +6 -6
  67. package/dist/9503.index.js +2 -2
  68. package/dist/9560.index.js +3232 -0
  69. package/dist/957.index.js +4 -4
  70. package/dist/9801.index.js +1 -1
  71. package/dist/9824.index.js +5 -5
  72. package/dist/agentic-security.mjs +23 -23
  73. package/dist/agentic-security.mjs.sha256 +1 -1
  74. package/package.json +21 -3
  75. package/src/dataflow/catalog.js +52 -0
  76. package/src/egress/redact.js +1 -1
  77. package/src/engine.js +13 -2
  78. package/src/lineage/source-registry.js +8 -0
  79. package/src/llm-validator/agent-loop.js +85 -5
  80. package/src/llm-validator/fix-proposal.js +16 -3
  81. package/src/llm-validator/model-capabilities.js +26 -1
  82. package/src/llm-validator/model-probe.js +46 -8
  83. package/src/llm-validator/ollama-provider.js +26 -1
  84. package/src/llm-validator/oom-feedback.js +69 -0
  85. package/src/llm-validator/providers.js +50 -0
  86. package/src/llm-validator/redact.js +177 -11
  87. package/src/posture/deterministic-fix.js +11 -0
  88. package/src/report/index.js +14 -3
  89. package/src/sast/java-ast-folding.js +18 -2
@@ -1 +1 @@
1
- 188a70d75f1a823e67422f8af02d42d14bc8dd476bf66136e02d6e59abc51f4d agentic-security.mjs
1
+ 205f60e669498ecd7e51d8e4849419dcbbf4afd6a1d55504c8ac06ab77458327 agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.150.1",
3
+ "version": "0.151.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",
@@ -66,10 +66,11 @@
66
66
  "test": "node ../scripts/run-unit-tests.mjs && AGENTIC_SECURITY_CPP_DATAFLOW=1 node --test test/cpp-dataflow.test.js && node --experimental-test-module-mocks --test test/fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection-k8s-admission.test.js && node --experimental-test-module-mocks --test test/lineage-fault-injection.test.js && npm run test:python",
67
67
  "test:python": "node ../scripts/run-python-tests.mjs",
68
68
  "test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
69
+ "test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
69
70
  "test:glob": "node --test test/glob-compat.test.js",
70
71
  "test:sast": "node --test test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/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 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",
71
- "test:posture": "node --test test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/release-workflow.test.js test/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/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/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",
72
- "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-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",
72
+ "test:posture": "node --test test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/release-workflow.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js",
73
+ "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-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",
73
74
  "test:mcp": "node --test test/mcp.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js test/mcp-dataflow-tools.test.js test/cli/governance-propose-edit.test.js test/cli/remediation-open-update.test.js test/cli/remediation-verify-reopen.test.js test/cli/dataflow-observations.test.js test/cli/federate-declare-list.test.js",
74
75
  "test:report": "node --test test/report-scan-invariants.test.js test/junit.test.js test/ci.test.js test/poc-generator.test.js test/verifier.test.js test/verifier-target.test.js test/annotator-errors.test.js test/scan-health.test.js test/finding-schema.test.js test/pipeline/finding-schema-provenance.test.js test/evidence-provenance.test.js test/report/provenance-output.test.js test/report/provenance-format-parity.test.js test/legacy-compat.test.js test/annotator-runner.test.js test/detector-runner.test.js test/detector-errors.test.js test/analyzer-supervisor.test.js test/cascade-worker-pool.test.js test/cascade-pool-wiring.test.js test/coverage-ledger.test.js test/coverage-ledger-completeness.test.js test/assurance-mode.test.js test/artifact-registry.test.js test/artifact-registry-completeness.test.js test/retention-policy.test.js test/state-lifecycle-report.test.js test/legal-hold.test.js test/production-feedback.test.js test/calibration-feedback.test.js test/encryption-provider.test.js test/grader-calibration.test.js test/pr-delta-gate.test.js test/vex.test.js test/report-render.test.js test/oscal-conformance.test.js test/cli-fix-apply.test.js test/apply-fix-service.test.js test/high-impact-approval-gate.test.js test/approver-registry.test.js test/suppression-exceptions.test.js test/fix-history.test.js test/producer-registry.test.js test/producer-collector.test.js test/enrichment-completion.test.js",
75
76
  "test:lineage": "node --test test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js",
@@ -128,6 +129,23 @@
128
129
  "bench:sca-replay:reachability": "node ../bench/sca-replay/reachability.mjs",
129
130
  "bench:sca-replay:fetch": "node ../bench/sca-replay/fetch.mjs",
130
131
  "bench:sca-replay:label": "node ../bench/sca-replay/label.mjs",
132
+ "bench:sard:score": "node ../bench/sard/scripts/macro-score.mjs",
133
+ "bench:sard:leakage-audit": "node ../bench/sard/scripts/leakage-audit.mjs",
134
+ "bench:sard:java": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-java-strict --blind --scramble-identifiers --json | node ../bench/sard/scripts/macro-score.mjs",
135
+ "bench:sard:csharp": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-csharp-strict --blind --scramble-identifiers --json | node ../bench/sard/scripts/macro-score.mjs",
136
+ "bench:sard:java:fast-unsafe": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-java-strict --blind --json | node ../bench/sard/scripts/macro-score.mjs",
137
+ "bench:sard:split": "node ../bench/sard/scripts/split.mjs",
138
+ "bench:sard:split:check": "node ../bench/sard/scripts/split.mjs --verify",
139
+ "bench:sard:update-baseline": "node ../bench/sard/scripts/compare-baseline.mjs --update-baseline",
140
+ "bench:sard:check-baseline": "node ../bench/sard/scripts/compare-baseline.mjs --check-baseline",
141
+ "bench:sard:holdout-update-baseline": "node ../bench/sard/scripts/holdout-check.mjs --update-baseline",
142
+ "bench:sard:holdout-check": "node ../bench/sard/scripts/holdout-check.mjs --check-baseline",
143
+ "bench:sard:smoke": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-java-strict --blind --scramble-identifiers --cwe CWE-89,CWE-78,CWE-23,CWE-80,CWE-327 --json | node ../bench/sard/scripts/macro-score.mjs",
144
+ "bench:sard:mutate": "node ../bench/sard/scripts/mutate.mjs --app sard-juliet-java-strict --cwe 89,78,23,80,327 --limit 60",
145
+ "bench:sard:verify-fixes": "node ../bench/sard/scripts/verify-fixes.mjs --app sard-juliet-java-strict --cwe 89,78,23,80,327,328 --limit 200",
146
+ "bench:sard:analyze-errors": "node ../bench/sard/scripts/analyze-errors.mjs",
147
+ "bench:sard:php-ingest": "node ../bench/sard/scripts/ingest-php.mjs",
148
+ "bench:sard:php-score": "node ../bench/sard/scripts/score-php.mjs",
131
149
  "bench:layer-recall": "node ../bench/layer-recall/runner.mjs",
132
150
  "bench:layer-recall:check": "node ../bench/layer-recall/runner.mjs --check",
133
151
  "bench:layer-recall:update-baseline": "node ../bench/layer-recall/runner.mjs --update-baseline",
@@ -217,6 +217,32 @@ export const CATALOG = [
217
217
  { kind: 'source', id: 'java-request-getReader', language: 'java', framework: 'servlet', match: { type: 'call', callee: 'getReader' }, label: 'request.getReader' },
218
218
  { kind: 'source', id: 'java-system-getenv', language: 'java', framework: 'stdlib', match: { type: 'call', callee: 'getenv' }, label: 'System.getenv' },
219
219
  { kind: 'source', id: 'java-system-getProperty', language: 'java', framework: 'stdlib', match: { type: 'call', callee: 'getProperty' }, label: 'System.getProperty' },
220
+ // Found via SARD_AGENTIC_SECURITY_PRD.md bench work: bench/sard/scripts/
221
+ // analyze-errors.mjs clustered Java false negatives by Juliet's own
222
+ // source-descriptor filename segment (evidence-based — see that script's
223
+ // header for why this is legitimate without reading raw corpus content)
224
+ // and found "console_readLine" and "database" among the largest clusters,
225
+ // spanning multiple unrelated CWEs uniformly — the signature of a missing
226
+ // SOURCE model, not a per-CWE bug. Confirmed by grep: this catalog had no
227
+ // entry for either before now. Both are standard real-world taint sources
228
+ // (interactive stdin input; a value read back out of a database result
229
+ // set — classic second-order injection), not SARD-specific.
230
+ { kind: 'source', id: 'java-io-readline', language: 'java', framework: 'stdlib', match: { type: 'call', callee: 'readLine' }, label: 'BufferedReader.readLine() / Console.readLine()' },
231
+ // `getString`/`getObject` are common enough method names on unrelated
232
+ // classes that this SHOULD be scoped to a resolved ResultSet-shaped
233
+ // receiver, and receiverTypeIn is written here expressing that intent —
234
+ // but verified (via a dedicated precision test, see java-taint-flow.
235
+ // test.js) that it is currently INERT for Java: `class-hierarchy.js`'s
236
+ // own module header states its CHA/classOfVar is JS/TS-only (built by
237
+ // walking Babel ASTs), so `_receiverTypeFor` always returns null for a
238
+ // Java call site and `_receiverTypeAllowed`'s "unknown != clean" rule
239
+ // (documented in this file, `_receiverTypeAllowed`'s own header) then
240
+ // permits the match unconditionally — identical to a bare unscoped entry
241
+ // today. Left in place (correct once Java CHA support exists, and
242
+ // documents the intended precision boundary) rather than removed, but
243
+ // do not assume it is actually gating anything yet.
244
+ { kind: 'source', id: 'java-resultset-getstring', language: 'java', framework: 'jdbc', match: { type: 'call', callee: 'getString', receiverTypeIn: ['resultset'] }, label: 'ResultSet.getString' },
245
+ { kind: 'source', id: 'java-resultset-getobject', language: 'java', framework: 'jdbc', match: { type: 'call', callee: 'getObject', receiverTypeIn: ['resultset'] }, label: 'ResultSet.getObject' },
220
246
 
221
247
  // ─── SOURCES (Annotation/Decorator-shaped) ────────────────────────────────
222
248
  // R14(a): annotation/decorator-shaped framework sources (Spring @RequestParam,
@@ -356,6 +382,20 @@ export const CATALOG = [
356
382
  { kind: 'source', id: 'php-post', language: 'php', framework: 'core', match: { type: 'global', name: '_POST' }, label: '$_POST' },
357
383
  { kind: 'source', id: 'php-cookie', language: 'php', framework: 'core', match: { type: 'global', name: '_COOKIE' }, label: '$_COOKIE' },
358
384
  { kind: 'source', id: 'php-server', language: 'php', framework: 'core', match: { type: 'global', name: '_SERVER' }, label: '$_SERVER' },
385
+ // $_SESSION and $_ENV were absent from this catalog entirely (only
386
+ // _REQUEST/_GET/_POST/_COOKIE/_SERVER were modeled) — found via
387
+ // SARD_AGENTIC_SECURITY_PRD.md bench work on the NIST SARD PHP corpus,
388
+ // whose templates use $_SESSION as one of several source stand-ins.
389
+ // $_SESSION isn't purely attacker-controlled the way $_GET is (session
390
+ // data usually originates from server-side writes), but a common,
391
+ // real-world-relevant subclass IS attacker-controlled: any value the app
392
+ // itself copies into $_SESSION from $_GET/$_POST/$_COOKIE without
393
+ // validation. Modeling it as a source (same as the existing four) is the
394
+ // conservative, general choice — flagging that class rather than missing
395
+ // it entirely, consistent with how $_COOKIE (also partly server-set) is
396
+ // already modeled.
397
+ { kind: 'source', id: 'php-session', language: 'php', framework: 'core', match: { type: 'global', name: '_SESSION' }, label: '$_SESSION' },
398
+ { kind: 'source', id: 'php-env', language: 'php', framework: 'core', match: { type: 'global', name: '_ENV' }, label: '$_ENV' },
359
399
  { kind: 'source', id: 'php-symfony-query', language: 'php', framework: 'symfony', match: { type: 'member', object: '$request', prop: 'query' }, label: '$request->query (Symfony)' },
360
400
  { kind: 'source', id: 'php-symfony-request', language: 'php', framework: 'symfony', match: { type: 'member', object: '$request', prop: 'request' }, label: '$request->request (Symfony)' },
361
401
  { kind: 'source', id: 'php-symfony-cookies', language: 'php', framework: 'symfony', match: { type: 'member', object: '$request', prop: 'cookies' }, label: '$request->cookies (Symfony)' },
@@ -444,6 +484,18 @@ export const CATALOG = [
444
484
  { kind: 'sink', id: 'php-mysqli-query', language: 'php', framework: 'mysqli', match: { type: 'call', callee: 'mysqli_query' }, argIndex: 1,
445
485
  vuln: { name: 'SQL Injection (mysqli_query)', severity: 'critical', cwe: 'CWE-89',
446
486
  remediation: 'Use prepared statements: $stmt = $conn->prepare("SELECT * WHERE id = ?"); $stmt->bind_param("i", $id);' } },
487
+ // Legacy mysql_* extension (deprecated PHP 5.5, removed 7.0) — still widely
488
+ // present in real-world PHP and, per SARD_AGENTIC_SECURITY_PRD.md bench
489
+ // work, in the NIST SARD PHP Vulnerability Test Suite corpus. Only
490
+ // php.js's same-line structural regex (`sqlInjectionStructural`) covered
491
+ // this call before; a variable assigned on one line and passed to
492
+ // mysql_query() on a later line (the dominant real-world and SARD shape)
493
+ // was invisible to the taint dataflow engine entirely. Query is argIndex 0
494
+ // (the optional link-identifier connection is argIndex 1), unlike
495
+ // mysqli_query's argIndex 1.
496
+ { kind: 'sink', id: 'php-mysql-query', language: 'php', framework: 'mysql', match: { type: 'call', callee: 'mysql_query' }, argIndex: 0,
497
+ vuln: { name: 'SQL Injection (mysql_query)', severity: 'critical', cwe: 'CWE-89',
498
+ remediation: 'The mysql_* extension is removed in PHP 7+; migrate to mysqli or PDO with prepared statements: $stmt = $mysqli->prepare("SELECT * WHERE id = ?"); $stmt->bind_param("i", $id).' } },
447
499
  { kind: 'sink', id: 'php-pdo-query', language: 'php', framework: 'pdo', match: { type: 'call', callee: 'query' }, argIndex: 0,
448
500
  vuln: { name: 'SQL Injection (PDO::query)', severity: 'critical', cwe: 'CWE-89',
449
501
  remediation: 'Use PDO::prepare with bound parameters.' } },
@@ -156,7 +156,7 @@ export function redactPayload({ text, filePath = null, scanRoot = null, taxonomy
156
156
 
157
157
  let out = text;
158
158
 
159
- const secretResult = redactSecrets(out);
159
+ const secretResult = redactSecrets(out, { filePath });
160
160
  out = secretResult.text;
161
161
  categories.secrets = secretResult.redactions;
162
162
 
package/src/engine.js CHANGED
@@ -191,7 +191,7 @@ import { annotateExploitability, detectProjectContext } from './posture/exploita
191
191
  import { applyFeedback as applyLearnedFeedback } from './posture/learning.js';
192
192
  import { validateMany as llmValidateMany, applyValidatorVerdicts } from './llm-validator/index.js';
193
193
  import { MODEL_STATUS, stageSummaryFromModelStatus } from './llm-validator/model-status.js';
194
- import { resolveProvider as resolveLlmProvider } from './llm-validator/providers.js';
194
+ import { resolveProvider as resolveLlmProvider, otherRemoteRoles as llmOtherRemoteRoles, NO_CLOUD_FALLBACK } from './llm-validator/providers.js';
195
195
  import { scanCrossLangOpenAPI } from './posture/cross-lang-openapi.js';
196
196
  import { scanCrossLangGrpc } from './posture/cross-lang-grpc.js';
197
197
  import { scanCrossLangGraphql } from './posture/cross-lang-graphql.js';
@@ -9993,12 +9993,23 @@ function _deterministicFileTimings(timings) {
9993
9993
  const status = finalFindings.llmValidatorStatus;
9994
9994
  if (status && status.counts[MODEL_STATUS.DISABLED] !== status.total) {
9995
9995
  const resolved = resolveLlmProvider({ role: 'validate' });
9996
+ // Adversarial-review finding (Ollama offline PRD premortem, 2026-09):
9997
+ // this block only ever checked the `validate` role, but printed an
9998
+ // unqualified "LLM inference was loopback-only" claim a reader could
9999
+ // reasonably take as describing the WHOLE scan — while `verify`/
10000
+ // `explain`/`fix`/`poc`/`logic`/`hunt` each have their own
10001
+ // independent per-role provider override (providers.js's `_forRole`)
10002
+ // and could genuinely be pointed at a cloud vendor at the same time.
10003
+ // `otherRolesRemote` makes that visible instead of silent.
10004
+ const otherRolesRemote = llmOtherRemoteRoles('validate');
9996
10005
  _aiAssistance = {
10006
+ scopeRole: 'validate',
9997
10007
  provider: resolved.ok ? resolved.config.provider : null,
9998
10008
  model: resolved.ok ? resolved.config.model : null,
9999
10009
  endpoint: resolved.ok ? resolved.config.endpoint : null,
10000
10010
  egress: resolved.ok ? resolved.config.egress : null,
10001
- cloudFallback: false,
10011
+ cloudFallback: !NO_CLOUD_FALLBACK,
10012
+ otherRolesRemote,
10002
10013
  stages: { validate: stageSummaryFromModelStatus(status) },
10003
10014
  };
10004
10015
  }
@@ -153,6 +153,12 @@ export const NO_PROVENANCE_OVERRIDES = Object.freeze({
153
153
  // Java / stdlib
154
154
  'java-system-getenv': 'env-value',
155
155
  'java-system-getProperty': 'env-value', // JVM system properties, env-adjacent
156
+ 'java-io-readline': 'user-input', // BufferedReader.readLine() / Console.readLine() — interactive stdin
157
+ // Java / JDBC — a value read back out of a database result set (classic
158
+ // second-order injection); `database-read` is SOURCE_CATEGORIES' own
159
+ // vocabulary for exactly this shape.
160
+ 'java-resultset-getstring': 'database-read',
161
+ 'java-resultset-getobject': 'database-read',
156
162
  // Java / Spring annotations
157
163
  'java-spring-requestparam': 'http-query',
158
164
  'java-spring-pathvariable': 'http-route',
@@ -209,6 +215,8 @@ export const NO_PROVENANCE_OVERRIDES = Object.freeze({
209
215
  'php-post': 'http-body',
210
216
  'php-cookie': 'http-cookie',
211
217
  'php-server': 'http-header',
218
+ 'php-session': 'http-cookie', // $_SESSION is cookie-backed by default, same convention as rb-rails-session
219
+ 'php-env': 'env-value',
212
220
  'php-symfony-query': 'http-query',
213
221
  'php-symfony-request': 'http-body',
214
222
  'php-symfony-cookies': 'http-cookie',
@@ -26,6 +26,7 @@ import { evaluateEgress } from '../egress/policy.js';
26
26
  import { TOOL_DEFINITIONS, TOOL_ERROR, runTool } from './agent-tools.js';
27
27
  import { getModelCapabilities } from './model-probe.js';
28
28
  import { statePath as defaultStatePath } from '../posture/state-dir.js';
29
+ import { priorOOMFor } from './oom-feedback.js';
29
30
 
30
31
  export const AGENT_LOOP_ERROR = Object.freeze({
31
32
  NOT_CONFIGURED: 'agent-loop-not-configured',
@@ -37,6 +38,35 @@ export const AGENT_LOOP_ERROR = Object.freeze({
37
38
  export const DEFAULT_MAX_TOOL_ITERATIONS = 12;
38
39
  const DEFAULT_WALL_CLOCK_TIMEOUT_MS = 5 * 60 * 1000;
39
40
 
41
+ // Adversarial-review finding (2026-09), confirmed against a real, slow
42
+ // (CPU-only) Ollama server: the wall-clock deadline used to be checked ONLY
43
+ // at the top of each iteration, never around the in-flight callOllamaChat
44
+ // itself. `docs/guides/ollama.md` tells users to raise
45
+ // AGENTIC_SECURITY_LLM_TIMEOUT_MS for a cold-loading model — that value
46
+ // flows into the PER-CALL requestTimeoutMs, which used to be entirely
47
+ // independent of this loop's own wall-clock budget. A single call already
48
+ // running when the wall clock expires would complete anyway (its own
49
+ // timeout could be minutes longer), and only the NEXT iteration's top-of-
50
+ // loop check would notice — reporting `wall-clock-timeout` after exactly
51
+ // one useful call, no matter how high the per-call timeout was raised,
52
+ // which made the documented remedy for slow models actively useless.
53
+ //
54
+ // Fix: cap the PER-CALL requestTimeoutMs at whatever wall-clock budget
55
+ // actually remains, every iteration. A slow call now gets cut off by ITS
56
+ // OWN timeout mechanism (producing the same clean `{ok:false,
57
+ // code:'ollama-timeout'}` every other caller already handles) at exactly
58
+ // the moment the wall clock would have run out anyway — never later. This
59
+ // also makes the two settings coherent for the first time: raising
60
+ // AGENTIC_SECURITY_LLM_TIMEOUT_MS now genuinely helps, as long as the loop's
61
+ // OWN budget (wallClockTimeoutMs / AGENTIC_SECURITY_LLM_AGENT_TIMEOUT_MS)
62
+ // is raised enough to give it room.
63
+ function _cappedTimeouts(baseTimeouts, remainingMs) {
64
+ if (!baseTimeouts) return { requestTimeoutMs: Math.max(1, remainingMs) };
65
+ const base = Number(baseTimeouts.requestTimeoutMs);
66
+ const capped = Number.isFinite(base) ? Math.min(base, remainingMs) : remainingMs;
67
+ return { ...baseTimeouts, requestTimeoutMs: Math.max(1, capped) };
68
+ }
69
+
40
70
  function systemPrompt(scanRoot) {
41
71
  return [
42
72
  'You are a security-scan assistant with READ-ONLY access to the scanned',
@@ -54,14 +84,49 @@ function systemPrompt(scanRoot) {
54
84
  * maxToolIterations?:number, wallClockTimeoutMs?:number}} opts
55
85
  * `statePath` defaults to posture/state-dir.js's real implementation;
56
86
  * overridable only for tests that need a fixture-scoped state dir.
87
+ * `wallClockTimeoutMs`, when not passed explicitly, falls back to
88
+ * `AGENTIC_SECURITY_LLM_AGENT_TIMEOUT_MS` — a SEPARATE setting from
89
+ * `AGENTIC_SECURITY_LLM_TIMEOUT_MS` (the per-call timeout) on purpose: the
90
+ * two used to be incoherent (raising the per-call setting alone did nothing
91
+ * for a loop that could still time out after one call), so a caller who
92
+ * genuinely needs a longer overall budget for a slow model must raise BOTH.
57
93
  * @returns {{ok:true, finalText, iterations, toolCalls, stopReason} |
58
94
  * {ok:false, code, reason}}
59
95
  */
60
- export async function runAgentLoop({
96
+ export async function runAgentLoop(opts = {}) {
97
+ const result = await _runAgentLoopCore(opts);
98
+ // Adversarial-review fix (2026-09, second pass): Round 1's original OOM-
99
+ // feedback fix only surfaced `priorOOMWarning` in `models doctor`'s
100
+ // advisory output — a user who never happens to run `doctor` would OOM
101
+ // again on the exact same model via `ask` with no warning at all, since
102
+ // `recommendAdmission` (where the warning lives) is never consulted on
103
+ // this real call path. Surface it here too, on any outcome where a real
104
+ // call was actually attempted (a pure config/capability refusal before
105
+ // any call has nothing useful to warn about).
106
+ const attemptedARealCall = result.ok || result.code === AGENT_LOOP_ERROR.FAILED;
107
+ if (attemptedARealCall) {
108
+ const resolved = resolveProvider({ role: 'hunt', env: opts.env || process.env });
109
+ const prior = resolved.ok ? priorOOMFor(resolved.config.model) : null;
110
+ if (prior) {
111
+ return {
112
+ ...result,
113
+ priorOOMWarning: `'${resolved.config.model}' has previously failed with an out-of-memory error on this machine ` +
114
+ `(${prior.count} time${prior.count === 1 ? '' : 's'}, most recently ${new Date(prior.lastAt).toISOString()}).`,
115
+ };
116
+ }
117
+ }
118
+ return result;
119
+ }
120
+
121
+ async function _runAgentLoopCore({
61
122
  goal, scanRoot, env = process.env, statePath = defaultStatePath,
62
- maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, wallClockTimeoutMs = DEFAULT_WALL_CLOCK_TIMEOUT_MS,
123
+ maxToolIterations = DEFAULT_MAX_TOOL_ITERATIONS, wallClockTimeoutMs,
63
124
  } = {}) {
64
125
  const boundedIterations = Math.max(1, Math.min(maxToolIterations, DEFAULT_MAX_TOOL_ITERATIONS));
126
+ if (wallClockTimeoutMs === undefined) {
127
+ const fromEnv = Number(env.AGENTIC_SECURITY_LLM_AGENT_TIMEOUT_MS);
128
+ wallClockTimeoutMs = Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : DEFAULT_WALL_CLOCK_TIMEOUT_MS;
129
+ }
65
130
 
66
131
  const resolved = resolveProvider({ role: 'hunt', env });
67
132
  if (!resolved.ok || resolved.config.provider !== 'ollama') {
@@ -97,15 +162,30 @@ export async function runAgentLoop({
97
162
  const deadline = Date.now() + boundedTimeoutMs;
98
163
 
99
164
  for (let iteration = 0; iteration < boundedIterations; iteration++) {
100
- if (Date.now() >= deadline) {
165
+ const remainingMs = deadline - Date.now();
166
+ if (remainingMs <= 0) {
101
167
  return { ok: true, finalText: null, iterations: iteration, toolCalls: toolCallLog, stopReason: 'wall-clock-timeout' };
102
168
  }
103
169
 
170
+ // Cap this call's own timeout at whatever wall-clock budget remains, so
171
+ // a slow call can never silently outlive the loop's overall bound (see
172
+ // the header comment above _cappedTimeouts for the incident this fixes).
173
+ const callTimeouts = _cappedTimeouts(timeouts, remainingMs);
174
+ const deadlineWasBinding = timeouts && Number(timeouts.requestTimeoutMs) > remainingMs;
104
175
  const r = await callOllamaChat({
105
176
  host: resolved.config.endpoint, model: resolved.config.model, messages,
106
- tools: TOOL_DEFINITIONS, keepAlive: oc?.keepAlive, timeouts,
177
+ tools: TOOL_DEFINITIONS, keepAlive: oc?.keepAlive, timeouts: callTimeouts,
107
178
  });
108
- if (!r.ok) return { ok: false, code: AGENT_LOOP_ERROR.FAILED, reason: r.reason || r.code };
179
+ if (!r.ok) {
180
+ // A timeout caused by the WALL CLOCK (not the operator's own per-call
181
+ // setting) is this loop doing exactly what it's supposed to, not an
182
+ // unexpected error — report it the same way the pre-flight check
183
+ // above does, rather than as a hard failure.
184
+ if (r.code === 'ollama-timeout' && deadlineWasBinding) {
185
+ return { ok: true, finalText: null, iterations: iteration, toolCalls: toolCallLog, stopReason: 'wall-clock-timeout' };
186
+ }
187
+ return { ok: false, code: AGENT_LOOP_ERROR.FAILED, reason: r.reason || r.code };
188
+ }
109
189
 
110
190
  const toolCalls = r.result.toolCalls || [];
111
191
  if (toolCalls.length === 0) {
@@ -52,7 +52,7 @@ export const FIX_PROPOSAL_ERROR = Object.freeze({
52
52
  * through the same redaction pipeline llm-validator/index.js's renderPrompt
53
53
  * uses, and is framed as data the model must never treat as instructions.
54
54
  */
55
- export function buildFixPrompt(finding, fileContent, scanRoot) {
55
+ export function buildFixPrompt(finding, fileContent, scanRoot, rejectionFeedback) {
56
56
  const sterileContent = redactPayload({ text: String(fileContent || ''), filePath: finding.file, scanRoot }).text;
57
57
  return [
58
58
  'You are a security patch-synthesis component. You PROPOSE a fix; a separate',
@@ -71,6 +71,19 @@ export function buildFixPrompt(finding, fileContent, scanRoot) {
71
71
  sterileContent,
72
72
  '--- END-UNTRUSTED-FILE-CONTENT ---',
73
73
  '',
74
+ // Adversarial-review fix (2026-09): at temperature 0 with the SAME
75
+ // prompt, a rejected patch would very likely just reproduce itself on
76
+ // retry — this is the one place cmdFix's bounded one-time retry
77
+ // (bin/agentic-security.js) feeds the deterministic gate's OWN rejection
78
+ // reason back in, so the second attempt has an actual reason to differ
79
+ // rather than repeating the first attempt's exact mistake.
80
+ ...(rejectionFeedback ? [
81
+ 'Your previous proposal for this exact finding was REJECTED by the',
82
+ 'deterministic verification gate below. Propose a DIFFERENT fix that',
83
+ 'avoids this specific problem — do not repeat the same patch:',
84
+ ` ${String(rejectionFeedback).slice(0, 500)}`,
85
+ '',
86
+ ] : []),
74
87
  'Propose a minimal, targeted fix for the finding above. Reply with ONLY a',
75
88
  'single JSON object, no other text:',
76
89
  '{"target_file": "<must exactly equal the File given above>", ' +
@@ -94,7 +107,7 @@ function validateFixResponse(obj, { file }) {
94
107
  * @returns {{ok:true, replacement, rationale, expectedSecurityEffect,
95
108
  * testsToRun, model} | {ok:false, code, reason}}
96
109
  */
97
- export async function proposeOllamaFix({ finding, fileContent, scanRoot, env = process.env }) {
110
+ export async function proposeOllamaFix({ finding, fileContent, scanRoot, env = process.env, rejectionFeedback } = {}) {
98
111
  const resolved = resolveProvider({ role: 'fix', env });
99
112
  if (!resolved.ok || resolved.config.provider !== 'ollama') {
100
113
  return {
@@ -112,7 +125,7 @@ export async function proposeOllamaFix({ finding, fileContent, scanRoot, env = p
112
125
  return { ok: false, code: FIX_PROPOSAL_ERROR.POLICY_BLOCKED, reason: decision.reason, egressDecision: decision };
113
126
  }
114
127
 
115
- const prompt = buildFixPrompt(finding, fileContent, scanRoot);
128
+ const prompt = buildFixPrompt(finding, fileContent, scanRoot, rejectionFeedback);
116
129
  const oc = resolved.config.ollama;
117
130
  const r = await callOllamaStructured({
118
131
  host: resolved.config.endpoint,
@@ -18,6 +18,7 @@
18
18
  // memory to the byte.
19
19
 
20
20
  import * as os from 'node:os';
21
+ import { priorOOMFor } from './oom-feedback.js';
21
22
 
22
23
  // PRD §12/§13 FR-1203 — non-authoritative family hint from a model name.
23
24
  // Longest/most-specific pattern first so `qwen3.5:4b` doesn't fall through to
@@ -200,7 +201,31 @@ export function evaluateMemoryAdmission({
200
201
  * `{admitted:false}` with a human-readable explanation, which callers treat
201
202
  * as "run deterministic-only" (PRD §23.4).
202
203
  */
203
- export function recommendAdmission({ profile, freeBytes, requestedContextTokens, requestedModel } = {}) {
204
+ export function recommendAdmission(opts = {}) {
205
+ const result = _recommendAdmissionCore(opts);
206
+ // Adversarial-review fix (2026-09): a memory-admission ESTIMATE that
207
+ // actually caused a real OOM (ollama-provider.js's callOllamaChat now
208
+ // records this via oom-feedback.js) used to have no way to affect a
209
+ // future admission decision for the SAME model on the SAME machine — the
210
+ // exact same "admitted: true" would repeat forever. This does not
211
+ // recalibrate the underlying estimate (that needs real hardware variety
212
+ // one machine's observed failures can't substitute for); it attaches an
213
+ // honest warning so the decision is no longer presented with unqualified
214
+ // confidence.
215
+ const chosenModel = result.model || opts.requestedModel;
216
+ const prior = chosenModel ? priorOOMFor(chosenModel) : null;
217
+ if (prior) {
218
+ return {
219
+ ...result,
220
+ priorOOMWarning: `'${chosenModel}' has previously failed with an out-of-memory error on this machine ` +
221
+ `(${prior.count} time${prior.count === 1 ? '' : 's'}, most recently ${new Date(prior.lastAt).toISOString()}). ` +
222
+ 'The memory estimate below may be optimistic for your hardware.',
223
+ };
224
+ }
225
+ return result;
226
+ }
227
+
228
+ function _recommendAdmissionCore({ profile, freeBytes, requestedContextTokens, requestedModel } = {}) {
204
229
  const p = MEMORY_PROFILES[profile];
205
230
  if (!p) return { admitted: false, reason: `unknown memory profile '${profile}'` };
206
231
 
@@ -18,10 +18,21 @@
18
18
  // CACHE KEY = Ollama version + model digest + model name (PRD §13.2 exactly).
19
19
  // Digest is load-bearing: `ollama pull` replacing a tag's underlying weights
20
20
  // must invalidate the cache even though the name/tag string is unchanged.
21
- // Persisted forever (no TTL) because the key itself is what expires the
22
- // entry — a version/digest bump makes a new key, not a stale hit on the old
23
- // one. Same disk-cache directory convention as sca/sigstore-verify.js and
21
+ // Same disk-cache directory convention as sca/sigstore-verify.js and
24
22
  // engine.js's OSV cache (`~/.claude/agentic-security/<name>/`).
23
+ //
24
+ // TTL + force-reprobe (adversarial-review fix, 2026-09). The key-based
25
+ // invalidation above is real but not complete: this module's own comment
26
+ // used to claim the entry is safe "forever" because the key changes when
27
+ // the model does — but `/api/show` doesn't expose a digest on every Ollama
28
+ // version (falls back to model NAME alone then, a few lines below), so a
29
+ // same-tag re-pull, or simply an unlucky single-trial probe the first time
30
+ // (see probeStructuredOutput/probeToolCalling's own single-call design),
31
+ // had no way to ever self-correct short of a user manually deleting a file
32
+ // under `~/.claude/agentic-security/`. Two independent fixes, since either
33
+ // alone leaves a real gap: a default TTL as a safety net for the case
34
+ // nobody notices, and an explicit `force` option (`models test --force`)
35
+ // for the case someone DOES suspect a stale answer and wants it right now.
25
36
 
26
37
  import * as fs from 'node:fs';
27
38
  import * as path from 'node:path';
@@ -32,18 +43,38 @@ import { capabilitiesFromFamilyHint } from './model-capabilities.js';
32
43
 
33
44
  const CACHE_DIR = path.join(os.homedir(), '.claude', 'agentic-security', 'ollama-capability-cache');
34
45
 
46
+ // Default safety-net TTL: 30 days. Not the primary invalidation mechanism
47
+ // (the key is) — a backstop for the cases the key can't see: a same-tag
48
+ // re-pull on an Ollama version that doesn't expose a digest, or a single
49
+ // unlucky probe trial that happened to pass/fail against the model's true
50
+ // behavior. Overridable for anyone who wants a tighter or looser bound.
51
+ export const DEFAULT_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
52
+
35
53
  function _ensureCacheDir() { try { fs.mkdirSync(CACHE_DIR, { recursive: true }); } catch {} }
36
54
  function _cacheKey(ollamaVersion, modelDigest, modelName) {
37
55
  return crypto.createHash('sha256').update(`${ollamaVersion}::${modelDigest}::${modelName}`).digest('hex');
38
56
  }
39
57
  function _cachePath(key) { return path.join(CACHE_DIR, key + '.json'); }
40
58
 
41
- function _readProbeCache(key) {
42
- try { return JSON.parse(fs.readFileSync(_cachePath(key), 'utf8')); } catch { return null; }
59
+ /**
60
+ * @returns {object|null} the cached probe RESULT (not the envelope), or
61
+ * `null` on a miss, a parse failure, OR an entry older than `ttlMs`.
62
+ */
63
+ function _readProbeCache(key, ttlMs) {
64
+ let envelope;
65
+ try { envelope = JSON.parse(fs.readFileSync(_cachePath(key), 'utf8')); } catch { return null; }
66
+ // Backward-compatible with a pre-TTL cache file that was just the bare
67
+ // result object (no `probedAt`) — treat an entry with no timestamp as
68
+ // fresh rather than discarding every cache written before this fix.
69
+ if (envelope && typeof envelope === 'object' && 'probedAt' in envelope && 'result' in envelope) {
70
+ if (Number.isFinite(ttlMs) && ttlMs > 0 && Date.now() - envelope.probedAt > ttlMs) return null;
71
+ return envelope.result;
72
+ }
73
+ return envelope;
43
74
  }
44
75
  function _writeProbeCache(key, value) {
45
76
  _ensureCacheDir();
46
- try { fs.writeFileSync(_cachePath(key), JSON.stringify(value)); } catch {}
77
+ try { fs.writeFileSync(_cachePath(key), JSON.stringify({ probedAt: Date.now(), result: value })); } catch {}
47
78
  }
48
79
 
49
80
  /**
@@ -146,9 +177,16 @@ function _mergeLayer(base, overlay, sourceFlag) {
146
177
  * should use, since Layer C spends real inference time on the user's
147
178
  * machine.
148
179
  *
180
+ * `force: true` (adversarial-review fix, 2026-09 — `models test --force`)
181
+ * skips reading the cache — always runs a fresh probe and overwrites
182
+ * whatever was there. `ttlMs` (default 30 days, `DEFAULT_CACHE_TTL_MS`)
183
+ * bounds how long a cached entry is trusted without either; pass `0`/
184
+ * `Infinity` to disable the TTL safety net entirely and rely on the key
185
+ * alone, matching this module's original design intent.
186
+ *
149
187
  * @returns {{ok:true, capabilities:object, cached:boolean} | {ok:false, code, reason}}
150
188
  */
151
- export async function getModelCapabilities({ host, model, env = process.env, probe = false, timeouts, keepAlive } = {}) {
189
+ export async function getModelCapabilities({ host, model, env = process.env, probe = false, force = false, ttlMs = DEFAULT_CACHE_TTL_MS, timeouts, keepAlive } = {}) {
152
190
  let capabilities = capabilitiesFromFamilyHint(model);
153
191
 
154
192
  const show = await showOllamaModel({ host, model, timeouts });
@@ -169,7 +207,7 @@ export async function getModelCapabilities({ host, model, env = process.env, pro
169
207
  const modelDigest = show.ok && show.details?.digest ? show.details.digest : 'unknown-digest';
170
208
  const cacheKey = _cacheKey(ollamaVersion, modelDigest, model);
171
209
 
172
- const cached = _readProbeCache(cacheKey);
210
+ const cached = force ? null : _readProbeCache(cacheKey, ttlMs);
173
211
  if (cached) {
174
212
  return { ok: true, capabilities: _mergeLayer(capabilities, cached, 'runtimeProbe'), cached: true };
175
213
  }