@clear-capabilities/agentic-security-scanner 0.168.0 → 0.170.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/bin/agentic-security.js +567 -0
  3. package/dist/2199.index.js +1 -1
  4. package/dist/2675.index.js +174 -0
  5. package/dist/2923.index.js +369 -1
  6. package/dist/4142.index.js +6 -6
  7. package/dist/4700.index.js +151 -0
  8. package/dist/5014.index.js +2 -2
  9. package/dist/5086.index.js +645 -0
  10. package/dist/5144.index.js +2 -2
  11. package/dist/5206.index.js +32 -0
  12. package/dist/642.index.js +197 -0
  13. package/dist/6447.index.js +131 -0
  14. package/dist/6645.index.js +87 -0
  15. package/dist/6730.index.js +2 -2
  16. package/dist/6932.index.js +2 -2
  17. package/dist/7055.index.js +369 -0
  18. package/dist/7232.index.js +24 -0
  19. package/dist/7552.index.js +2 -2
  20. package/dist/7952.index.js +301 -0
  21. package/dist/8714.index.js +2 -2
  22. package/dist/9102.index.js +110 -0
  23. package/dist/9800.index.js +1 -1
  24. package/dist/agentic-security.mjs +2 -2
  25. package/dist/agentic-security.mjs.sha256 +1 -1
  26. package/package.json +3 -3
  27. package/src/artifact/CLAUDE.md +100 -0
  28. package/src/artifact/filesystem.js +100 -0
  29. package/src/artifact/image-archive.js +159 -0
  30. package/src/artifact/inventory.js +69 -0
  31. package/src/artifact/layer-secrets.js +92 -0
  32. package/src/artifact/mismatch.js +72 -0
  33. package/src/artifact/provenance-graph.js +115 -0
  34. package/src/artifact/safe-tar.js +175 -0
  35. package/src/artifact/signature.js +156 -0
  36. package/src/artifact/vex.js +94 -0
  37. package/src/artifact/vuln-correlate.js +17 -0
  38. package/src/lineage/CLAUDE.md +19 -0
  39. package/src/lineage/observation-adapters.js +368 -0
  40. package/src/lineage/runtime-assurance.js +135 -0
  41. package/src/lineage/runtime-observation.js +37 -4
  42. package/src/posture/artifact-registry.js +8 -0
@@ -1 +1 @@
1
- 9444e40759885491c495ceb57ff8e0e475ee277434236667a9ebf8ac9d29be83 agentic-security.mjs
1
+ 40d5efd2eaa9410528e168bd5055148ac0de203d57c44b68abc12c25618db762 agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.168.0",
3
+ "version": "0.170.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,11 +74,11 @@
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 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",
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 test/artifact/safe-tar.test.js test/artifact/image-archive.test.js test/artifact/filesystem.test.js test/artifact/layer-secrets.test.js test/artifact/inventory.test.js test/artifact/vuln-correlate.test.js test/artifact/signature.test.js test/artifact/provenance-graph.test.js test/artifact/mismatch.test.js test/artifact/vex.test.js test/cli/artifact-provenance-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",
81
- "test:lineage": "node --test test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js",
81
+ "test:lineage": "node --test test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js test/lineage/runtime-assurance.test.js test/cli/runtime-twin-cli.test.js",
82
82
  "test:bench-modules": "node --test test/phase4-harness.test.js test/pipeline.test.js test/proof-corpus-lib.test.js test/proof-corpus-runner.test.js",
83
83
  "test:server": "node --test test/server/graph-loader.test.js test/server/security.test.js test/server/routes.test.js test/server/http-server.test.js test/server/cmd-explore.test.js test/server/cmd-dataflow-export.test.js test/server/static-assets.test.js test/server/dataflow-scenario-cli.test.js test/server/dataflow-impact-cli.test.js",
84
84
  "test:haskell": "node --test test/haskell/haskell-injection.test.js test/haskell/haskell-ir.test.js test/haskell/haskell-manifests.test.js test/haskell/haskell-parser.test.js test/haskell/haskell-remediation.test.js test/haskell/haskell-resolved-graph.test.js test/haskell/haskell-sca.test.js test/haskell/haskell-sca-severity.test.js test/haskell/haskell-web-guards.test.js test/haskell/haskell-advisory-feed.test.js test/haskell/haskell-security-rules.test.js test/haskell/haskell-taint.test.js test/haskell/haskell-boundaries.test.js test/haskell/haskell-live-feed-scan.test.js test/haskell/haskell-supply-bounds.test.js test/haskell/haskell-ir-scaling.test.js",
