@clear-capabilities/agentic-security-scanner 0.166.0 → 0.168.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 +91 -0
- package/bin/agentic-security.js +40 -4
- package/dist/2199.index.js +348 -0
- package/dist/3736.index.js +2 -1
- 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/6932.index.js +1190 -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 +6 -0
- package/src/posture/fleet/cli.js +103 -0
- package/src/posture/fleet/fleet-graph.js +142 -0
- package/src/posture/fleet/fleet-manifest.js +77 -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
|
+
9444e40759885491c495ceb57ff8e0e475ee277434236667a9ebf8ac9d29be83 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.168.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 test/fleet/fleet-graph.test.js test/fleet/fleet-manifest.test.js test/fleet/fleet-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,11 @@ 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.' },
|
|
124
|
+
{ name: 'fleet-state.json', kind: 'file', classification: 'generated', retentionClass: 'scan', source: 'src/posture/fleet/cli.js, src/posture/fleet.js (loadFleetState/_saveFleetState)', note: 'Per-repository resume checkpoint for `fleet scan` — which fleet repositories are already complete, so a resumed run skips them. Scanner-managed; a plain reset may delete it.' },
|
|
125
|
+
{ name: 'fleet-rollup.json', kind: 'file', classification: 'generated', retentionClass: 'scan', source: 'src/posture/fleet/cli.js', note: 'The last fleet rollup (rollupFleet output), kept only so the NEXT `fleet scan` can compute a delta against it and so `fleet status` has something to read without re-scanning.' },
|
|
126
|
+
{ name: 'fleet-graph.json', kind: 'file', classification: 'generated', retentionClass: 'scan', source: 'src/posture/fleet/cli.js, src/posture/fleet/fleet-graph.js', note: 'The cross-repository dependency graph built from each fleet repository\'s own manifest, consumed by `fleet query --depends-on`. Rebuilt on every `fleet scan`.' },
|
|
122
127
|
{ name: 'mcp-audit.log', kind: 'file', classification: 'generated', retentionClass: 'evidence' },
|
|
123
128
|
{ 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
129
|
{ name: 'hook-throttle.json', kind: 'file', classification: 'generated', retentionClass: 'cache' },
|
|
@@ -222,6 +227,7 @@ export const ARTIFACT_REGISTRY = [
|
|
|
222
227
|
{ name: 'rules.yml', kind: 'file', classification: 'operator-config' },
|
|
223
228
|
{ name: 'rules', kind: 'dir', classification: 'operator-config' },
|
|
224
229
|
{ name: 'rules-proposed', kind: 'dir', classification: 'operator-config', note: 'proposed rules awaiting human review — not yet approved config, but not scanner-regenerable either' },
|
|
230
|
+
{ 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
231
|
{ name: 'license-policy.yml', kind: 'file', classification: 'operator-config' },
|
|
226
232
|
{ name: 'trusted-keys.json', kind: 'file', classification: 'operator-config' },
|
|
227
233
|
{ name: 'ruleset-version.json', kind: 'file', classification: 'operator-config', note: 'pinning intent, hand-set' },
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// `agentic-security fleet init|scan|status|resume|query`.
|
|
2
|
+
//
|
|
3
|
+
// Reuses the existing posture/fleet.js (runFleet: bounded concurrency, resumable checkpoint, per-repo isolation,
|
|
4
|
+
// honest rollup) for the scanning half — this module adds only the manifest-driven entry point and the
|
|
5
|
+
// cross-repository dependency graph (fleet-graph.js) that runFleet has no notion of.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { loadFleetManifest, starterManifestYaml } from './fleet-manifest.js';
|
|
9
|
+
import { buildFleetDependencyGraph, blastRadius } from './fleet-graph.js';
|
|
10
|
+
import { runFleet, rollupFleet, renderFleetSummary } from '../fleet.js';
|
|
11
|
+
import { statePath, ensureStateDir, safeWriteState } from '../state-dir.js';
|
|
12
|
+
|
|
13
|
+
const GRAPH_FILE = 'fleet-graph.json';
|
|
14
|
+
const STATE_FILE = 'fleet-state.json';
|
|
15
|
+
|
|
16
|
+
function graphPath(root) { return statePath(root, GRAPH_FILE); }
|
|
17
|
+
function statePathFor(root) { return statePath(root, STATE_FILE); }
|
|
18
|
+
|
|
19
|
+
export async function cmdFleet(args, root) {
|
|
20
|
+
const sub = args._[1];
|
|
21
|
+
if (sub === 'init') return cmdInit(args, root);
|
|
22
|
+
if (sub === 'scan') return cmdScan(args, root);
|
|
23
|
+
if (sub === 'status') return cmdStatus(args, root);
|
|
24
|
+
if (sub === 'resume') return cmdScan(args, root, { resumeOnly: true });
|
|
25
|
+
if (sub === 'query') return cmdQuery(args, root);
|
|
26
|
+
console.error('usage: fleet init | scan <manifest.yaml> | status <manifest.yaml> | resume <manifest.yaml> | query --depends-on <repo-id> [<manifest.yaml>]');
|
|
27
|
+
return 4;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function manifestArg(args) {
|
|
31
|
+
const m = args._[2];
|
|
32
|
+
return m && !String(m).startsWith('--') ? m : 'fleet.yaml';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function cmdInit(args, root) {
|
|
36
|
+
const target = path.resolve(root, manifestArg(args) === 'fleet.yaml' ? 'fleet.yaml' : manifestArg(args));
|
|
37
|
+
if (fs.existsSync(target) && !args.flags.force) { console.error(`${target} already exists. Pass --force to overwrite.`); return 4; }
|
|
38
|
+
fs.writeFileSync(target, starterManifestYaml(args.flags.organization || 'example'));
|
|
39
|
+
console.log(`Wrote ${target}. Edit it to list your repositories, then run: agentic-security fleet scan ${path.basename(target)}`);
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function cmdScan(args, root, { resumeOnly = false } = {}) {
|
|
44
|
+
const manifestFile = path.resolve(root, manifestArg(args));
|
|
45
|
+
const loaded = loadFleetManifest(manifestFile);
|
|
46
|
+
if (!loaded.ok) { console.error(`Invalid fleet manifest: ${loaded.errors.join('; ')}`); return 4; }
|
|
47
|
+
const { manifest } = loaded;
|
|
48
|
+
|
|
49
|
+
if (!ensureStateDir(root)) { console.error('No safe state directory for this project.'); return 4; }
|
|
50
|
+
const prevRollupFile = statePath(root, 'fleet-rollup.json');
|
|
51
|
+
let previous = null;
|
|
52
|
+
try { previous = JSON.parse(fs.readFileSync(prevRollupFile, 'utf8')); } catch { /* first run */ }
|
|
53
|
+
|
|
54
|
+
const { runScan: runScanFn } = await import('../../runScan.js');
|
|
55
|
+
// runScanFn resolves to { scan, meta } already — runFleet's worker expects exactly that shape from runScan, so
|
|
56
|
+
// this must NOT wrap `scan` a second time (an earlier draft did, and every repo silently reported 0 findings:
|
|
57
|
+
// `_summarise` reads `scan.findings` defensively rather than throwing on the wrong shape).
|
|
58
|
+
const runScan = (repoPath) => runScanFn(repoPath, { provenance: false });
|
|
59
|
+
|
|
60
|
+
console.error(`[fleet] scanning ${manifest.repositories.length} repository(ies) for ${manifest.organization} (${manifest.workers} worker(s))...`);
|
|
61
|
+
const result = await runFleet({
|
|
62
|
+
repos: manifest.repositories.map((r) => r.id), concurrency: manifest.workers,
|
|
63
|
+
stateFile: statePathFor(root), resume: true, previous,
|
|
64
|
+
runScan: async (repoId) => runScan(manifest.repositories.find((r) => r.id === repoId).path),
|
|
65
|
+
onProgress: (entry) => { console.error(`[fleet] ${entry.ok ? 'done' : 'FAILED'}: ${entry.repo}${entry.ok ? ` (${entry.total} finding(s))` : ` — ${entry.error}`}`); },
|
|
66
|
+
});
|
|
67
|
+
if (resumeOnly && result.skipped.length === manifest.repositories.length) console.error('[fleet] nothing to resume — every repository was already complete.');
|
|
68
|
+
|
|
69
|
+
safeWriteState(prevRollupFile, JSON.stringify(result.rollup, null, 2));
|
|
70
|
+
const graph = buildFleetDependencyGraph(manifest.repositories);
|
|
71
|
+
safeWriteState(graphPath(root), JSON.stringify({ organization: manifest.organization, builtAt: new Date().toISOString(), ...graph }, null, 2));
|
|
72
|
+
|
|
73
|
+
console.log(renderFleetSummary(result.rollup));
|
|
74
|
+
console.log(`\nRepositories requested: ${manifest.repositories.length}`);
|
|
75
|
+
console.log(`Complete: ${result.results.filter((r) => r.ok).length + result.skipped.length}`);
|
|
76
|
+
console.log(`Failed: ${result.results.filter((r) => !r.ok).length}`);
|
|
77
|
+
const anyFailed = result.results.some((r) => !r.ok);
|
|
78
|
+
return anyFailed ? 1 : 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function cmdStatus(args, root) {
|
|
82
|
+
const f = statePath(root, 'fleet-rollup.json');
|
|
83
|
+
if (!fs.existsSync(f)) { console.error('No fleet scan has been run yet. Run `agentic-security fleet scan <manifest.yaml>` first.'); return 4; }
|
|
84
|
+
const rollup = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
85
|
+
if (args.flags.json) console.log(JSON.stringify(rollup, null, 2));
|
|
86
|
+
else console.log(renderFleetSummary(rollup));
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function cmdQuery(args, root) {
|
|
91
|
+
const target = args.flags['depends-on'];
|
|
92
|
+
if (!target) { console.error('usage: fleet query --depends-on <repo-id>'); return 4; }
|
|
93
|
+
const f = graphPath(root);
|
|
94
|
+
if (!fs.existsSync(f)) { console.error('No fleet dependency graph yet. Run `agentic-security fleet scan <manifest.yaml>` first.'); return 4; }
|
|
95
|
+
const graph = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
96
|
+
if (!graph.nodes.some((n) => n.id === target)) { console.error(`${target} is not a repository in the last fleet scan.`); return 4; }
|
|
97
|
+
const radius = blastRadius(graph, target);
|
|
98
|
+
if (args.flags.json) { console.log(JSON.stringify({ target, blastRadius: radius }, null, 2)); return 0; }
|
|
99
|
+
if (!radius.length) { console.log(`No fleet repository depends on ${target} (by declared package name).`); return 0; }
|
|
100
|
+
console.log(`Repositories depending on ${target} (directly or transitively):`);
|
|
101
|
+
for (const r of radius) console.log(` ${r.id} (depth ${r.depth}, via ${r.path.slice(1).join(' -> ')})`);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// The cross-repository dependency graph for the fleet feature (PRD 4).
|
|
2
|
+
//
|
|
3
|
+
// Reads each repo's own manifest to learn (a) the package name it publishes under, if any, and (b) the package
|
|
4
|
+
// names it depends on. An edge A -> B means "A declares a dependency that resolves to B's own published name" —
|
|
5
|
+
// i.e. B is upstream of A, and a defect in B can affect A. This is a real, lightweight cross-repo dependency graph
|
|
6
|
+
// built purely from manifests already on disk; it does not need deployment config (Kubernetes/compose/Terraform)
|
|
7
|
+
// the way the existing deployment-boundary graph does, so a fleet of plain application repos works out of the box.
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
const MANIFESTS = Object.freeze([
|
|
12
|
+
{ file: 'package.json', ecosystem: 'npm' },
|
|
13
|
+
{ file: 'go.mod', ecosystem: 'go' },
|
|
14
|
+
{ file: 'Cargo.toml', ecosystem: 'cargo' },
|
|
15
|
+
{ file: 'requirements.txt', ecosystem: 'pypi' },
|
|
16
|
+
{ file: 'pyproject.toml', ecosystem: 'pypi' },
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
function readBounded(p, maxBytes = 1024 * 1024) {
|
|
20
|
+
try {
|
|
21
|
+
const st = fs.statSync(p);
|
|
22
|
+
if (!st.isFile() || st.size > maxBytes) return null;
|
|
23
|
+
return fs.readFileSync(p, 'utf8');
|
|
24
|
+
} catch { return null; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parsePackageJson(text) {
|
|
28
|
+
let j;
|
|
29
|
+
try { j = JSON.parse(text); } catch { return { name: null, deps: new Set() }; }
|
|
30
|
+
const deps = new Set([...Object.keys(j.dependencies || {}), ...Object.keys(j.devDependencies || {}), ...Object.keys(j.peerDependencies || {})]);
|
|
31
|
+
return { name: typeof j.name === 'string' ? j.name : null, deps };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseGoMod(text) {
|
|
35
|
+
const m = /^module\s+(\S+)/m.exec(text);
|
|
36
|
+
const deps = new Set();
|
|
37
|
+
const block = /require\s*\(([\s\S]*?)\)/.exec(text);
|
|
38
|
+
const lines = block ? block[1].split('\n') : text.split('\n').filter((l) => /^require\s/.test(l.trim()));
|
|
39
|
+
for (const raw of lines) {
|
|
40
|
+
const l = raw.replace(/^require\s+/, '').trim();
|
|
41
|
+
const dm = /^(\S+)\s+v?\S+/.exec(l);
|
|
42
|
+
if (dm) deps.add(dm[1]);
|
|
43
|
+
}
|
|
44
|
+
return { name: m ? m[1] : null, deps };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseCargoToml(text) {
|
|
48
|
+
const nm = /\[package\][\s\S]*?\bname\s*=\s*"([^"]+)"/.exec(text);
|
|
49
|
+
const deps = new Set();
|
|
50
|
+
const section = /\[dependencies\]([\s\S]*?)(\n\[|$)/.exec(text);
|
|
51
|
+
if (section) {
|
|
52
|
+
for (const line of section[1].split('\n')) {
|
|
53
|
+
const dm = /^\s*([A-Za-z0-9_-]+)\s*=/.exec(line);
|
|
54
|
+
if (dm) deps.add(dm[1]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { name: nm ? nm[1] : null, deps };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseRequirementsTxt(text) {
|
|
61
|
+
const deps = new Set();
|
|
62
|
+
for (const raw of text.split('\n')) {
|
|
63
|
+
const l = raw.trim();
|
|
64
|
+
if (!l || l.startsWith('#') || l.startsWith('-')) continue;
|
|
65
|
+
const dm = /^([A-Za-z0-9._-]+)/.exec(l);
|
|
66
|
+
if (dm) deps.add(dm[1].toLowerCase());
|
|
67
|
+
}
|
|
68
|
+
return { name: null, deps };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parsePyprojectToml(text) {
|
|
72
|
+
const nm = /\[project\][\s\S]*?\bname\s*=\s*"([^"]+)"/.exec(text);
|
|
73
|
+
const deps = new Set();
|
|
74
|
+
const section = /dependencies\s*=\s*\[([\s\S]*?)\]/.exec(text);
|
|
75
|
+
if (section) {
|
|
76
|
+
for (const m of section[1].matchAll(/"([A-Za-z0-9._-]+)/g)) deps.add(m[1].toLowerCase());
|
|
77
|
+
}
|
|
78
|
+
return { name: nm ? nm[1] : null, deps };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const PARSERS = { 'package.json': parsePackageJson, 'go.mod': parseGoMod, 'Cargo.toml': parseCargoToml, 'requirements.txt': parseRequirementsTxt, 'pyproject.toml': parsePyprojectToml };
|
|
82
|
+
|
|
83
|
+
/** One repo's declared identity and dependency names, merged across every manifest file it has. */
|
|
84
|
+
export function readRepoManifest(repoPath) {
|
|
85
|
+
let name = null;
|
|
86
|
+
const deps = new Set();
|
|
87
|
+
const ecosystems = new Set();
|
|
88
|
+
for (const { file, ecosystem } of MANIFESTS) {
|
|
89
|
+
const text = readBounded(path.join(repoPath, file));
|
|
90
|
+
if (text == null) continue;
|
|
91
|
+
const parsed = PARSERS[file](text);
|
|
92
|
+
if (parsed.name && !name) name = parsed.name;
|
|
93
|
+
for (const d of parsed.deps) deps.add(d);
|
|
94
|
+
ecosystems.add(ecosystem);
|
|
95
|
+
}
|
|
96
|
+
return { name, deps, ecosystems: [...ecosystems] };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build the fleet dependency graph. `repos`: [{id, path}]. An edge {from, to} means `from` depends on `to` (so a
|
|
101
|
+
* defect in `to` can affect `from`). Matching is by declared package NAME only — two repos that happen to share a
|
|
102
|
+
* name under different ecosystems are not disambiguated, a disclosed limit, not a guess.
|
|
103
|
+
*/
|
|
104
|
+
export function buildFleetDependencyGraph(repos) {
|
|
105
|
+
const manifests = new Map();
|
|
106
|
+
for (const r of repos) manifests.set(r.id, { ...r, manifest: readRepoManifest(r.path) });
|
|
107
|
+
|
|
108
|
+
const byName = new Map();
|
|
109
|
+
for (const [id, r] of manifests) if (r.manifest.name) {
|
|
110
|
+
if (!byName.has(r.manifest.name)) byName.set(r.manifest.name, []);
|
|
111
|
+
byName.get(r.manifest.name).push(id);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const edges = [];
|
|
115
|
+
for (const [id, r] of manifests) {
|
|
116
|
+
for (const dep of r.manifest.deps) {
|
|
117
|
+
const targets = byName.get(dep);
|
|
118
|
+
if (!targets) continue;
|
|
119
|
+
for (const to of targets) if (to !== id) edges.push({ from: id, to, via: dep });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const nodes = [...manifests.values()].map((r) => ({ id: r.id, path: r.path, declaredName: r.manifest.name, ecosystems: r.manifest.ecosystems }));
|
|
123
|
+
return { nodes, edges };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Transitive downstream closure: which fleet repos depend on `repoId`, directly or transitively, with the shortest path to each. */
|
|
127
|
+
export function blastRadius(graph, repoId) {
|
|
128
|
+
const incoming = new Map();
|
|
129
|
+
for (const e of graph.edges) { if (!incoming.has(e.to)) incoming.set(e.to, []); incoming.get(e.to).push(e.from); }
|
|
130
|
+
const best = new Map();
|
|
131
|
+
const queue = [{ id: repoId, path: [repoId] }];
|
|
132
|
+
while (queue.length) {
|
|
133
|
+
const { id, path: p } = queue.shift();
|
|
134
|
+
for (const from of incoming.get(id) || []) {
|
|
135
|
+
if (best.has(from)) continue;
|
|
136
|
+
const newPath = [...p, from];
|
|
137
|
+
best.set(from, { depth: newPath.length - 1, path: newPath });
|
|
138
|
+
queue.push({ id: from, path: newPath });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return [...best.entries()].map(([id, v]) => ({ id, ...v })).sort((a, b) => a.depth - b.depth || (a.id < b.id ? -1 : 1));
|
|
142
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// The fleet manifest (PRD 4 "Fleet Manifest"). `fleet.yaml`:
|
|
2
|
+
//
|
|
3
|
+
// organization: example
|
|
4
|
+
// repositories:
|
|
5
|
+
// - id: frontend
|
|
6
|
+
// path: ../frontend
|
|
7
|
+
// workers: 4
|
|
8
|
+
// perRepoTimeout: 20m
|
|
9
|
+
//
|
|
10
|
+
// Parsing reuses util/yaml.js (the one YAML entry point in this codebase). Validation is closed-world: an unknown
|
|
11
|
+
// top-level key, a duplicate repository id, or a path that does not resolve to a real directory all refuse the
|
|
12
|
+
// whole manifest rather than silently skipping one repo.
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { load as loadYaml } from '../../util/yaml.js';
|
|
16
|
+
|
|
17
|
+
const FLEET_MANIFEST_SCHEMA_KEYS = Object.freeze(['organization', 'repositories', 'workers', 'perRepoTimeout']);
|
|
18
|
+
const DURATION_RE = /^(\d+)(s|m|h)$/;
|
|
19
|
+
|
|
20
|
+
export function parseDuration(s, fallbackMs) {
|
|
21
|
+
if (typeof s === 'number' && s > 0) return s * 1000;
|
|
22
|
+
const m = DURATION_RE.exec(String(s ?? ''));
|
|
23
|
+
if (!m) return fallbackMs;
|
|
24
|
+
const n = Number(m[1]);
|
|
25
|
+
return m[2] === 's' ? n * 1000 : m[2] === 'm' ? n * 60000 : n * 3600000;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Validate a parsed manifest object against the manifest's own base directory (for path resolution). */
|
|
29
|
+
export function validateFleetManifest(doc, { baseDir = '.' } = {}) {
|
|
30
|
+
const errors = [];
|
|
31
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return { ok: false, errors: ['manifest must be a YAML mapping'] };
|
|
32
|
+
for (const k of Object.keys(doc)) if (!FLEET_MANIFEST_SCHEMA_KEYS.includes(k)) errors.push(`unknown top-level key: ${k}`);
|
|
33
|
+
if (typeof doc.organization !== 'string' || !doc.organization) errors.push('organization is required and must be a string');
|
|
34
|
+
if (!Array.isArray(doc.repositories) || doc.repositories.length === 0) errors.push('repositories must be a non-empty array');
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const repos = [];
|
|
37
|
+
for (const [i, r] of (Array.isArray(doc.repositories) ? doc.repositories : []).entries()) {
|
|
38
|
+
if (!r || typeof r !== 'object') { errors.push(`repositories[${i}] must be a mapping`); continue; }
|
|
39
|
+
if (typeof r.id !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(r.id)) { errors.push(`repositories[${i}].id must be a lowercase kebab-case string`); continue; }
|
|
40
|
+
if (seen.has(r.id)) { errors.push(`duplicate repository id: ${r.id}`); continue; }
|
|
41
|
+
seen.add(r.id);
|
|
42
|
+
if (typeof r.path !== 'string' || !r.path) { errors.push(`repositories[${i}].path is required`); continue; }
|
|
43
|
+
const abs = path.resolve(baseDir, r.path);
|
|
44
|
+
let isDir = false;
|
|
45
|
+
try { isDir = fs.statSync(abs).isDirectory(); } catch { /* reported below */ }
|
|
46
|
+
if (!isDir) errors.push(`repositories[${i}].path does not resolve to a directory: ${r.path}`);
|
|
47
|
+
repos.push({ id: r.id, path: abs });
|
|
48
|
+
}
|
|
49
|
+
if (doc.workers != null && !(Number.isInteger(doc.workers) && doc.workers >= 1 && doc.workers <= 32)) errors.push('workers must be an integer in 1..32');
|
|
50
|
+
if (doc.perRepoTimeout != null && parseDuration(doc.perRepoTimeout, null) == null) errors.push(`perRepoTimeout is not a valid duration: ${doc.perRepoTimeout}`);
|
|
51
|
+
if (errors.length) return { ok: false, errors };
|
|
52
|
+
return {
|
|
53
|
+
ok: true, errors: [],
|
|
54
|
+
manifest: {
|
|
55
|
+
organization: doc.organization, repositories: repos,
|
|
56
|
+
workers: doc.workers ?? 4, perRepoTimeoutMs: parseDuration(doc.perRepoTimeout, 20 * 60000),
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function loadFleetManifest(filePath) {
|
|
62
|
+
let text;
|
|
63
|
+
try { text = fs.readFileSync(filePath, 'utf8'); } catch (e) { return { ok: false, errors: [`cannot read ${filePath}: ${e.message}`] }; }
|
|
64
|
+
let doc;
|
|
65
|
+
try { doc = loadYaml(text); } catch (e) { return { ok: false, errors: [`invalid YAML: ${e.message}`] }; }
|
|
66
|
+
return validateFleetManifest(doc, { baseDir: path.dirname(path.resolve(filePath)) });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function starterManifestYaml(organization = 'example') {
|
|
70
|
+
return `organization: ${organization}
|
|
71
|
+
repositories:
|
|
72
|
+
- id: example-repo
|
|
73
|
+
path: .
|
|
74
|
+
workers: 4
|
|
75
|
+
perRepoTimeout: 20m
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
@@ -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
|
+
}
|