@clear-capabilities/agentic-security-scanner 0.150.1 → 0.150.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/bin/agentic-security.js +73 -10
- package/dist/1122.index.js +16 -16
- package/dist/1208.index.js +23 -0
- package/dist/1301.index.js +3 -3
- package/dist/1379.index.js +3 -3
- package/dist/1444.index.js +3 -3
- package/dist/1660.index.js +1 -1
- package/dist/1700.index.js +5 -5
- package/dist/1905.index.js +12 -12
- package/dist/1920.index.js +10 -10
- package/dist/2238.index.js +4 -4
- package/dist/2271.index.js +3 -3
- package/dist/2432.index.js +8 -8
- package/dist/2659.index.js +1 -1
- package/dist/2826.index.js +2 -2
- package/dist/2830.index.js +3 -3
- package/dist/2923.index.js +1 -1
- package/dist/3001.index.js +4 -4
- package/dist/3117.index.js +4 -4
- package/dist/3180.index.js +9 -9
- package/dist/3276.index.js +1 -1
- package/dist/3415.index.js +1 -1
- package/dist/3499.index.js +1 -1
- package/dist/3518.index.js +4 -4
- package/dist/3736.index.js +7 -7
- package/dist/3839.index.js +4 -4
- package/dist/4113.index.js +441 -418
- package/dist/4265.index.js +4 -4
- package/dist/4384.index.js +8 -3
- package/dist/4399.index.js +29 -3
- package/dist/4547.index.js +2 -2
- package/dist/4863.index.js +6 -6
- package/dist/4970.index.js +2 -2
- package/dist/5051.index.js +15 -15
- package/dist/5144.index.js +4 -4
- package/dist/5333.index.js +8 -8
- package/dist/5343.index.js +2 -2
- package/dist/5350.index.js +6 -6
- package/dist/5561.index.js +1 -1
- package/dist/5637.index.js +10 -5
- package/dist/5724.index.js +21642 -0
- package/dist/5756.index.js +99 -489
- package/dist/5830.index.js +3 -3
- package/dist/6257.index.js +21 -8
- package/dist/6626.index.js +5 -5
- package/dist/6662.index.js +1 -1
- package/dist/6675.index.js +4 -4
- package/dist/6730.index.js +10 -10
- package/dist/6829.index.js +4 -4
- package/dist/6944.index.js +2 -2
- package/dist/6994.index.js +5 -5
- package/dist/7039.index.js +83 -19
- package/dist/7178.index.js +11 -6
- package/dist/7227.index.js +3 -3
- package/dist/7552.index.js +2 -2
- package/dist/7709.index.js +2 -2
- package/dist/7838.index.js +1037 -0
- package/dist/8218.index.js +4 -4
- package/dist/8476.index.js +4 -4
- package/dist/8513.index.js +8 -8
- package/dist/8520.index.js +1 -1
- package/dist/8752.index.js +4 -4
- package/dist/9207.index.js +2 -2
- package/dist/9220.index.js +5 -5
- package/dist/9390.index.js +6 -6
- package/dist/9503.index.js +2 -2
- package/dist/9560.index.js +3221 -0
- package/dist/957.index.js +4 -4
- package/dist/9801.index.js +1 -1
- package/dist/9824.index.js +5 -5
- package/dist/agentic-security.mjs +23 -23
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +3 -2
- package/src/egress/redact.js +1 -1
- package/src/engine.js +13 -2
- package/src/llm-validator/agent-loop.js +85 -5
- package/src/llm-validator/fix-proposal.js +16 -3
- package/src/llm-validator/model-capabilities.js +26 -1
- package/src/llm-validator/model-probe.js +46 -8
- package/src/llm-validator/ollama-provider.js +26 -1
- package/src/llm-validator/oom-feedback.js +69 -0
- package/src/llm-validator/providers.js +50 -0
- package/src/llm-validator/redact.js +177 -11
- package/src/report/index.js +14 -3
- package/src/sast/java-ast-folding.js +18 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
54183e4ed75d70aeec00ba53f44450879c52d23fc1bd3ccc834719531f18a017 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.
|
|
3
|
+
"version": "0.150.2",
|
|
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,9 +66,10 @@
|
|
|
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: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",
|
|
72
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",
|
|
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",
|
package/src/egress/redact.js
CHANGED
|
@@ -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:
|
|
10011
|
+
cloudFallback: !NO_CLOUD_FALLBACK,
|
|
10012
|
+
otherRolesRemote,
|
|
10002
10013
|
stages: { validate: stageSummaryFromModelStatus(status) },
|
|
10003
10014
|
};
|
|
10004
10015
|
}
|
|
@@ -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
|
|
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
|
-
|
|
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)
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
|
|
42
|
-
|
|
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
|
}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
// never to this module's error path. See ollama-offline-egress.test.js.
|
|
33
33
|
|
|
34
34
|
import { isLoopbackUrl } from './local-endpoint.js';
|
|
35
|
+
import { recordOOMEvent } from './oom-feedback.js';
|
|
35
36
|
|
|
36
37
|
export const DEFAULT_OLLAMA_HOST = 'http://127.0.0.1:11434';
|
|
37
38
|
export const DEFAULT_OLLAMA_MODEL = 'qwen3.5:4b';
|
|
@@ -44,6 +45,7 @@ const DEFAULT_MAX_CONCURRENCY = 1;
|
|
|
44
45
|
// exactly one of these, never an ad-hoc string, so a caller (and a report) can
|
|
45
46
|
// react on `code` instead of parsing prose.
|
|
46
47
|
export const OLLAMA_ERROR_CODES = Object.freeze([
|
|
48
|
+
'ollama-disabled',
|
|
47
49
|
'ollama-not-running',
|
|
48
50
|
'ollama-unreachable',
|
|
49
51
|
'ollama-non-loopback-refused',
|
|
@@ -69,6 +71,18 @@ function _err(code, reason) {
|
|
|
69
71
|
* @returns {{ok:true, config:object} | {ok:false, code:string, reason:string}}
|
|
70
72
|
*/
|
|
71
73
|
export function ollamaEndpointConfig(env = process.env) {
|
|
74
|
+
// Adversarial-review fix (2026-09): no kill switch existed for this whole
|
|
75
|
+
// provider, unlike AGENTIC_SECURITY_MCP_DISABLED for the MCP server —
|
|
76
|
+
// worse, a per-role AGENTIC_SECURITY_LLM_PRESET_<ROLE>=ollama override
|
|
77
|
+
// (providers.js's _forRole) can keep a role calling Ollama even after an
|
|
78
|
+
// operator unsets the GLOBAL preset during an incident, so "just unset
|
|
79
|
+
// the preset" is not reliably enough. This check is here, in the one
|
|
80
|
+
// function every Ollama call path resolves through (resolveProvider's
|
|
81
|
+
// ollama branch, and models/setup's direct callers), so it can never be
|
|
82
|
+
// bypassed by a role-specific override the operator forgot about.
|
|
83
|
+
if (env.AGENTIC_SECURITY_OLLAMA_DISABLED === '1') {
|
|
84
|
+
return _err('ollama-disabled', 'Ollama is disabled (AGENTIC_SECURITY_OLLAMA_DISABLED=1). Unset it to re-enable.');
|
|
85
|
+
}
|
|
72
86
|
const rawHost = env.AGENTIC_SECURITY_OLLAMA_HOST || DEFAULT_OLLAMA_HOST;
|
|
73
87
|
const host = String(rawHost).replace(/\/+$/, '');
|
|
74
88
|
const allowRemote = env.AGENTIC_SECURITY_OLLAMA_ALLOW_REMOTE === '1';
|
|
@@ -220,7 +234,18 @@ export async function callOllamaChat({ host, model, messages, maxTokens, schema,
|
|
|
220
234
|
if (res.status === 404 || /not found/i.test(detail)) {
|
|
221
235
|
return _err('ollama-model-not-installed', `Model '${model}' is not installed. ${detail || ''}`.trim());
|
|
222
236
|
}
|
|
223
|
-
if (/memory|oom/i.test(detail))
|
|
237
|
+
if (/memory|oom/i.test(detail)) {
|
|
238
|
+
// Adversarial-review fix (2026-09): this was a real, defined error
|
|
239
|
+
// code with no reactive call site anywhere — a wrong memory-admission
|
|
240
|
+
// estimate that caused a genuine OOM would repeat the identical wrong
|
|
241
|
+
// decision forever. Record it so recommendAdmission (model-
|
|
242
|
+
// capabilities.js) can warn on the NEXT admission check for this
|
|
243
|
+
// model on this machine, even though the underlying size/KV-cache
|
|
244
|
+
// ESTIMATES themselves stay uncalibrated (that needs real hardware
|
|
245
|
+
// variety a single machine's observed failures can't substitute for).
|
|
246
|
+
recordOOMEvent(model);
|
|
247
|
+
return _err('ollama-model-out-of-memory', detail || `HTTP ${res.status}`);
|
|
248
|
+
}
|
|
224
249
|
if (/context/i.test(detail)) return _err('ollama-context-overflow', detail || `HTTP ${res.status}`);
|
|
225
250
|
return _err('ollama-model-load-failed', detail || `HTTP ${res.status}`);
|
|
226
251
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Adversarial-review fix (2026-09): `ollama-model-out-of-memory` was a real,
|
|
2
|
+
// defined error code (ollama-provider.js's OLLAMA_ERROR_CODES) with ZERO
|
|
3
|
+
// call sites reacting to it anywhere in this codebase — a wrong memory-
|
|
4
|
+
// admission estimate that caused a genuine OOM would silently repeat the
|
|
5
|
+
// identical wrong "admitted: true" decision forever, since nothing recorded
|
|
6
|
+
// the failure for next time.
|
|
7
|
+
//
|
|
8
|
+
// NOT a replacement for real calibration. model-capabilities.js's
|
|
9
|
+
// KNOWN_MODEL_SIZE_GB / ESTIMATED_KV_CACHE_MB_PER_1K_TOKENS /
|
|
10
|
+
// RUNTIME_OVERHEAD_MB remain asserted, unmeasured "best-effort" constants —
|
|
11
|
+
// properly calibrating them needs real hardware variety this session cannot
|
|
12
|
+
// manufacture. This module is the cheapest thing that CAN improve after a
|
|
13
|
+
// wrong estimate without that: a per-machine, per-model OBSERVED-FAILURE
|
|
14
|
+
// ledger. A model that has already OOM'd on THIS machine gets an explicit
|
|
15
|
+
// warning attached to the next admission decision, rather than the same
|
|
16
|
+
// unqualified confidence a first-time estimate gets.
|
|
17
|
+
//
|
|
18
|
+
// Same disk-cache directory convention as model-probe.js's capability
|
|
19
|
+
// cache and sca/sigstore-verify.js's Rekor cache
|
|
20
|
+
// (`~/.claude/agentic-security/<name>/`).
|
|
21
|
+
|
|
22
|
+
import * as fs from 'node:fs';
|
|
23
|
+
import * as path from 'node:path';
|
|
24
|
+
import * as os from 'node:os';
|
|
25
|
+
|
|
26
|
+
const LOG_PATH = path.join(os.homedir(), '.claude', 'agentic-security', 'ollama-oom-log.json');
|
|
27
|
+
|
|
28
|
+
function _readLog() {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(fs.readFileSync(LOG_PATH, 'utf8'));
|
|
31
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
32
|
+
} catch { return {}; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function _writeLog(log) {
|
|
36
|
+
try {
|
|
37
|
+
fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true });
|
|
38
|
+
fs.writeFileSync(LOG_PATH, JSON.stringify(log));
|
|
39
|
+
} catch { /* best-effort; a failure here must never break the caller's real request */ }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Record a real, observed OOM failure for `model` on this machine. Called
|
|
44
|
+
* from ollama-provider.js's callOllamaChat — the single choke point every
|
|
45
|
+
* Ollama HTTP call in this codebase goes through, so every role's OOM
|
|
46
|
+
* failures land in the same ledger regardless of which one hit it.
|
|
47
|
+
*/
|
|
48
|
+
export function recordOOMEvent(model) {
|
|
49
|
+
if (typeof model !== 'string' || !model) return;
|
|
50
|
+
const log = _readLog();
|
|
51
|
+
const entry = log[model] || { count: 0, firstAt: Date.now() };
|
|
52
|
+
entry.count += 1;
|
|
53
|
+
entry.lastAt = Date.now();
|
|
54
|
+
log[model] = entry;
|
|
55
|
+
_writeLog(log);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @returns {{count:number, firstAt:number, lastAt:number} | null} prior OOM
|
|
60
|
+
* history for `model` on this machine, or null if it has never failed
|
|
61
|
+
* this way here before.
|
|
62
|
+
*/
|
|
63
|
+
export function priorOOMFor(model) {
|
|
64
|
+
if (typeof model !== 'string' || !model) return null;
|
|
65
|
+
const log = _readLog();
|
|
66
|
+
return log[model] || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const _internals = { LOG_PATH };
|
|
@@ -237,6 +237,18 @@ export function buildProviderRequest(config, prompt, maxTokens) {
|
|
|
237
237
|
};
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
// Architectural invariant, not a per-scan observation: nothing in this
|
|
241
|
+
// module (or any caller of resolveProvider) ever reacts to a failed/refused
|
|
242
|
+
// resolution by silently trying a DIFFERENT provider — a refusal is refused,
|
|
243
|
+
// full stop (see the local/ollama loopback-refusal branches above, both of
|
|
244
|
+
// which return `{ok:false}` rather than falling through to a vendor preset).
|
|
245
|
+
// Declared once, HERE, so every place in the codebase that asserts "no cloud
|
|
246
|
+
// fallback" (currently only engine.js's AI-Assistance report) reads the same
|
|
247
|
+
// single source of truth instead of each hardcoding its own `false` literal
|
|
248
|
+
// — if this invariant ever needs to become conditional, there is exactly one
|
|
249
|
+
// place to change it and everything downstream updates with it.
|
|
250
|
+
export const NO_CLOUD_FALLBACK = true;
|
|
251
|
+
|
|
240
252
|
/** Which provider each role would use, for reporting. Never includes keys. */
|
|
241
253
|
export function providerMatrix(env = process.env) {
|
|
242
254
|
const out = {};
|
|
@@ -249,4 +261,42 @@ export function providerMatrix(env = process.env) {
|
|
|
249
261
|
return out;
|
|
250
262
|
}
|
|
251
263
|
|
|
264
|
+
// `hunt` is deliberately NOT one of `ROLES` above (discovery/hunter.js's own
|
|
265
|
+
// comment: it has no per-role override of its own, always falling back to
|
|
266
|
+
// whatever the GLOBAL preset resolves to) — which means `providerMatrix()`,
|
|
267
|
+
// built by iterating `ROLES`, structurally never sees it. Adversarial-review
|
|
268
|
+
// fix (2026-09, second pass): that made `otherRemoteRoles` blind to hunt by
|
|
269
|
+
// construction, not by an oversight in its filter — and hunt is exactly the
|
|
270
|
+
// role most likely to diverge from `validate` in the scenario this function
|
|
271
|
+
// exists to catch (a global cloud preset with `validate` given its own
|
|
272
|
+
// `ollama` override: hunt then silently follows the global cloud preset).
|
|
273
|
+
// Checked explicitly, alongside the `ROLES`-iterated matrix, rather than
|
|
274
|
+
// added to `ROLES` itself — doing that would wrongly imply hunt supports a
|
|
275
|
+
// `_HUNT`-suffixed per-role override, which it does not and this fix does
|
|
276
|
+
// not add.
|
|
277
|
+
const ROLES_WITH_NO_PER_ROLE_OVERRIDE = Object.freeze(['hunt']);
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Which roles OTHER than `excludeRole` resolve to a remote (non-loopback)
|
|
281
|
+
* provider right now. Exists so a report scoped to one role (the AI
|
|
282
|
+
* Assistance block's `validate`-only measurement) can disclose when a
|
|
283
|
+
* DIFFERENT role is genuinely configured for a cloud vendor at the same
|
|
284
|
+
* time, instead of leaving that role invisible to a reader who reasonably
|
|
285
|
+
* generalizes a single-role loopback claim onto the whole scan.
|
|
286
|
+
*
|
|
287
|
+
* @returns {{role:string, provider:string}[]}
|
|
288
|
+
*/
|
|
289
|
+
export function otherRemoteRoles(excludeRole, env = process.env) {
|
|
290
|
+
const matrix = providerMatrix(env);
|
|
291
|
+
const out = Object.entries(matrix)
|
|
292
|
+
.filter(([role, r]) => role !== excludeRole && r.provider && r.egress === 'remote')
|
|
293
|
+
.map(([role, r]) => ({ role, provider: r.provider }));
|
|
294
|
+
for (const role of ROLES_WITH_NO_PER_ROLE_OVERRIDE) {
|
|
295
|
+
if (role === excludeRole) continue;
|
|
296
|
+
const r = resolveProvider({ role, env });
|
|
297
|
+
if (r.ok && r.config.egress === 'remote') out.push({ role, provider: r.config.provider });
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
|
|
252
302
|
export const _internals = { SHAPES, DEFAULT_MODEL, _forRole };
|