@@ -0,0 +1,100 @@
1
+ # src/artifact/
2
+
3
+ PRD 5 — Source-to-Artifact Security & Provenance. Binds a finding to the actual **built artifact** (a
4
+ container image, today) that ships it, closing the gap between "this source file has a vulnerability" and
5
+ "this specific image digest contains it." Isolated from every other subsystem this package already uses the
6
+ word "provenance" for — see the naming section below before adding a new field or function name here.
7
+
8
+ ## Naming discipline (read this before adding a field)
9
+
10
+ This repo already has 5 unrelated uses of "provenance"/"artifact": `posture/provenance/`'s `findingProvenance`
11
+ (git-commit origin), `posture/provenance.js`'s `annotateFindingProvenance` (parser corroboration),
12
+ `sca/sigstore-verify.js`'s `annotateProvenance` (package-build Sigstore verification), a pre-existing bare
13
+ `finding.provenance` field, and `posture/artifact-registry.js` (internal `.agentic-security/*` state
14
+ classification — nothing to do with container images). **Never** introduce a bare `.provenance`/`*Provenance`
15
+ field or a module/field literally named `artifact-registry` here. This package's own graph field is
16
+ `artifactProvenanceGraph`, built by `buildArtifactProvenanceGraph` — the compound name is deliberate. The
17
+ PRD's own literal CLI surface (`agentic-security provenance trace`) is honored as user-facing text regardless;
18
+ the discipline is about internal names, not the CLI vocabulary the PRD mandates.
19
+
20
+ ## Module map (read in this order — each builds on the last)
21
+
22
+ - `safe-tar.js` — hardened POSIX/GNU tar reader. Every byte any other module in this package reads passes
23
+ through here first. Hard caps on entry count/size/archive size (`DEFAULT_LIMITS`), gzip decompression with
24
+ Node's native `maxOutputLength` guard (no custom streaming counter needed — see the file header), and an
25
+ entry-name safety check (`isSafeEntryName`) that flags `..`/absolute/NUL paths rather than normalizing
26
+ them. No caller ever writes extracted content to a real filesystem path — every result is an in-memory
27
+ `Map`/object keyed by the (safety-checked) tar entry name, which is what makes zip-slip structurally
28
+ impossible here rather than merely guarded against.
29
+ - `image-archive.js` — parses a `docker save`-shaped archive (`manifest.json` + a config blob + per-layer
30
+ tars) or an OCI-layout archive (`oci-layout` + `index.json` + `blobs/sha256/*`) into one normalized shape.
31
+ Rejects (never guesses) a multi-image archive, a missing referenced blob, or malformed JSON.
32
+ - `filesystem.js` — merges an image's layers in order, applying AUFS/overlay whiteout semantics
33
+ (`.wh.<name>` deletes, `.wh..wh..opq` makes a directory opaque to earlier layers). `readLayers` keeps the
34
+ RAW per-layer, pre-merge view (what `layer-secrets.js` needs to see "this existed in layer 3" even after a
35
+ later layer's whiteout hides it); `mergeLayers` produces the final filesystem view, bounded by
36
+ `DEFAULT_FS_LIMITS` (per-file and total materialization caps — large/binary files are tracked in `files`
37
+ metadata but never fully read into memory).
38
+ - `inventory.js` — PRD P0 §1-2 (OCI image scanning + inventory). Ties the above together and feeds the merged
39
+ filesystem's text content through the EXISTING SCA pipeline: `sca/image-packages.js#extractImagePackages`
40
+ for OS packages, and an injected `manifestParser` (the engine's own `parseManifests`, from `sca/index.js`)
41
+ for application packages — no parallel manifest-parsing logic. `baseImage` is read ONLY from the
42
+ `org.opencontainers.image.base.name`/`.digest` labels when present; absent that, it is `null`, never a
43
+ guess from layer-content heuristics (no base-image fingerprint table exists in this codebase, and inventing
44
+ one would produce a confident-looking wrong answer).
45
+ - `vuln-correlate.js` — PRD P0 §4. A thin wrapper handing `inventory`'s components to an injected
46
+ `queryOSVFn` (the engine's own `queryOSV`) — "reuse current SCA semantics" literally, not a reimplementation.
47
+ - `layer-secrets.js` — PRD P0 §3 (layer secret analysis). `detectFn` is injected, mirroring
48
+ `posture/secret-history.js`'s own `(filePath, rawText) -> Finding[]` detector-injection shape for the exact
49
+ same reason: this package never imports `engine.js` directly, and the real `scanCredentials` detector
50
+ already returns redacted/masked values — this module never re-touches raw content once a finding exists,
51
+ so there's no second place a secret value could leak back in. `deletedButRecoverable` is the subset of
52
+ findings whose path (or an ancestor opaque-marked directory) was removed by a LATER layer's whiteout — the
53
+ PRD's named scenario ("added in layer 3, deleted in layer 5, but remains recoverable").
54
+ - `signature.js` — PRD P0 §5. States are exactly the PRD's own four (`verified`/`invalid`/`missing`/
55
+ `unavailable`) — deliberately different words from `sigstore-verify.js`'s own vocabulary so the two are
56
+ never confused. `verifyImageSignature` is this tool's own local detached-signature convention (NOT cosign's
57
+ "simple signing" format). `verifyProvenanceAttestation` implements the REAL DSSE PAE (Pre-Authentication
58
+ Encoding) cosign/Sigstore attestations actually use — a real cosign-keyed attestation bundle verifies here
59
+ given the matching public key. Keyless/Rekor-transparency-log verification is NOT implemented (disclosed
60
+ gap; `sigstore-verify.js`'s Rekor-query pattern is the template a future increment could extend this with).
61
+ - `provenance-graph.js` — PRD P0 §6. Assembles Git Commit → Build → OCI Digest → SBOM Component → Finding →
62
+ Deployment from evidence the modules above already computed — no new detection. The Git-Commit edge's only
63
+ evidence source is the `org.opencontainers.image.revision` label; absent that, the commit node is
64
+ `known: false`, never inferred from local git history by heuristic. `deployment` is always
65
+ OPERATOR-DECLARED (`{id, environment, digest}`) — nothing in this codebase discovers "which digest is
66
+ running where" on its own (`lineage/deployment/` models topology, not running digests), so a missing
67
+ `deployment` argument means the graph simply stops short, never a fabricated edge.
68
+ - `mismatch.js` — PRD P0 §7, the PRD's own "Killer Demonstration". Compares a source-tree SCA scan against
69
+ one artifact's correlation by **OSV advisory id alone** — never by purl (which bakes in the version). Keying
70
+ by purl was tried first and is wrong: source and artifact are usually running different versions of the
71
+ same package by the entire premise of this comparison, so a purl-keyed match would report every version
72
+ difference as a double mismatch instead of asking "does this specific advisory affect each side's own
73
+ installed version." Scenario A ("Git: fixed / Production: still vulnerable") = an advisory present against
74
+ the artifact's version, absent against source's. Scenario B ("Git: vulnerable / Built artifact: absent") =
75
+ an advisory source flags whose PACKAGE (ecosystem+name, not version) never appears in the artifact's
76
+ inventory at all.
77
+ - `vex.js` — PRD P0 §8. `not_affected` is reachable from exactly two evidence paths (component absent from
78
+ the artifact; confirmed-unreachable per existing dataflow/reachability evidence) — never from the absence
79
+ of information, per the PRD's own "`not affected` requires positive evidence" rule. Symmetrically,
80
+ `affected` requires CONFIRMED reachability, not merely "OSV lists this version as vulnerable"; absent that
81
+ confirmation the honest status is `under_investigation`, never a confident `affected` the evidence doesn't
82
+ support. `fixed` requires a clean dotted-numeric version comparison against a real `fixedVersions` entry —
83
+ an unparseable version pair skips that check rather than guessing.
84
+
85
+ ## CLI (`bin/agentic-security.js`)
86
+
87
+ `image scan`/`image verify`/`provenance trace`/`supply vex` — the PRD's own literal CLI shape (NOT a mode of
88
+ the `/supply` SLASH COMMAND, which is a different layer: Claude Code UI sugar that shells out to `scan --only
89
+ sca`/`cve-watch`). A scanned artifact persists to `.agentic-security/artifact-scans/<digest-hex>.json`
90
+ (registered in `posture/artifact-registry.js`) so `provenance trace <digest>`/`supply vex <digest>` can
91
+ re-reference it without re-parsing the archive. Local-daemon image input (`docker save`) and a remote OCI
92
+ registry reference are **NOT implemented** — only `docker save`/OCI-layout TAR FILE input — disclosed in
93
+ `docs/implementation-status/source-to-artifact-provenance.md`, not silently dropped.
94
+
95
+ ## Test scope
96
+
97
+ `test/artifact/*.test.js` + `test/cli/artifact-provenance-cli.test.js`, wired into `npm run test:posture`
98
+ (same precedent as `fleet/`, `rule-compiler/`, `proof-fabric/` — not large enough for its own scoped script).
99
+ `test/helpers/build-tar-fixture.js` is a test-only tar/OCI-archive WRITER (production code only ever reads
100
+ tar, via `safe-tar.js`) — mirrors `build-git-fixture.js`'s role for the lineage/deployment suites.
@@ -0,0 +1,100 @@
1
+ // Merges an image's layers (in order) into one virtual filesystem view,
2
+ // applying AUFS/overlay whiteout semantics (`.wh.<name>` deletes a file,
3
+ // `.wh..wh..opq` makes a directory opaque to everything below it in earlier
4
+ // layers). Nothing here ever writes to a real filesystem path — the merged
5
+ // view is an in-memory Map keyed by the tar entry's (already path-safety-
6
+ // checked) name, which is what makes this immune to zip-slip by construction
7
+ // rather than by convention.
8
+ import { readTarEntries, isWhiteoutName, whiteoutTarget, DEFAULT_LIMITS } from './safe-tar.js';
9
+
10
+ export const DEFAULT_FS_LIMITS = Object.freeze({
11
+ maxFileBytes: 1 * 1024 * 1024, // per-file materialization cap
12
+ maxMaterializedTotalBytes: 256 * 1024 * 1024, // total content kept in memory
13
+ });
14
+
15
+ /**
16
+ * Reads every layer's tar into per-layer entry lists (bounded by the same
17
+ * safe-tar limits as the outer archive) without merging — the raw, ordered
18
+ * per-layer view that layer-secrets.js needs to see what a LATER layer
19
+ * deleted while it was still present in an EARLIER one.
20
+ */
21
+ export function readLayers(layers, tarLimits = DEFAULT_LIMITS) {
22
+ const result = [];
23
+ const unsafeEntries = [];
24
+ for (const layer of layers) {
25
+ const entries = [];
26
+ for (const e of readTarEntries(layer.tar, tarLimits)) {
27
+ if (e.unsafe) {
28
+ unsafeEntries.push({ layerIndex: layer.index, name: e.name });
29
+ continue;
30
+ }
31
+ entries.push(e);
32
+ }
33
+ result.push({ layerIndex: layer.index, entries });
34
+ }
35
+ return { layers: result, unsafeEntries };
36
+ }
37
+
38
+ /**
39
+ * Merges the per-layer entry lists (from readLayers) into one final
40
+ * filesystem view, applying whiteouts in layer order. Returns:
41
+ * - `files`: Map<path, { sizeBytes, sourceLayerIndex, typeflag }> — every
42
+ * surviving path, metadata only.
43
+ * - `fileContents`: { path: string } — decoded text content, capped per
44
+ * file and in total (see DEFAULT_FS_LIMITS); large/binary files are
45
+ * present in `files` but absent here.
46
+ * - `deletedPaths`: [{ path, deletedAtLayer, opaque }] — every whiteout
47
+ * seen, in order, regardless of whether the path ever existed.
48
+ * - `materializationTruncated`: true once the total content cap was hit —
49
+ * disclosed, not silent.
50
+ */
51
+ export function mergeLayers(readLayersResult, fsLimits = DEFAULT_FS_LIMITS) {
52
+ const files = new Map(); // path -> { sizeBytes, sourceLayerIndex, typeflag, data }
53
+ const deletedPaths = [];
54
+ let materializedTotal = 0;
55
+ let materializationTruncated = false;
56
+
57
+ for (const { layerIndex, entries } of readLayersResult.layers) {
58
+ const opaqueDirs = [];
59
+ for (const e of entries) {
60
+ if (isWhiteoutName(e.name)) {
61
+ const wh = whiteoutTarget(e.name);
62
+ if (wh.opaque) {
63
+ opaqueDirs.push(wh.dir);
64
+ deletedPaths.push({ path: wh.dir, deletedAtLayer: layerIndex, opaque: true });
65
+ for (const existing of [...files.keys()]) {
66
+ if (existing === wh.dir || existing.startsWith(`${wh.dir}/`)) files.delete(existing);
67
+ }
68
+ } else {
69
+ deletedPaths.push({ path: wh.path, deletedAtLayer: layerIndex, opaque: false });
70
+ files.delete(wh.path);
71
+ }
72
+ continue;
73
+ }
74
+ // An opaque marker earlier in THIS layer hides any further entry in
75
+ // this same layer that would resurrect something under that dir —
76
+ // real overlay semantics scope opaque to "everything from layers
77
+ // BELOW this one", so same-layer adds after the marker still apply.
78
+ files.set(e.name, { sizeBytes: e.size, sourceLayerIndex: layerIndex, typeflag: e.typeflag, data: e.data });
79
+ }
80
+ }
81
+
82
+ const fileContents = {};
83
+ for (const [path, meta] of files) {
84
+ if (meta.typeflag !== '0' && meta.typeflag !== '') continue; // only regular files get content
85
+ if (meta.sizeBytes > fsLimits.maxFileBytes) continue;
86
+ if (materializedTotal + meta.sizeBytes > fsLimits.maxMaterializedTotalBytes) {
87
+ materializationTruncated = true;
88
+ continue;
89
+ }
90
+ fileContents[path] = meta.data.toString('utf8');
91
+ materializedTotal += meta.sizeBytes;
92
+ }
93
+
94
+ const fileMeta = new Map();
95
+ for (const [path, meta] of files) {
96
+ fileMeta.set(path, { sizeBytes: meta.sizeBytes, sourceLayerIndex: meta.sourceLayerIndex, typeflag: meta.typeflag });
97
+ }
98
+
99
+ return { files: fileMeta, fileContents, deletedPaths, materializationTruncated };
100
+ }
@@ -0,0 +1,159 @@
1
+ // Parses a Docker-save ("docker-archive") or OCI-layout ("oci-archive") tar
2
+ // into one normalized shape, without executing or daemon-mounting the image
3
+ // (PRD 5 Safety Requirements: "OCI inspection should not require executing
4
+ // the image"). Built entirely on `safe-tar.js` — every byte read here passes
5
+ // through the same entry-count/size/path/decompression guards.
6
+ import crypto from 'node:crypto';
7
+ import { readTarEntries, decompressIfGzip, DEFAULT_LIMITS } from './safe-tar.js';
8
+
9
+ export class ImageArchiveError extends Error {
10
+ constructor(message, code) {
11
+ super(message);
12
+ this.name = 'ImageArchiveError';
13
+ this.code = code;
14
+ }
15
+ }
16
+
17
+ function safeJsonParse(buf, what) {
18
+ try {
19
+ return JSON.parse(buf.toString('utf8'));
20
+ } catch (err) {
21
+ throw new ImageArchiveError(`${what} is not valid JSON: ${err.message}`, 'malformed_manifest');
22
+ }
23
+ }
24
+
25
+ function readOuterEntries(buf, limits) {
26
+ const outer = decompressIfGzip(buf, limits);
27
+ const byName = new Map();
28
+ const unsafeEntries = [];
29
+ for (const e of readTarEntries(outer, limits)) {
30
+ if (e.unsafe) {
31
+ unsafeEntries.push({ name: e.name });
32
+ continue;
33
+ }
34
+ byName.set(e.name, e);
35
+ }
36
+ return { byName, unsafeEntries };
37
+ }
38
+
39
+ function extractConfigShape(configObj) {
40
+ const cfg = (configObj && configObj.config) || {};
41
+ return {
42
+ architecture: configObj.architecture || null,
43
+ os: configObj.os || null,
44
+ labels: (cfg.Labels && typeof cfg.Labels === 'object') ? { ...cfg.Labels } : {},
45
+ entrypoint: Array.isArray(cfg.Entrypoint) ? [...cfg.Entrypoint] : null,
46
+ cmd: Array.isArray(cfg.Cmd) ? [...cfg.Cmd] : null,
47
+ history: Array.isArray(configObj.history)
48
+ ? configObj.history.map((h) => ({ createdBy: h.created_by || null, created: h.created || null, emptyLayer: !!h.empty_layer }))
49
+ : [],
50
+ rootfsDiffIds: (configObj.rootfs && Array.isArray(configObj.rootfs.diff_ids)) ? [...configObj.rootfs.diff_ids] : [],
51
+ };
52
+ }
53
+
54
+ function parseDockerArchive(byName, outerUnsafe, limits) {
55
+ const manifestEntry = byName.get('manifest.json');
56
+ const manifestArr = safeJsonParse(manifestEntry.data, 'manifest.json');
57
+ if (!Array.isArray(manifestArr) || manifestArr.length === 0) {
58
+ throw new ImageArchiveError('manifest.json contains no images', 'empty_manifest');
59
+ }
60
+ if (manifestArr.length > 1) {
61
+ throw new ImageArchiveError(`manifest.json describes ${manifestArr.length} images; image scan expects exactly one`, 'multi_image_archive');
62
+ }
63
+ const m = manifestArr[0];
64
+ if (typeof m.Config !== 'string' || !Array.isArray(m.Layers)) {
65
+ throw new ImageArchiveError('manifest.json entry is missing Config or Layers', 'malformed_manifest');
66
+ }
67
+ const configEntry = byName.get(m.Config);
68
+ if (!configEntry) throw new ImageArchiveError(`manifest.json references missing config "${m.Config}"`, 'missing_blob');
69
+ const configObj = safeJsonParse(configEntry.data, m.Config);
70
+ const configDigest = `sha256:${crypto.createHash('sha256').update(configEntry.data).digest('hex')}`;
71
+
72
+ const layers = [];
73
+ for (let i = 0; i < m.Layers.length; i++) {
74
+ const layerPath = m.Layers[i];
75
+ const entry = byName.get(layerPath);
76
+ if (!entry) throw new ImageArchiveError(`manifest.json references missing layer "${layerPath}"`, 'missing_blob');
77
+ const tar = decompressIfGzip(entry.data, limits);
78
+ layers.push({ index: i, path: layerPath, diffId: configObj?.rootfs?.diff_ids?.[i] || null, tar, sizeBytes: entry.size });
79
+ }
80
+
81
+ return {
82
+ format: 'docker-archive',
83
+ digest: configDigest,
84
+ digestKind: 'config',
85
+ repoTags: Array.isArray(m.RepoTags) ? m.RepoTags : [],
86
+ config: extractConfigShape(configObj),
87
+ layers,
88
+ unsafeEntries: outerUnsafe,
89
+ };
90
+ }
91
+
92
+ function parseOciArchive(byName, outerUnsafe, limits) {
93
+ const indexObj = safeJsonParse(byName.get('index.json').data, 'index.json');
94
+ const manifests = Array.isArray(indexObj.manifests) ? indexObj.manifests : [];
95
+ if (manifests.length === 0) throw new ImageArchiveError('index.json lists no manifests', 'empty_manifest');
96
+ if (manifests.length > 1) {
97
+ throw new ImageArchiveError(`index.json describes ${manifests.length} manifests; image scan expects exactly one`, 'multi_image_archive');
98
+ }
99
+ const manifestDigest = manifests[0].digest;
100
+ if (typeof manifestDigest !== 'string' || !manifestDigest.startsWith('sha256:')) {
101
+ throw new ImageArchiveError('index.json manifest entry has no sha256 digest', 'malformed_manifest');
102
+ }
103
+ const manifestBlobPath = `blobs/sha256/${manifestDigest.slice('sha256:'.length)}`;
104
+ const manifestEntry = byName.get(manifestBlobPath);
105
+ if (!manifestEntry) throw new ImageArchiveError(`index.json references missing manifest blob "${manifestBlobPath}"`, 'missing_blob');
106
+ const manifestObj = safeJsonParse(manifestEntry.data, manifestBlobPath);
107
+
108
+ const configDigest = manifestObj.config && manifestObj.config.digest;
109
+ if (typeof configDigest !== 'string' || !configDigest.startsWith('sha256:')) {
110
+ throw new ImageArchiveError('image manifest has no config digest', 'malformed_manifest');
111
+ }
112
+ const configBlobPath = `blobs/sha256/${configDigest.slice('sha256:'.length)}`;
113
+ const configEntry = byName.get(configBlobPath);
114
+ if (!configEntry) throw new ImageArchiveError(`manifest references missing config blob "${configBlobPath}"`, 'missing_blob');
115
+ const configObj = safeJsonParse(configEntry.data, configBlobPath);
116
+
117
+ const layerDescs = Array.isArray(manifestObj.layers) ? manifestObj.layers : [];
118
+ const layers = [];
119
+ for (let i = 0; i < layerDescs.length; i++) {
120
+ const d = layerDescs[i];
121
+ if (typeof d.digest !== 'string' || !d.digest.startsWith('sha256:')) {
122
+ throw new ImageArchiveError(`layer ${i} has no sha256 digest`, 'malformed_manifest');
123
+ }
124
+ const blobPath = `blobs/sha256/${d.digest.slice('sha256:'.length)}`;
125
+ const entry = byName.get(blobPath);
126
+ if (!entry) throw new ImageArchiveError(`manifest references missing layer blob "${blobPath}"`, 'missing_blob');
127
+ const tar = decompressIfGzip(entry.data, limits);
128
+ layers.push({ index: i, path: blobPath, diffId: d.digest, mediaType: d.mediaType || null, tar, sizeBytes: entry.size });
129
+ }
130
+
131
+ return {
132
+ format: 'oci-archive',
133
+ digest: manifestDigest,
134
+ digestKind: 'manifest',
135
+ repoTags: [],
136
+ config: extractConfigShape(configObj),
137
+ layers,
138
+ unsafeEntries: outerUnsafe,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Parses an in-memory tar buffer (already read from disk/stdin by the
144
+ * caller — this module never touches the filesystem or network) as either a
145
+ * `docker save`-shaped archive or an OCI-layout archive. Throws
146
+ * ImageArchiveError for anything it cannot recognize or whose manifest
147
+ * references a blob that isn't present — never guesses or substitutes a
148
+ * default.
149
+ */
150
+ export function parseImageArchive(buf, limits = DEFAULT_LIMITS) {
151
+ if (!Buffer.isBuffer(buf)) throw new ImageArchiveError('image archive input must be a Buffer', 'bad_input');
152
+ const { byName, unsafeEntries } = readOuterEntries(buf, limits);
153
+ if (byName.has('manifest.json')) return parseDockerArchive(byName, unsafeEntries, limits);
154
+ if (byName.has('index.json') && byName.has('oci-layout')) return parseOciArchive(byName, unsafeEntries, limits);
155
+ throw new ImageArchiveError(
156
+ 'archive is neither a docker-archive (manifest.json) nor an oci-archive (oci-layout + index.json)',
157
+ 'unrecognized_format',
158
+ );
159
+ }
@@ -0,0 +1,69 @@
1
+ // PRD 5 P0 §1-2 — OCI image scanning + inventory. Ties image-archive.js
2
+ // (manifest/config/layer parsing) and filesystem.js (whiteout-aware layer
3
+ // merge) into the single inventory shape the PRD names: digest, OS, OS
4
+ // packages, application packages, architecture, base image, layers,
5
+ // entrypoint, build labels.
6
+ import { parseImageArchive } from './image-archive.js';
7
+ import { readLayers, mergeLayers, DEFAULT_FS_LIMITS } from './filesystem.js';
8
+ import { extractImagePackages } from '../sca/image-packages.js';
9
+ import { DEFAULT_LIMITS as DEFAULT_TAR_LIMITS } from './safe-tar.js';
10
+
11
+ // Buildkit/Docker provenance convention — not an OCI spec requirement, so
12
+ // its absence is reported as "not declared", never guessed from layer
13
+ // heuristics (no base-image fingerprint table exists in this codebase, and
14
+ // inventing one would produce a confident-looking wrong answer, which is the
15
+ // one failure mode PRD 5's own Safety Requirements single out by name for
16
+ // signature state — the same discipline applies here).
17
+ const BASE_IMAGE_NAME_LABEL = 'org.opencontainers.image.base.name';
18
+ const BASE_IMAGE_DIGEST_LABEL = 'org.opencontainers.image.base.digest';
19
+
20
+ /**
21
+ * `buf` is an already-read image archive buffer (tar, optionally
22
+ * gzip-wrapped). `manifestParser` is the engine's existing
23
+ * `parseManifests(allFileContents)` (injected, mirroring
24
+ * `src/posture/secret-history.js`'s detectFn injection, so this module never
25
+ * imports `engine.js` directly and `src/sca/index.js` stays the one seam).
26
+ * Returns `{ image, inventory }` where `image` is the raw parsed archive
27
+ * (image-archive.js's shape, kept for callers that need layer tars — vuln
28
+ * correlation, signature verification) and `inventory` is the PRD-named
29
+ * report.
30
+ */
31
+ export function buildImageInventory(buf, { manifestParser, tarLimits = DEFAULT_TAR_LIMITS, fsLimits = DEFAULT_FS_LIMITS } = {}) {
32
+ if (typeof manifestParser !== 'function') throw new TypeError('buildImageInventory requires manifestParser(allFileContents)');
33
+ const image = parseImageArchive(buf, tarLimits);
34
+ const rl = readLayers(image.layers, tarLimits);
35
+ const merged = mergeLayers(rl, fsLimits);
36
+
37
+ const osPackages = extractImagePackages(merged.fileContents);
38
+ let appPackages = [];
39
+ try {
40
+ appPackages = manifestParser(merged.fileContents) || [];
41
+ } catch {
42
+ appPackages = []; // a parser failure on hostile layer content must never abort inventory extraction
43
+ }
44
+
45
+ const labels = image.config.labels || {};
46
+ const baseImage = labels[BASE_IMAGE_NAME_LABEL]
47
+ ? { name: labels[BASE_IMAGE_NAME_LABEL], digest: labels[BASE_IMAGE_DIGEST_LABEL] || null, source: 'label' }
48
+ : null;
49
+
50
+ const inventory = {
51
+ digest: image.digest,
52
+ digestKind: image.digestKind,
53
+ repoTags: image.repoTags,
54
+ os: image.config.os,
55
+ architecture: image.config.architecture,
56
+ baseImage,
57
+ osPackages,
58
+ appPackages,
59
+ layers: image.layers.map((l) => ({ index: l.index, sizeBytes: l.sizeBytes, diffId: l.diffId || null })),
60
+ entrypoint: image.config.entrypoint,
61
+ cmd: image.config.cmd,
62
+ buildLabels: { ...labels },
63
+ history: image.config.history,
64
+ unsafeEntries: [...image.unsafeEntries, ...rl.unsafeEntries],
65
+ materializationTruncated: merged.materializationTruncated,
66
+ };
67
+
68
+ return { image, inventory, mergedFilesystem: merged, readLayersResult: rl };
69
+ }
@@ -0,0 +1,92 @@
1
+ // PRD 5 P0 §3 — Layer Secret Analysis: a secret added in one layer and later
2
+ // deleted (via an overlay whiteout) is still sitting in that earlier layer's
3
+ // raw tar bytes inside the image archive — recoverable by anyone who has the
4
+ // archive, even though it is invisible in `docker run`'s final filesystem.
5
+ //
6
+ // The actual credential detection is INJECTED as `detectFn` (the exact
7
+ // `(filePath, rawText) -> Finding[]` shape `src/posture/secret-history.js`
8
+ // already uses for the git-history analog of this same idea), so this module
9
+ // never imports `engine.js` and stays a pure, independently-testable
10
+ // transform over the layer data `filesystem.js` already extracted. The
11
+ // injected detector (`scanCredentials`) already returns only redacted/masked
12
+ // values (`masked`, a redacted `snippet`) — this module never re-touches raw
13
+ // file content once a finding exists, so there is no second place a value
14
+ // could leak back in.
15
+ import { isWhiteoutName } from './safe-tar.js';
16
+
17
+ export const DEFAULT_SECRET_SCAN_LIMITS = Object.freeze({
18
+ maxFileBytes: 512 * 1024,
19
+ });
20
+
21
+ /**
22
+ * `readLayersResult` is `filesystem.js#readLayers`'s output (ordered,
23
+ * per-layer, pre-merge entries — the only shape that preserves "this path
24
+ * existed in layer 3" even after a later layer's whiteout removes it from
25
+ * the merged view). `deletedPaths` is `mergeLayers`'s output.
26
+ *
27
+ * Returns `{ findings, deletedButRecoverable, materializationSkipped }`:
28
+ * - `findings`: every detector hit across every layer's added files, each
29
+ * tagged with `path`/`layerIndex` and the detector's own (already
30
+ * redacted) finding fields — this is the full history sweep, not just
31
+ * the deleted-but-recoverable subset.
32
+ * - `deletedButRecoverable`: the subset of `findings` whose `path` was
33
+ * later removed by a whiteout in a LATER layer — PRD 5's named scenario.
34
+ * - `materializationSkipped`: paths whose content was too large to scan
35
+ * (disclosed, never silently treated as "no secret found").
36
+ */
37
+ export function analyzeLayerSecrets({ readLayersResult, deletedPaths, detectFn, limits = DEFAULT_SECRET_SCAN_LIMITS }) {
38
+ if (typeof detectFn !== 'function') throw new TypeError('analyzeLayerSecrets requires a detectFn(filePath, rawText) -> Finding[]');
39
+ if (!readLayersResult || !Array.isArray(readLayersResult.layers)) throw new TypeError('analyzeLayerSecrets requires filesystem.js#readLayers output');
40
+
41
+ const deletedAtByPath = new Map(); // path -> earliest layer index that deleted it
42
+ for (const d of deletedPaths || []) {
43
+ if (d.opaque) continue; // opaque-directory deletions are reported separately below
44
+ if (!deletedAtByPath.has(d.path) || d.deletedAtLayer < deletedAtByPath.get(d.path)) {
45
+ deletedAtByPath.set(d.path, d.deletedAtLayer);
46
+ }
47
+ }
48
+ const opaqueDirs = (deletedPaths || []).filter((d) => d.opaque);
49
+
50
+ const findings = [];
51
+ const materializationSkipped = [];
52
+
53
+ for (const { layerIndex, entries } of readLayersResult.layers) {
54
+ for (const e of entries) {
55
+ if (isWhiteoutName(e.name)) continue;
56
+ if (e.typeflag !== '0' && e.typeflag !== '') continue; // regular files only
57
+ if (e.size > limits.maxFileBytes) {
58
+ materializationSkipped.push({ path: e.name, layerIndex, sizeBytes: e.size });
59
+ continue;
60
+ }
61
+ let text;
62
+ try {
63
+ text = e.data.toString('utf8');
64
+ } catch {
65
+ continue; // not decodable as text — not a candidate for a text-pattern secret scan
66
+ }
67
+ let hits;
68
+ try {
69
+ hits = detectFn(e.name, text) || [];
70
+ } catch {
71
+ hits = []; // a detector failure on hostile/malformed content must never abort the sweep
72
+ }
73
+ for (const hit of hits) {
74
+ const deletedAtLayer = deletedAtByPath.has(e.name) && deletedAtByPath.get(e.name) > layerIndex
75
+ ? deletedAtByPath.get(e.name)
76
+ : null;
77
+ const underOpaqueDir = opaqueDirs.some((d) => d.deletedAtLayer > layerIndex && (e.name === d.path || e.name.startsWith(`${d.path}/`)));
78
+ findings.push({
79
+ ...hit,
80
+ path: e.name,
81
+ addedAtLayer: layerIndex,
82
+ deletedAtLayer: deletedAtLayer ?? (underOpaqueDir ? opaqueDirs.find((d) => d.deletedAtLayer > layerIndex && (e.name === d.path || e.name.startsWith(`${d.path}/`))).deletedAtLayer : null),
83
+ recoverable: deletedAtLayer != null || underOpaqueDir,
84
+ });
85
+ }
86
+ }
87
+ }
88
+
89
+ const deletedButRecoverable = findings.filter((f) => f.recoverable);
90
+
91
+ return { findings, deletedButRecoverable, materializationSkipped };
92
+ }