@clear-capabilities/agentic-security-scanner 0.166.0 → 0.167.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/bin/agentic-security.js +39 -4
- package/dist/2199.index.js +348 -0
- package/dist/3740.index.js +722 -0
- package/dist/4113.index.js +1 -1
- package/dist/4142.index.js +929 -0
- package/dist/5738.index.js +240 -0
- package/dist/7838.index.js +1 -1
- package/dist/808.index.js +1330 -0
- package/dist/818.index.js +93 -0
- package/dist/9800.index.js +245 -0
- package/dist/9999.index.js +2 -2
- package/dist/agentic-security.mjs +2 -2
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +2 -2
- package/src/posture/artifact-registry.js +3 -0
- package/src/posture/proof-fabric/cli.js +115 -0
- package/src/posture/proof-fabric/fix-proof.js +73 -0
- package/src/posture/proof-fabric/oracles.js +101 -0
- package/src/posture/proof-fabric/orchestrator.js +129 -0
- package/src/posture/proof-fabric/reproducer.js +108 -0
- package/src/posture/proof-fabric/runner.js +243 -0
- package/src/posture/proof-fabric/states.js +53 -0
- package/src/posture/proof-fabric/store.js +83 -0
- package/src/posture/rule-compiler/cli.js +141 -0
- package/src/posture/rule-compiler/corpus.js +61 -0
- package/src/posture/rule-compiler/eligibility.js +25 -0
- package/src/posture/rule-compiler/ir.js +87 -0
- package/src/posture/rule-compiler/lifecycle.js +57 -0
- package/src/posture/rule-compiler/mutation.js +60 -0
- package/src/posture/rule-compiler/promotion.js +47 -0
- package/src/posture/rule-compiler/store.js +96 -0
- package/src/sandbox/backend-userspace.js +7 -1
- package/src/sandbox/trust-boundary.js +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2e44c68c2c211c228f7a80226210e4c15a722a1fa450433222f42abb127f3b32 agentic-security.mjs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clear-capabilities/agentic-security-scanner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.167.0",
|
|
4
4
|
"description": "Scanner engine for the agentic-security Claude Code plugin: SAST, SCA with reachability and CISA KEV, secrets, IaC, Haskell and Nix/NixOS, prompt-injection, MCP/agent-tool audit, authZ analysis, SBOM, compliance attestation, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
|
|
75
75
|
"test:glob": "node --test test/glob-compat.test.js",
|
|
76
76
|
"test:sast": "node --test test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/csharp-cross-file.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/java-structural-cross-file.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/java-ast-folding.test.js test/java-bench-extras.test.js test/reachability-missing-callgraph-data.test.js test/rate-limit.test.js test/auth-signals.test.js test/convention-deviation.test.js test/resource-exhaustion.test.js test/resource-exhaustion-realworld.test.js test/redirect-toctou.test.js test/redirect-toctou-realworld.test.js test/codegen-sink.test.js test/ownership-authz.test.js test/ownership-authz-realworld.test.js test/t21-precision-audit.test.js test/secrets-coverage.test.js test/prompt-injection-payloads.test.js test/iac-cloud-templates.test.js test/ruby-path-join.test.js test/sast/manifest-line-tracking.test.js test/rust-sast.test.js test/java-deserialization-precision.test.js test/pipeline-env-secret.test.js test/mark-used-vuln-functions.test.js",
|
|
77
|
-
"test:posture": "node --test test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js test/posture/assurance-baseline.test.js test/posture/assurance-contracts.test.js test/posture/assurance-config.test.js test/evidence-issuer.test.js test/load-factor.test.js",
|
|
77
|
+
"test:posture": "node --test test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js test/posture/assurance-baseline.test.js test/posture/assurance-contracts.test.js test/posture/assurance-config.test.js test/evidence-issuer.test.js test/load-factor.test.js test/proof-fabric/core.test.js test/proof-fabric/fix-store-runner.test.js test/proof-fabric/fixtures.test.js test/cli/proof-fabric-cli.test.js test/rule-compiler/core.test.js test/cli/rule-compiler-cli.test.js",
|
|
78
78
|
"test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/chrome-probe.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-rust.test.js test/rust-taint-flow.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js test/catalog-ai-model-provider-precision.test.js test/catalog-orm-write.test.js test/dataflow/provenance-pipeline-integration.test.js test/dataflow-progress.test.js test/scan-progress.test.js test/sard-leakage-pipeline.test.js test/sard-split-scoring.test.js test/sard-llm-isolation.test.js test/sard-mutation-adversarial.test.js test/sard-baseline-adaptive-tolerance.test.js test/sard-independent-holdout.test.js test/sard-flow-aware-scoring.test.js test/sard-macro-score-support-floor.test.js test/sard-php-split.test.js test/parser-java-nary-concat.test.js test/sard-naive-baseline.test.js test/field-taint-cross-method.test.js test/field-taint-engine-unit.test.js test/coercion-sanitizer-kill.test.js test/constant-propagation-path-sensitivity.test.js test/parser-java-assignments.test.js test/catalog-java-sard.test.js test/parser-cs-juliet-shapes.test.js test/catalog-cs-sard-p2.test.js test/sard-cwe-key-merge.test.js test/parser-php-backtick.test.js test/parser-php-include.test.js test/php-include-merge.test.js test/parser-php-array-field-taint.test.js test/parser-php-cond-assign.test.js test/parser-rust-redos.test.js test/multi-sink-taint-chain.test.js test/static-field-cross-class-call-string.test.js test/regex-guard-narrowing.test.js test/object-instance-field-taint.test.js test/param-type-inference.test.js test/smt-path-feasibility.test.js test/cross-service-taint.test.js test/dataflow-expr-taint-scaling.test.js test/evaluation/guard-dominance.test.js test/evaluation/engine-mechanisms.test.js test/rx-exec-receiver.test.js",
|
|
79
79
|
"test:mcp": "node --test test/mcp.test.js test/mcp-apply-fix-multifile.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js test/mcp-dataflow-tools.test.js test/cli/governance-propose-edit.test.js test/cli/remediation-open-update.test.js test/cli/remediation-verify-reopen.test.js test/cli/dataflow-observations.test.js test/cli/federate-declare-list.test.js test/capabilities/tools.test.js",
|
|
80
80
|
"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",
|
|
@@ -119,6 +119,8 @@ export const ARTIFACT_REGISTRY = [
|
|
|
119
119
|
// directory-of-files shape re-keyed commit -> import.
|
|
120
120
|
{ name: 'runtime-observations', kind: 'dir', classification: 'generated', retentionClass: 'evidence', confidential: true, source: 'src/lineage/observation-store.js (persistObservationImport)', note: 'FR-505 requires an observation store follow artifact encryption, RETENTION, RESET, access-control and no-egress rules. That is why this is `generated` (a plain `reset` MUST be able to delete it) rather than `operator-config`, and why it carries a real retentionClass — deliberately NOT the `remediation`/`legal-holds.json` no-retention call one section down, and deliberately NOT `provenance`\'s permanent-history call. This DOES stretch `generated`\'s usual definition — a rescan does not re-derive an import, the operator re-imports it — and that stretch is disclosed here rather than hidden: FR-505\'s explicit reset requirement breaks the tie. `confidential: true` is enforced by observation-store.js calling maybeEncryptForWrite/maybeDecryptForRead itself (posture/encryption-provider.js), the same per-writer opt-in compliance-evidence.json makes — the flag alone enforces nothing.' },
|
|
121
121
|
{ name: 'shadow-findings.json', kind: 'file', classification: 'generated', retentionClass: 'scan' },
|
|
122
|
+
{ name: 'reproducers', kind: 'dir', classification: 'operator-config', source: 'src/posture/proof-fabric/cli.js (loadReproducer)', note: 'Operator-supplied serialized reproducers read by `triage exploit --prove`. Input, never written by the scanner, so reset leaves it alone.' },
|
|
123
|
+
{ name: 'proofs', kind: 'dir', classification: 'generated', retentionClass: 'evidence', confidential: true, source: 'src/posture/proof-fabric/store.js (saveProof)', note: 'Universal Exploit Proof Fabric evidence: hashed records of exploit reproductions. Never holds plaintext credentials, per-run nonces or raw secrets (the runner redacts before persisting). Encrypted per the encryption policy via maybeEncryptForWrite, and purged by retention like any evidence-class artifact; a legal hold on `proofs` blocks the purge.' },
|
|
122
124
|
{ name: 'mcp-audit.log', kind: 'file', classification: 'generated', retentionClass: 'evidence' },
|
|
123
125
|
{ name: 'egress-audit.log', kind: 'file', classification: 'generated', retentionClass: 'evidence', note: "FR-604 per-call egress audit log — hash-chained NDJSON written by egress/audit.js's recordEgressCall, never read as config" },
|
|
124
126
|
{ name: 'hook-throttle.json', kind: 'file', classification: 'generated', retentionClass: 'cache' },
|
|
@@ -222,6 +224,7 @@ export const ARTIFACT_REGISTRY = [
|
|
|
222
224
|
{ name: 'rules.yml', kind: 'file', classification: 'operator-config' },
|
|
223
225
|
{ name: 'rules', kind: 'dir', classification: 'operator-config' },
|
|
224
226
|
{ name: 'rules-proposed', kind: 'dir', classification: 'operator-config', note: 'proposed rules awaiting human review — not yet approved config, but not scanner-regenerable either' },
|
|
227
|
+
{ name: 'rule-compiler', kind: 'dir', classification: 'generated', retentionClass: 'evidence', source: 'src/posture/rule-compiler/store.js', note: 'Self-Learning Finding-to-Rule Compiler: per-rule lifecycle ledger, IR, evaluation score and regression fixtures. The PROMOTED custom-rule document it publishes lives separately under `rules/` (operator-config, unaffected by reset) once promoted; this directory holds only the scanner-managed provenance and corpus behind that decision.' },
|
|
225
228
|
{ name: 'license-policy.yml', kind: 'file', classification: 'operator-config' },
|
|
226
229
|
{ name: 'trusted-keys.json', kind: 'file', classification: 'operator-config' },
|
|
227
230
|
{ name: 'ruleset-version.json', kind: 'file', classification: 'operator-config', note: 'pinning intent, hand-set' },
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// CLI surface for the proof fabric: `proof show|replay|doctor`, `triage exploit|validate`. Non-interactive by design: no
|
|
2
|
+
// command reads stdin, every run is bounded by the policy timeouts, and a missing reproducer is a typed exit code, not a
|
|
3
|
+
// prompt. Exit codes: 0 confirmed/ok, 1 not confirmed (any reason), 4 usage/precondition.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { statePath } from '../state-dir.js';
|
|
8
|
+
import { proveFinding } from './orchestrator.js';
|
|
9
|
+
import { saveProof, latestProof, listProofs, findingKey, scrubDeep } from './store.js';
|
|
10
|
+
import { selectEnvironment, hostTools, languageSupport } from './runner.js';
|
|
11
|
+
import { validateReproducer } from './reproducer.js';
|
|
12
|
+
import { LANGUAGES } from './reproducer.js';
|
|
13
|
+
|
|
14
|
+
function loadFinding(root, id) {
|
|
15
|
+
const p = statePath(root, 'last-scan.json');
|
|
16
|
+
if (!fs.existsSync(p)) return null;
|
|
17
|
+
const last = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
18
|
+
return (last.findings || []).find((f) => f.id === id || f.stableId === id) ?? null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function loadReproducerFor(root, id, flagPath) { return loadReproducer(root, id, flagPath); }
|
|
22
|
+
|
|
23
|
+
function loadReproducer(root, id, flagPath) {
|
|
24
|
+
const candidates = [flagPath && path.resolve(String(flagPath)), statePath(root, 'reproducers', `${findingKey(id)}.json`)].filter(Boolean);
|
|
25
|
+
for (const c of candidates) {
|
|
26
|
+
try { return { reproducer: JSON.parse(fs.readFileSync(c, 'utf8')), file: c }; } catch { /* next */ }
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatResult(r) {
|
|
32
|
+
const env = r.environment ? `${r.environment.mode} (${r.environment.assurance} assurance)` : 'none';
|
|
33
|
+
return [
|
|
34
|
+
`Finding: ${r.findingId}`,
|
|
35
|
+
`Evidence: ${r.state}`,
|
|
36
|
+
`Oracle: ${r.oracle}`,
|
|
37
|
+
`Reproductions: ${r.attempts?.successes ?? 0}/${r.attempts?.run ?? 0} (required ${r.attempts?.required})`,
|
|
38
|
+
`Independent replay: ${r.independentReplay?.status ?? 'not-run'}`,
|
|
39
|
+
`Environment: ${env}`,
|
|
40
|
+
`Completeness: ${r.completeness}`,
|
|
41
|
+
`Duration: ${(r.timing.proofMs / 1000).toFixed(1)}s`,
|
|
42
|
+
...(r.degradation?.length ? [`Degraded: ${r.degradation.join(', ')}`] : []),
|
|
43
|
+
...(r.note ? [`Note: ${r.note}`] : []),
|
|
44
|
+
].join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function cmdProve(args, root, { json = false } = {}) {
|
|
48
|
+
const id = String(args._[2] ?? '');
|
|
49
|
+
if (!id) { console.error('usage: triage exploit <finding-id> --prove [--reproducer file] [--attempts N] [--required N]'); return 4; }
|
|
50
|
+
const finding = loadFinding(root, id);
|
|
51
|
+
if (!finding) { console.error(`Finding ${id} not found in last scan.`); return 4; }
|
|
52
|
+
const loaded = loadReproducer(root, id, args.flags.reproducer);
|
|
53
|
+
if (!loaded) { console.error(`No reproducer for ${id}. Supply one with --reproducer <file> or place it at .agentic-security/reproducers/. Nothing was executed.`); return 4; }
|
|
54
|
+
const errs = validateReproducer(loaded.reproducer);
|
|
55
|
+
if (errs.length) { console.error(`Invalid reproducer: ${errs.join('; ')}`); return 4; }
|
|
56
|
+
const policy = {};
|
|
57
|
+
if (args.flags.attempts) policy.attempts = Number(args.flags.attempts);
|
|
58
|
+
if (args.flags.required) policy.required = Number(args.flags.required);
|
|
59
|
+
if (args.flags.timeout) policy.timeoutMs = Number(args.flags.timeout) * 1000;
|
|
60
|
+
if (args.flags['require-container']) policy.requireContainer = true;
|
|
61
|
+
const ac = new AbortController();
|
|
62
|
+
const onSig = () => ac.abort();
|
|
63
|
+
process.once('SIGINT', onSig);
|
|
64
|
+
let result;
|
|
65
|
+
try { result = await proveFinding({ finding, reproducer: loaded.reproducer, revision: args.flags.revision ?? null, policy, signal: ac.signal }); } finally { process.off('SIGINT', onSig); }
|
|
66
|
+
const saved = saveProof(root, { ...result, reproducer: loaded.reproducer });
|
|
67
|
+
// Printed output gets the same secret-scrubbing pass as what is persisted (defense in depth: a free-text reason/note
|
|
68
|
+
// field that captured something from stdout/stderr should never reach a terminal unredacted either).
|
|
69
|
+
if (json || args.flags.json) console.log(JSON.stringify(scrubDeep({ ...result, stored: saved.ok ? saved.file : null }), null, 2));
|
|
70
|
+
else { console.log(formatResult(result)); if (!saved.ok) console.log(`(proof not stored: ${saved.reason})`); }
|
|
71
|
+
return result.state === 'EXPLOIT_CONFIRMED' ? 0 : 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function cmdProof(args, root) {
|
|
75
|
+
const sub = args._[1];
|
|
76
|
+
if (sub === 'doctor') return doctor(args);
|
|
77
|
+
const id = String(args._[2] ?? '');
|
|
78
|
+
if (!id) { console.error('usage: proof show <finding-id> | proof replay <finding-id> | proof doctor'); return 4; }
|
|
79
|
+
if (sub === 'show') {
|
|
80
|
+
const all = listProofs(root, id);
|
|
81
|
+
if (!all.length) { console.error(`No stored proof for ${id}.`); return 4; }
|
|
82
|
+
const latest = all.find((p) => p.record);
|
|
83
|
+
if (all.some((p) => p.tampered)) console.error(`WARNING: ${all.filter((p) => p.tampered).length} stored record(s) failed their integrity check and are not trusted.`);
|
|
84
|
+
const good = all.find((p) => p.record && !p.tampered);
|
|
85
|
+
if (!good) { console.error('No intact proof record.'); return 1; }
|
|
86
|
+
if (args.flags.json) console.log(JSON.stringify(good.record, null, 2)); else console.log(formatResult(good.record));
|
|
87
|
+
void latest;
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
if (sub === 'replay') {
|
|
91
|
+
const stored = latestProof(root, id);
|
|
92
|
+
if (!stored?.record?.reproducer) { console.error(`No stored reproducer for ${id}.`); return 4; }
|
|
93
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'proof-replay-'));
|
|
94
|
+
const tmp = path.join(tmpDir, 'reproducer.json');
|
|
95
|
+
fs.writeFileSync(tmp, JSON.stringify(stored.record.reproducer));
|
|
96
|
+
args.flags.reproducer = tmp;
|
|
97
|
+
args._ = ['triage', 'exploit', id];
|
|
98
|
+
try { return await cmdProve(args, root); } finally { fs.rmSync(tmpDir, { recursive: true, force: true }); }
|
|
99
|
+
}
|
|
100
|
+
console.error('usage: proof show|replay <finding-id> | proof doctor');
|
|
101
|
+
return 4;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function doctor(args) {
|
|
105
|
+
const tools = hostTools();
|
|
106
|
+
const rows = LANGUAGES.map((l) => ({ lang: l, ...languageSupport(l, tools) }));
|
|
107
|
+
const envs = ['javascript', 'python'].map((l) => ({ lang: l, ...selectEnvironment({ lang: l }) }));
|
|
108
|
+
if (args.flags.json) { console.log(JSON.stringify({ tools, languages: rows, environments: envs }, null, 2)); return 0; }
|
|
109
|
+
console.log('Proof fabric doctor');
|
|
110
|
+
for (const r of rows) console.log(` ${r.lang.padEnd(11)} ${r.supported ? 'supported' : `unsupported (${r.reason})`}`);
|
|
111
|
+
for (const e of envs) console.log(` environment for ${e.lang}: ${e.mode}, ${e.assurance} assurance${e.reason ? ` (${e.reason})` : ''}${e.containerSkipped ? `; container skipped: ${e.containerSkipped}` : ''}`);
|
|
112
|
+
const usable = envs.some((e) => e.mode !== 'unavailable');
|
|
113
|
+
if (!usable) console.log(' RESULT: no execution environment available; proofs will report blocked, never clean.');
|
|
114
|
+
return usable ? 0 : 1;
|
|
115
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Verified remediation, extended with exploit replay. This EXTENDS the fix-verify vocabulary (`verifiedFull`,
|
|
2
|
+
// `degradedLegs`); it does not define a competing meaning of "verified".
|
|
3
|
+
//
|
|
4
|
+
// 1. pre-patch: the original exploit must be EXPLOIT_CONFIRMED (3/3 + independent replay) on the unpatched code;
|
|
5
|
+
// 2. the candidate patch is overlaid on the imported sources and the SAME exploit is replayed;
|
|
6
|
+
// 3. the patched replay must COMPLETE in every attempt and the oracle must not fire in any: BLOCKED.
|
|
7
|
+
// An attempt that did not complete (timeout, build failure, no environment) is inconclusive, never "blocked";
|
|
8
|
+
// 4. static legs (re-scan, lint, tests, honesty) come from `verifyFix`, passed in so they are not re-implemented;
|
|
9
|
+
// 5. FIX_VERIFIED only when 1-4 hold and the static verification had no degraded leg.
|
|
10
|
+
import { advance } from './states.js';
|
|
11
|
+
import { proveFinding } from './orchestrator.js';
|
|
12
|
+
import { runAttempt } from './runner.js';
|
|
13
|
+
import { reproducerDigest, sha256, canonicalize } from './reproducer.js';
|
|
14
|
+
|
|
15
|
+
export async function verifyFixWithProof({ finding, reproducer, files, scanRoot, revision = null, policy = {}, staticVerification = null, siblings = null, signal, deps = {} } = {}) {
|
|
16
|
+
const t0 = Date.now();
|
|
17
|
+
const attemptFn = deps.attemptFn ?? runAttempt;
|
|
18
|
+
const patchHash = sha256(canonicalize(files || {}));
|
|
19
|
+
const legs = { prePatch: null, postPatch: null, regression: staticVerification ? { ok: !!staticVerification.ok, verifiedFull: !!staticVerification.verifiedFull } : { status: 'not-run' }, rescan: staticVerification?.rescan ?? { status: 'not-run' } };
|
|
20
|
+
const out = (extra) => ({ schema: 'agentic-security/fix-proof', version: 1, findingId: finding?.stableId ?? finding?.id ?? null, patchHash, reproducerDigest: reproducerDigest(reproducer), legs, durationMs: Date.now() - t0, ...extra });
|
|
21
|
+
|
|
22
|
+
const pre = await proveFinding({ finding, revision, reproducer, policy, signal, deps, sourceRoot: scanRoot });
|
|
23
|
+
legs.prePatch = { state: pre.state, outcome: pre.outcome, completeness: pre.completeness };
|
|
24
|
+
if (pre.state !== 'EXPLOIT_CONFIRMED') {
|
|
25
|
+
return out({ state: pre.state, verified: false, completeness: 'INCOMPLETE', incompleteStep: 'pre-patch-exploit', reason: `the original exploit was not confirmed before the patch (${pre.outcome}); a fix cannot be proven against an exploit that does not reproduce` });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const attempts = Math.max(1, Math.min(10, policy.attempts ?? 3));
|
|
29
|
+
const results = [];
|
|
30
|
+
for (let i = 0; i < attempts; i++) {
|
|
31
|
+
if (signal?.aborted) break;
|
|
32
|
+
results.push(await attemptFn(structuredClone(reproducer), { limits: { timeoutMs: policy.timeoutMs }, signal, sourceRoot: scanRoot, overlay: files, requireContainer: policy.requireContainer, force: policy.force }));
|
|
33
|
+
}
|
|
34
|
+
const completed = results.filter((r) => r.status === 'completed' && !r.oracle?.inconclusive);
|
|
35
|
+
const fired = completed.filter((r) => r.oracle?.fired).length;
|
|
36
|
+
legs.postPatch = { attempts: results.length, completed: completed.length, exploitSucceeded: fired, statuses: results.map((r) => r.status) };
|
|
37
|
+
|
|
38
|
+
if (fired > 0) return out({ state: 'EXPLOIT_CONFIRMED', verified: false, completeness: 'FULL', incompleteStep: 'post-patch-exploit', reason: 'the exploit still succeeds against the patched code' });
|
|
39
|
+
if (completed.length < results.length || results.length === 0) {
|
|
40
|
+
return out({ state: 'EXPLOIT_CONFIRMED', verified: false, completeness: 'INCOMPLETE', incompleteStep: 'post-patch-replay', reason: 'the patched replay did not complete in every attempt, so the patch is not shown to block the exploit' });
|
|
41
|
+
}
|
|
42
|
+
let state = advance('EXPLOIT_CONFIRMED', 'FIX_REPLAY_BLOCKED').state;
|
|
43
|
+
|
|
44
|
+
const degraded = [];
|
|
45
|
+
if (!staticVerification) degraded.push('static-verification-not-run');
|
|
46
|
+
else {
|
|
47
|
+
if (!staticVerification.ok) return out({ state, verified: false, completeness: 'FULL', incompleteStep: 'static-verification', reason: `exploit blocked, but static verification failed: ${staticVerification.rescan?.reason || 'a leg failed'}` });
|
|
48
|
+
for (const d of staticVerification.degradedLegs || []) degraded.push(d);
|
|
49
|
+
}
|
|
50
|
+
const lowAssurance = pre.environment?.assurance === 'lower';
|
|
51
|
+
if (lowAssurance) degraded.push('lower-assurance-local-confinement');
|
|
52
|
+
const siblingCount = Array.isArray(siblings) ? siblings.length : null;
|
|
53
|
+
if (siblingCount === null) degraded.push('sibling-search-not-run');
|
|
54
|
+
else if (siblingCount > 0) degraded.push(`sibling-exposures:${siblingCount}`);
|
|
55
|
+
|
|
56
|
+
const full = degraded.length === 0;
|
|
57
|
+
// Same split as fix-verify: `ok` (nothing that ran failed) earns FIX_VERIFIED; `completeness` says whether anything was skipped.
|
|
58
|
+
if (staticVerification && staticVerification.ok) state = advance(state, 'FIX_VERIFIED').state;
|
|
59
|
+
return out({ state, verified: state === 'FIX_VERIFIED', completeness: full ? 'FULL' : 'DEGRADED', degradedLegs: degraded, siblings: siblings ?? null });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function formatFixProof(r) {
|
|
63
|
+
const L = r.legs || {};
|
|
64
|
+
return [
|
|
65
|
+
`Pre-patch exploit: ${L.prePatch?.state === 'EXPLOIT_CONFIRMED' ? 'PASS' : 'NOT CONFIRMED'}`,
|
|
66
|
+
`Post-patch exploit: ${!L.postPatch ? 'NOT RUN' : L.postPatch.exploitSucceeded > 0 ? 'STILL SUCCEEDS' : L.postPatch.completed === L.postPatch.attempts ? 'BLOCKED' : 'INCONCLUSIVE'}`,
|
|
67
|
+
`Regression tests: ${L.regression?.status === 'not-run' ? 'not run' : L.regression?.verifiedFull ? 'PASS' : L.regression?.ok ? 'PASS (degraded)' : 'FAIL'}`,
|
|
68
|
+
`Security rescan: ${L.rescan?.ok === true ? 'PASS' : L.rescan?.status === 'not-run' ? 'not run' : 'FAIL'}`,
|
|
69
|
+
`Completeness: ${r.completeness}${r.degradedLegs?.length ? ` (${r.degradedLegs.join('; ')})` : ''}`,
|
|
70
|
+
'',
|
|
71
|
+
`Evidence: ${r.state}${r.verified ? '' : ` (not verified: ${r.reason || r.incompleteStep || 'degraded'})`}`,
|
|
72
|
+
].join('\n');
|
|
73
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Deterministic security oracles. A model may SELECT an oracle and propose its parameters; it may never decide that the
|
|
2
|
+
// oracle succeeded. Each oracle is a pure function of an Observation gathered by the TRUSTED runner (never by the model):
|
|
3
|
+
//
|
|
4
|
+
// Observation = {
|
|
5
|
+
// exitCode, signal, timedOut, stdout, stderr,
|
|
6
|
+
// canaries: { <name>: { exists, digestBefore, digestAfter } } // checked by the runner on disk, after the run
|
|
7
|
+
// callbacks: [{ nonce }] // hits recorded by the runner's loopback listener
|
|
8
|
+
// nonce // per-run random token the target cannot predict
|
|
9
|
+
// }
|
|
10
|
+
//
|
|
11
|
+
// An oracle returns { fired, reason }. `fired:false` with `inconclusive:true` means the observation could not decide
|
|
12
|
+
// (for example a timeout); it is never treated as "not exploitable".
|
|
13
|
+
export const ORACLE_TYPES = Object.freeze([
|
|
14
|
+
'crash_sanitizer', 'command_execution', 'unauthorized_http_access', 'unauthorized_db_read', 'database_mutation',
|
|
15
|
+
'filesystem_mutation', 'outbound_callback', 'authentication_bypass', 'arbitrary_assertion',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const SANITIZER_RE = /(AddressSanitizer|UndefinedBehaviorSanitizer|stack-buffer-overflow|heap-buffer-overflow|SEGV on unknown address|stack smashing detected|buffer overflow detected)/i;
|
|
19
|
+
const CRASH_SIGNALS = new Set(['SIGSEGV', 'SIGABRT', 'SIGBUS', 'SIGILL', 'SIGFPE']);
|
|
20
|
+
|
|
21
|
+
const out = (fired, reason, extra = {}) => ({ fired, reason, ...extra });
|
|
22
|
+
const inconclusive = (reason) => ({ fired: false, inconclusive: true, reason });
|
|
23
|
+
const has = (obs, text) => typeof text === 'string' && text.length > 0 && `${obs.stdout || ''}${obs.stderr || ''}`.includes(text);
|
|
24
|
+
|
|
25
|
+
const IMPL = {
|
|
26
|
+
crash_sanitizer(obs) {
|
|
27
|
+
if (SANITIZER_RE.test(obs.stderr || '')) return out(true, 'sanitizer or runtime memory-safety report on stderr');
|
|
28
|
+
if (obs.signal && CRASH_SIGNALS.has(obs.signal)) return out(true, `process terminated by ${obs.signal}`);
|
|
29
|
+
if (obs.timedOut) return inconclusive('run timed out before a crash could be judged');
|
|
30
|
+
return out(false, 'no crash signal and no sanitizer report');
|
|
31
|
+
},
|
|
32
|
+
// The injected payload asks the shell/interpreter to create a file named after the per-run nonce inside the
|
|
33
|
+
// watched canary directory. The target cannot create it unless attacker data reached a command.
|
|
34
|
+
command_execution(obs, p) {
|
|
35
|
+
const c = obs.canaries?.[p.canary || 'command'];
|
|
36
|
+
if (!c) return inconclusive('command canary was not armed by the runner');
|
|
37
|
+
if (c.exists && !c.existedBefore) return out(true, 'nonce-named canary file created by an injected command');
|
|
38
|
+
if (obs.timedOut) return inconclusive('run timed out before the canary could be judged');
|
|
39
|
+
return out(false, 'canary file not created');
|
|
40
|
+
},
|
|
41
|
+
// A secret planted behind authorization (never shown to the exploit client). Seeing the nonce-bearing secret in the
|
|
42
|
+
// client's output while the actor held no credential is the violation.
|
|
43
|
+
unauthorized_http_access(obs, p) {
|
|
44
|
+
const secret = obs.secrets?.[p.secret || 'http'];
|
|
45
|
+
if (!secret) return inconclusive('protected secret was not armed by the runner');
|
|
46
|
+
if (has(obs, secret)) return out(true, 'protected resource content returned to an unauthenticated actor');
|
|
47
|
+
if (obs.timedOut) return inconclusive('run timed out');
|
|
48
|
+
return out(false, 'protected content not observed');
|
|
49
|
+
},
|
|
50
|
+
unauthorized_db_read(obs, p) {
|
|
51
|
+
const secret = obs.secrets?.[p.secret || 'db'];
|
|
52
|
+
if (!secret) return inconclusive('database canary row was not armed by the runner');
|
|
53
|
+
if (has(obs, secret)) return out(true, 'row the actor may not read was returned');
|
|
54
|
+
if (obs.timedOut) return inconclusive('run timed out');
|
|
55
|
+
return out(false, 'canary row value not observed');
|
|
56
|
+
},
|
|
57
|
+
database_mutation(obs, p) {
|
|
58
|
+
const c = obs.canaries?.[p.canary || 'db'];
|
|
59
|
+
if (!c) return inconclusive('database canary was not armed by the runner');
|
|
60
|
+
if (c.digestBefore && c.digestAfter && c.digestBefore !== c.digestAfter) return out(true, 'protected database state changed');
|
|
61
|
+
if (obs.timedOut) return inconclusive('run timed out');
|
|
62
|
+
return out(false, 'protected database state unchanged');
|
|
63
|
+
},
|
|
64
|
+
filesystem_mutation(obs, p) {
|
|
65
|
+
const c = obs.canaries?.[p.canary || 'fs'];
|
|
66
|
+
if (!c) return inconclusive('filesystem canary was not armed by the runner');
|
|
67
|
+
if ((c.exists && !c.existedBefore) || (c.digestBefore && c.digestAfter && c.digestBefore !== c.digestAfter)) return out(true, 'file outside the permitted area was created or modified');
|
|
68
|
+
if (obs.timedOut) return inconclusive('run timed out');
|
|
69
|
+
return out(false, 'protected path unchanged');
|
|
70
|
+
},
|
|
71
|
+
outbound_callback(obs) {
|
|
72
|
+
if (obs.callbackUnavailable) return { fired: false, unsupported: true, reason: 'loopback callback listener is not available on this backend' };
|
|
73
|
+
const hit = (obs.callbacks || []).some((c) => c && c.nonce && c.nonce === obs.nonce);
|
|
74
|
+
if (hit) return out(true, 'nonce-bearing request reached the runner-controlled listener');
|
|
75
|
+
if (obs.timedOut) return inconclusive('run timed out');
|
|
76
|
+
return out(false, 'no nonce-bearing callback received');
|
|
77
|
+
},
|
|
78
|
+
authentication_bypass(obs, p) {
|
|
79
|
+
const secret = obs.secrets?.[p.secret || 'auth'];
|
|
80
|
+
if (!secret) return inconclusive('protected secret was not armed by the runner');
|
|
81
|
+
if (has(obs, secret)) return out(true, 'privileged content returned without valid credentials');
|
|
82
|
+
if (obs.timedOut) return inconclusive('run timed out');
|
|
83
|
+
return out(false, 'privileged content not observed without credentials');
|
|
84
|
+
},
|
|
85
|
+
// Declarative only: no code, no regex supplied by a model. Operators are a closed set.
|
|
86
|
+
arbitrary_assertion(obs, p) {
|
|
87
|
+
const a = p.assert;
|
|
88
|
+
if (!a || typeof a !== 'object') return inconclusive('assertion missing');
|
|
89
|
+
if (obs.timedOut) return inconclusive('run timed out');
|
|
90
|
+
if ('exitCode' in a) return a.exitCode === obs.exitCode ? out(true, `exit code was ${obs.exitCode}`) : out(false, `exit code was ${obs.exitCode}`);
|
|
91
|
+
if ('stdoutIncludes' in a) return has({ stdout: obs.stdout }, String(a.stdoutIncludes)) ? out(true, 'stdout contained the asserted text') : out(false, 'stdout did not contain the asserted text');
|
|
92
|
+
if ('stderrIncludes' in a) return has({ stderr: obs.stderr }, String(a.stderrIncludes)) ? out(true, 'stderr contained the asserted text') : out(false, 'stderr did not contain the asserted text');
|
|
93
|
+
return inconclusive('unsupported assertion operator');
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export function evaluateOracle(spec, observation) {
|
|
98
|
+
if (!spec || !ORACLE_TYPES.includes(spec.type)) return { fired: false, unsupported: true, reason: `unknown oracle type: ${String(spec?.type)}` };
|
|
99
|
+
if (!observation || typeof observation !== 'object') return inconclusive('no observation');
|
|
100
|
+
try { return IMPL[spec.type](observation, spec.params || {}); } catch (e) { return inconclusive(`oracle error: ${e.message}`); }
|
|
101
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Proof orchestration: finding + revision + reproducer + policy -> structured evidence.
|
|
2
|
+
//
|
|
3
|
+
// Rules that make this safe to trust:
|
|
4
|
+
// * The model may propose a reproducer and select an oracle. The oracle (oracles.js) judges. Never a model.
|
|
5
|
+
// * An attempt that did not complete (timeout, build failure, blocked, unsupported, error) is NOT a negative and NOT a
|
|
6
|
+
// positive: it is degraded evidence, it never advances the state, and it makes the result INCOMPLETE.
|
|
7
|
+
// * EXPLOIT_CONFIRMED needs `required` successes in `attempts` completed attempts AND a pass from an independent
|
|
8
|
+
// verifier that receives only {revision, reproducer, findingId, oracle} in a fresh environment.
|
|
9
|
+
// * "Not reproduced" is reported as such; it is never reported as safe.
|
|
10
|
+
import { randomBytes } from 'node:crypto';
|
|
11
|
+
import { advance, fromProofTier, toProofTier, stateRank as stateRankOf } from './states.js';
|
|
12
|
+
import { runAttempt } from './runner.js';
|
|
13
|
+
import { validateReproducer, reproducerDigest, verifierView, sha256, canonicalize } from './reproducer.js';
|
|
14
|
+
import { proofTierOf } from '../proof-tier.js';
|
|
15
|
+
|
|
16
|
+
const PROOF_POLICY_DEFAULTS = Object.freeze({ attempts: 3, required: 3, independentReplay: true, timeoutMs: 20_000, cpus: 1, memoryMb: 512, network: 'none', requireContainer: false });
|
|
17
|
+
const RESULT_SCHEMA = 'agentic-security/proof-result';
|
|
18
|
+
|
|
19
|
+
function normalizePolicy(p = {}) {
|
|
20
|
+
const x = { ...PROOF_POLICY_DEFAULTS, ...p };
|
|
21
|
+
x.attempts = Math.max(1, Math.min(10, Math.trunc(Number(x.attempts)) || PROOF_POLICY_DEFAULTS.attempts));
|
|
22
|
+
x.required = Math.max(1, Math.min(x.attempts, Math.trunc(Number(x.required)) || x.attempts));
|
|
23
|
+
x.timeoutMs = Math.max(1000, Math.min(120_000, Number(x.timeoutMs) || PROOF_POLICY_DEFAULTS.timeoutMs));
|
|
24
|
+
return x;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const summarize = (a) => ({ status: a.status, fired: a.oracle?.fired === true, inconclusive: a.oracle?.inconclusive === true, reason: a.oracle?.reason ?? a.reasons?.[0] ?? null, durationMs: a.durationMs, degraded: a.degraded });
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Independent verifier. Takes a verifierView ONLY; any extra property (a model's reasoning, prior results) is rejected so
|
|
31
|
+
* the isolation of its inputs is checkable rather than a convention.
|
|
32
|
+
*/
|
|
33
|
+
export async function independentVerify(view, opts = {}) {
|
|
34
|
+
const allowed = new Set(['revision', 'reproducer', 'findingId', 'oracle']);
|
|
35
|
+
const extra = Object.keys(view || {}).filter((k) => !allowed.has(k));
|
|
36
|
+
if (extra.length) return { status: 'rejected', reason: `verifier input must be exactly revision, reproducer, findingId, oracle; got extra: ${extra.join(', ')}` };
|
|
37
|
+
const attempt = await (opts.attemptFn ?? runAttempt)(structuredClone(view.reproducer), { ...opts, preferContainer: opts.preferContainer });
|
|
38
|
+
return { status: attempt.status === 'completed' ? (attempt.oracle?.fired ? 'confirmed' : 'not-reproduced') : 'incomplete', attempt: summarize(attempt), environment: attempt.environment ?? null };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {object} a
|
|
43
|
+
* @param {object} a.finding the finding (id/stableId, severity, proofTier ...)
|
|
44
|
+
* @param {string} [a.revision] commit the proof applies to
|
|
45
|
+
* @param {object} a.reproducer serialized reproducer
|
|
46
|
+
* @param {object} [a.policy]
|
|
47
|
+
* @param {object} [a.deps] test seams: { attemptFn }
|
|
48
|
+
*/
|
|
49
|
+
export async function proveFinding({ finding, revision = null, reproducer, policy = {}, signal, deps = {}, sourceState = null, sourceRoot = null, overlay = null } = {}) {
|
|
50
|
+
const started = Date.now();
|
|
51
|
+
const pol = normalizePolicy(policy);
|
|
52
|
+
const attemptFn = deps.attemptFn ?? runAttempt;
|
|
53
|
+
const findingId = finding?.stableId ?? finding?.id ?? reproducer?.findingId ?? 'unknown';
|
|
54
|
+
const degradation = [];
|
|
55
|
+
let state = sourceState ?? fromProofTier(proofTierOf(finding || {}));
|
|
56
|
+
if (state === 'REPRODUCED') state = 'STATICALLY_CORROBORATED'; // an old single-run marker proof must be re-earned here
|
|
57
|
+
const startState = state;
|
|
58
|
+
|
|
59
|
+
const result = (extra) => ({
|
|
60
|
+
schema: RESULT_SCHEMA, version: 1, findingId, revision, startState, state, staticCorroboration: stateRankOf(startState) >= 1, tier: toProofTier(state),
|
|
61
|
+
oracle: reproducer?.oracle?.type ?? null, policy: pol,
|
|
62
|
+
reproducerDigest: reproducer ? safeDigest(reproducer) : null,
|
|
63
|
+
degradation, timing: { proofMs: Date.now() - started }, ...extra,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const errs = reproducer ? validateReproducer(reproducer) : ['no reproducer supplied'];
|
|
67
|
+
if (errs.length) { degradation.push('invalid-reproducer'); return result({ completeness: 'INCOMPLETE', outcome: 'invalid-reproducer', reasons: errs, attempts: { required: pol.required, run: 0, successes: 0, incomplete: 0 }, independentReplay: { status: 'not-run' } }); }
|
|
68
|
+
|
|
69
|
+
// A valid reproducer is itself the REPRODUCER_GENERATED rung. Static corroboration is recorded, not required: an exploit
|
|
70
|
+
// that reproduces under an independent oracle is deterministic corroboration in its own right, and a pattern-only finding
|
|
71
|
+
// must not be unable to earn proof merely because the taint engine never saw it. The record says which it was.
|
|
72
|
+
const staticCorroboration = stateRankOf(startState) >= stateRankOf('STATICALLY_CORROBORATED');
|
|
73
|
+
if (!staticCorroboration) degradation.push('no-static-corroboration');
|
|
74
|
+
state = 'REPRODUCER_GENERATED';
|
|
75
|
+
|
|
76
|
+
const attempts = [];
|
|
77
|
+
for (let i = 0; i < pol.attempts; i++) {
|
|
78
|
+
if (signal?.aborted) { degradation.push('cancelled'); break; }
|
|
79
|
+
const a = await attemptFn(structuredClone(reproducer), { limits: { timeoutMs: pol.timeoutMs }, signal, requireContainer: pol.requireContainer, preferContainer: pol.preferContainer, force: pol.force, sourceRoot, overlay });
|
|
80
|
+
attempts.push(a);
|
|
81
|
+
// Stop early once the threshold can no longer be met only if every attempt is a decisive negative; incomplete runs continue.
|
|
82
|
+
}
|
|
83
|
+
const completed = attempts.filter((a) => a.status === 'completed' && !a.oracle?.inconclusive);
|
|
84
|
+
const successes = completed.filter((a) => a.oracle?.fired).length;
|
|
85
|
+
const incomplete = attempts.length - completed.length;
|
|
86
|
+
for (const a of attempts) for (const d of a.degraded || []) if (!degradation.includes(d)) degradation.push(d);
|
|
87
|
+
if (incomplete) degradation.push(`incomplete-attempts:${incomplete}`);
|
|
88
|
+
const env = attempts.find((a) => a.environment)?.environment ?? null;
|
|
89
|
+
const base = {
|
|
90
|
+
environment: env && { mode: env.mode, assurance: env.assurance, backend: env.backend ?? null, note: env.note ?? null },
|
|
91
|
+
build: attempts.some((a) => a.status === 'build-failed') ? 'failed' : attempts.length ? 'ok' : 'not-run',
|
|
92
|
+
attempts: { required: pol.required, run: attempts.length, successes, incomplete, detail: attempts.map(summarize) },
|
|
93
|
+
artifactHashes: { reproducer: safeDigest(reproducer), observations: sha256(canonicalize(attempts.map((a) => a.observation ?? null))) },
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (successes > 0) {
|
|
97
|
+
const r = advance(state, 'REPRODUCED'); if (r.ok) state = r.state;
|
|
98
|
+
}
|
|
99
|
+
if (!attempts.length) return result({ ...base, completeness: 'INCOMPLETE', outcome: 'not-run', independentReplay: { status: 'not-run' } });
|
|
100
|
+
if (successes === 0) {
|
|
101
|
+
const decisive = completed.length === attempts.length;
|
|
102
|
+
return result({ ...base, completeness: decisive ? 'FULL' : 'INCOMPLETE', outcome: decisive ? 'not-reproduced' : 'inconclusive', note: 'not reproduced is not proof of safety', independentReplay: { status: 'not-run' } });
|
|
103
|
+
}
|
|
104
|
+
if (successes < pol.required || incomplete) {
|
|
105
|
+
return result({ ...base, completeness: incomplete ? 'INCOMPLETE' : 'DEGRADED', outcome: 'reproduced-below-threshold', independentReplay: { status: 'not-run' } });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let independent = { status: 'skipped' };
|
|
109
|
+
if (pol.independentReplay) {
|
|
110
|
+
independent = await independentVerify(verifierView({ revision, reproducer, findingId, oracle: reproducer.oracle }), { attemptFn, limits: { timeoutMs: pol.timeoutMs }, signal, requireContainer: pol.requireContainer, force: pol.force, sourceRoot, overlay });
|
|
111
|
+
if (independent.status !== 'confirmed') degradation.push(`independent-replay:${independent.status}`);
|
|
112
|
+
} else degradation.push('independent-replay-disabled');
|
|
113
|
+
if (independent.status === 'confirmed') {
|
|
114
|
+
const r = advance(state, 'EXPLOIT_CONFIRMED'); if (r.ok) state = r.state;
|
|
115
|
+
}
|
|
116
|
+
const full = state === 'EXPLOIT_CONFIRMED' && base.environment?.assurance !== 'none';
|
|
117
|
+
return result({ ...base, independentReplay: independent, completeness: full ? (base.environment.assurance === 'lower' ? 'DEGRADED' : 'FULL') : 'INCOMPLETE', outcome: full ? 'exploit-confirmed' : 'reproduced-unverified' });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const safeDigest = (r) => { try { return reproducerDigest(r); } catch { return null; } };
|
|
121
|
+
|
|
122
|
+
/** Severity policy: optional, never on by default. Returns the finding unchanged unless the policy asks to demote. */
|
|
123
|
+
export function applySeverityPolicy(finding, evidenceState, policy) {
|
|
124
|
+
const req = policy?.criticalRequires;
|
|
125
|
+
if (!Array.isArray(req) || !req.length || finding?.severity !== 'critical') return finding;
|
|
126
|
+
const ok = req.some((s) => stateRankOf(evidenceState ?? 'CANDIDATE') >= stateRankOf(s));
|
|
127
|
+
return ok ? finding : { ...finding, severity: 'high', severityPolicy: { demotedFrom: 'critical', reason: `critical requires ${req.join(' or ')}; evidence state is ${evidenceState ?? 'CANDIDATE'}` } };
|
|
128
|
+
}
|
|
129
|
+
export const _newRunId = () => randomBytes(6).toString('hex');
|