@intentius/chant 0.13.1 → 0.15.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/dist/audit/catalog.d.ts +45 -5
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/proof.d.ts +3 -0
- package/dist/audit/proof.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +4 -1
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts +3 -0
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts +8 -2
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
- package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
- package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +39 -0
- package/dist/cli/handlers/components.d.ts.map +1 -0
- package/dist/cli/handlers/dev.d.ts +3 -0
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts +4 -2
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +49 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +35 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +22 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.d.ts +110 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -0
- package/dist/codegen/pinned-upgrade.d.ts +153 -0
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
- package/dist/codegen/rolling-upgrade.d.ts +138 -0
- package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
- package/dist/codegen/surface-snapshot.d.ts +173 -0
- package/dist/codegen/surface-snapshot.d.ts.map +1 -0
- package/dist/codegen/version-bump.d.ts +21 -0
- package/dist/codegen/version-bump.d.ts.map +1 -0
- package/dist/components/auto-release.d.ts +93 -0
- package/dist/components/auto-release.d.ts.map +1 -0
- package/dist/components/builders.d.ts +37 -0
- package/dist/components/builders.d.ts.map +1 -0
- package/dist/components/capability-plugin-loader.d.ts +111 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -0
- package/dist/components/capability-plugin-schema.d.ts +24 -0
- package/dist/components/capability-plugin-schema.d.ts.map +1 -0
- package/dist/components/capability-plugin.d.ts +85 -0
- package/dist/components/capability-plugin.d.ts.map +1 -0
- package/dist/components/capability.d.ts +89 -0
- package/dist/components/capability.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +204 -0
- package/dist/components/cli-support.d.ts.map +1 -0
- package/dist/components/component.d.ts +179 -0
- package/dist/components/component.d.ts.map +1 -0
- package/dist/components/config-defaults.d.ts +41 -0
- package/dist/components/config-defaults.d.ts.map +1 -0
- package/dist/components/discover.d.ts +60 -0
- package/dist/components/discover.d.ts.map +1 -0
- package/dist/components/driver-output.d.ts +20 -0
- package/dist/components/driver-output.d.ts.map +1 -0
- package/dist/components/driver.d.ts +209 -0
- package/dist/components/driver.d.ts.map +1 -0
- package/dist/components/index.d.ts +22 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/manifest-persistence.d.ts +79 -0
- package/dist/components/manifest-persistence.d.ts.map +1 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
- package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
- package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
- package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
- package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
- package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
- package/dist/components/pilots/lambda.pilot.d.ts +35 -0
- package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
- package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
- package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
- package/dist/components/pilots/project.d.ts +13 -0
- package/dist/components/pilots/project.d.ts.map +1 -0
- package/dist/components/presets/ecs-fargate.d.ts +59 -0
- package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
- package/dist/components/presets/index.d.ts +29 -0
- package/dist/components/presets/index.d.ts.map +1 -0
- package/dist/components/presets/lambda.d.ts +35 -0
- package/dist/components/presets/lambda.d.ts.map +1 -0
- package/dist/components/presets/single-host-compose.d.ts +43 -0
- package/dist/components/presets/single-host-compose.d.ts.map +1 -0
- package/dist/components/registry.d.ts +29 -0
- package/dist/components/registry.d.ts.map +1 -0
- package/dist/components/starter-plugin.d.ts +51 -0
- package/dist/components/starter-plugin.d.ts.map +1 -0
- package/dist/components/temporal-codegen-loader.d.ts +27 -0
- package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
- package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/bom-writer.d.ts +155 -0
- package/dist/components/verbs/bom-writer.d.ts.map +1 -0
- package/dist/components/verbs/build-archive.d.ts +205 -0
- package/dist/components/verbs/build-archive.d.ts.map +1 -0
- package/dist/components/verbs/build.d.ts +150 -0
- package/dist/components/verbs/build.d.ts.map +1 -0
- package/dist/components/verbs/cloud-executor.d.ts +88 -0
- package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
- package/dist/components/verbs/component-bom.d.ts +103 -0
- package/dist/components/verbs/component-bom.d.ts.map +1 -0
- package/dist/components/verbs/index.d.ts +49 -0
- package/dist/components/verbs/index.d.ts.map +1 -0
- package/dist/components/verbs/license-policy.d.ts +56 -0
- package/dist/components/verbs/license-policy.d.ts.map +1 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
- package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/process-runner.d.ts +64 -0
- package/dist/components/verbs/process-runner.d.ts.map +1 -0
- package/dist/components/verbs/reproducibility.d.ts +80 -0
- package/dist/components/verbs/reproducibility.d.ts.map +1 -0
- package/dist/components/verbs/sbom-generator.d.ts +157 -0
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/sbom.d.ts +79 -0
- package/dist/components/verbs/sbom.d.ts.map +1 -0
- package/dist/components/verbs/shell.d.ts +35 -0
- package/dist/components/verbs/shell.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +222 -0
- package/dist/components/verbs/sign.d.ts.map +1 -0
- package/dist/components/verbs/stub.d.ts +18 -0
- package/dist/components/verbs/stub.d.ts.map +1 -0
- package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
- package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
- package/dist/components/verbs/verify.d.ts +106 -0
- package/dist/components/verbs/verify.d.ts.map +1 -0
- package/dist/components/verbs/vex.d.ts +65 -0
- package/dist/components/verbs/vex.d.ts.map +1 -0
- package/dist/components/verbs/vuln-gate.d.ts +91 -0
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
- package/dist/components/verbs/vuln-scan.d.ts +116 -0
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
- package/dist/components/verbs/wait-verify.d.ts +85 -0
- package/dist/components/verbs/wait-verify.d.ts.map +1 -0
- package/dist/config.d.ts +205 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +78 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts +121 -0
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
- package/dist/lifecycle/build-ledger.d.ts +225 -0
- package/dist/lifecycle/build-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +84 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +5 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
- package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
- package/dist/lifecycle/release-ledger.d.ts +122 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -0
- package/dist/lifecycle/status.d.ts +150 -0
- package/dist/lifecycle/status.d.ts.map +1 -0
- package/dist/lint/component-checks.d.ts +95 -0
- package/dist/lint/component-checks.d.ts.map +1 -0
- package/dist/lint/engine.d.ts +23 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/presets/strict.json +8 -1
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
- package/dist/lint/rules/comp/index.d.ts +18 -0
- package/dist/lint/rules/comp/index.d.ts.map +1 -0
- package/dist/lint/rules/comp/support.d.ts +71 -0
- package/dist/lint/rules/comp/support.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-registry.d.ts +11 -6
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-runtime.d.ts +24 -0
- package/dist/op/activity-runtime.d.ts.map +1 -0
- package/dist/op/builders.d.ts +56 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/ownership.d.ts +20 -14
- package/dist/ownership.d.ts.map +1 -1
- package/package.json +14 -1
- package/src/audit/catalog.test.ts +74 -22
- package/src/audit/catalog.ts +74 -471
- package/src/audit/proof.ts +4 -2
- package/src/audit/report-html.test.ts +14 -7
- package/src/audit/report-model.ts +20 -9
- package/src/audit/report.test.ts +36 -13
- package/src/audit/report.ts +2 -0
- package/src/audit/rules-doc.test.ts +8 -6
- package/src/audit/rules-doc.ts +13 -5
- package/src/cli/commands/audit.ts +23 -18
- package/src/cli/commands/component-lint.test.ts +158 -0
- package/src/cli/commands/doctor.test.ts +116 -2
- package/src/cli/commands/doctor.ts +46 -0
- package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
- package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
- package/src/cli/commands/lexicon-surface-diff.ts +185 -0
- package/src/cli/commands/lint.ts +142 -1
- package/src/cli/commands/pinned-upgrade.ts +204 -0
- package/src/cli/handlers/build.ts +58 -1
- package/src/cli/handlers/components.test.ts +504 -0
- package/src/cli/handlers/components.ts +404 -0
- package/src/cli/handlers/dev.ts +60 -1
- package/src/cli/handlers/graph.ts +47 -2
- package/src/cli/handlers/misc.ts +45 -1
- package/src/cli/handlers/run.test.ts +967 -3
- package/src/cli/handlers/run.ts +618 -11
- package/src/cli/main.test.ts +66 -0
- package/src/cli/main.ts +105 -2
- package/src/cli/registry.ts +35 -0
- package/src/codegen/fetch.test.ts +84 -3
- package/src/codegen/fetch.ts +76 -2
- package/src/codegen/lexicon-regen.test.ts +298 -0
- package/src/codegen/lexicon-regen.ts +545 -0
- package/src/codegen/pinned-upgrade.test.ts +399 -0
- package/src/codegen/pinned-upgrade.ts +423 -0
- package/src/codegen/rolling-upgrade.test.ts +333 -0
- package/src/codegen/rolling-upgrade.ts +320 -0
- package/src/codegen/surface-snapshot.test.ts +461 -0
- package/src/codegen/surface-snapshot.ts +546 -0
- package/src/codegen/version-bump.test.ts +47 -0
- package/src/codegen/version-bump.ts +39 -0
- package/src/components/README.md +130 -0
- package/src/components/SPRAWL-VALIDATION.md +181 -0
- package/src/components/__fixtures__/alb-ecs-service.json +51 -0
- package/src/components/__fixtures__/dynamodb-infra.json +32 -0
- package/src/components/__fixtures__/emr-job-consumer.json +26 -0
- package/src/components/__fixtures__/jar-lib-producer.json +16 -0
- package/src/components/__fixtures__/lambda-image-processor.json +28 -0
- package/src/components/__fixtures__/neo4j-fanout.json +34 -0
- package/src/components/__fixtures__/single-host-compose.json +29 -0
- package/src/components/auto-release.test.ts +217 -0
- package/src/components/auto-release.ts +216 -0
- package/src/components/builders.test.ts +45 -0
- package/src/components/builders.ts +71 -0
- package/src/components/capability-plugin-loader.ts +218 -0
- package/src/components/capability-plugin-schema.ts +56 -0
- package/src/components/capability-plugin.test.ts +235 -0
- package/src/components/capability-plugin.ts +108 -0
- package/src/components/capability.ts +117 -0
- package/src/components/cli-support.test.ts +579 -0
- package/src/components/cli-support.ts +481 -0
- package/src/components/component-schema.test.ts +265 -0
- package/src/components/component.schema.json +235 -0
- package/src/components/component.test.ts +251 -0
- package/src/components/component.ts +255 -0
- package/src/components/config-defaults.test.ts +263 -0
- package/src/components/config-defaults.ts +145 -0
- package/src/components/discover.test.ts +352 -0
- package/src/components/discover.ts +167 -0
- package/src/components/driver-output.ts +71 -0
- package/src/components/driver.test.ts +638 -0
- package/src/components/driver.ts +690 -0
- package/src/components/index.ts +113 -0
- package/src/components/manifest-persistence.test.ts +152 -0
- package/src/components/manifest-persistence.ts +164 -0
- package/src/components/pilots/README.md +33 -0
- package/src/components/pilots/alb-ecs.pilot.ts +63 -0
- package/src/components/pilots/dynamodb.pilot.ts +59 -0
- package/src/components/pilots/jar-emr.pilot.ts +69 -0
- package/src/components/pilots/lambda.pilot.ts +55 -0
- package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
- package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
- package/src/components/pilots/pilots.test.ts +126 -0
- package/src/components/pilots/project.ts +13 -0
- package/src/components/presets/comp007-reduction.test.ts +122 -0
- package/src/components/presets/ecs-fargate.test.ts +84 -0
- package/src/components/presets/ecs-fargate.ts +103 -0
- package/src/components/presets/index.ts +32 -0
- package/src/components/presets/lambda.test.ts +35 -0
- package/src/components/presets/lambda.ts +53 -0
- package/src/components/presets/single-host-compose.test.ts +65 -0
- package/src/components/presets/single-host-compose.ts +64 -0
- package/src/components/registry.test.ts +112 -0
- package/src/components/registry.ts +37 -0
- package/src/components/starter-plugin.ts +105 -0
- package/src/components/temporal-codegen-loader.ts +41 -0
- package/src/components/verbs/__fixtures__/package-lock.json +45 -0
- package/src/components/verbs/__fixtures__/pom.xml +37 -0
- package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
- package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
- package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
- package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
- package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
- package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
- package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
- package/src/components/verbs/bom-writer.test.ts +331 -0
- package/src/components/verbs/bom-writer.ts +386 -0
- package/src/components/verbs/build-archive.test.ts +251 -0
- package/src/components/verbs/build-archive.ts +296 -0
- package/src/components/verbs/build.test.ts +222 -0
- package/src/components/verbs/build.ts +252 -0
- package/src/components/verbs/cloud-executor.ts +181 -0
- package/src/components/verbs/component-bom.test.ts +219 -0
- package/src/components/verbs/component-bom.ts +189 -0
- package/src/components/verbs/index.ts +49 -0
- package/src/components/verbs/license-policy.ts +115 -0
- package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
- package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
- package/src/components/verbs/process-runner.test.ts +79 -0
- package/src/components/verbs/process-runner.ts +113 -0
- package/src/components/verbs/reproducibility.test.ts +24 -0
- package/src/components/verbs/reproducibility.ts +100 -0
- package/src/components/verbs/sbom-generator.test.ts +96 -0
- package/src/components/verbs/sbom-generator.ts +209 -0
- package/src/components/verbs/sbom.test.ts +184 -0
- package/src/components/verbs/sbom.ts +152 -0
- package/src/components/verbs/shell.test.ts +36 -0
- package/src/components/verbs/shell.ts +51 -0
- package/src/components/verbs/sign.test.ts +210 -0
- package/src/components/verbs/sign.ts +350 -0
- package/src/components/verbs/stub.ts +34 -0
- package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
- package/src/components/verbs/tool-sbom-generator.ts +192 -0
- package/src/components/verbs/verify.test.ts +159 -0
- package/src/components/verbs/verify.ts +179 -0
- package/src/components/verbs/vex.ts +182 -0
- package/src/components/verbs/vuln-gate.test.ts +201 -0
- package/src/components/verbs/vuln-gate.ts +170 -0
- package/src/components/verbs/vuln-scan.test.ts +145 -0
- package/src/components/verbs/vuln-scan.ts +266 -0
- package/src/components/verbs/wait-verify.test.ts +111 -0
- package/src/components/verbs/wait-verify.ts +171 -0
- package/src/config.test.ts +74 -1
- package/src/config.ts +219 -0
- package/src/index.ts +32 -0
- package/src/lexicon.ts +88 -0
- package/src/lifecycle/build-ledger-store.test.ts +217 -0
- package/src/lifecycle/build-ledger-store.ts +165 -0
- package/src/lifecycle/build-ledger.test.ts +349 -0
- package/src/lifecycle/build-ledger.ts +305 -0
- package/src/lifecycle/git.test.ts +118 -0
- package/src/lifecycle/git.ts +149 -14
- package/src/lifecycle/index.ts +5 -0
- package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
- package/src/lifecycle/oras-referrer-lookup.ts +110 -0
- package/src/lifecycle/release-ledger.test.ts +209 -0
- package/src/lifecycle/release-ledger.ts +185 -0
- package/src/lifecycle/status.test.ts +330 -0
- package/src/lifecycle/status.ts +278 -0
- package/src/lint/component-checks.ts +148 -0
- package/src/lint/config.test.ts +21 -0
- package/src/lint/engine.ts +11 -2
- package/src/lint/presets/relaxed.json +8 -1
- package/src/lint/presets/strict.json +8 -1
- package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
- package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
- package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
- package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
- package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
- package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
- package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
- package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
- package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
- package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
- package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
- package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
- package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
- package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
- package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
- package/src/lint/rules/comp/comp.test.ts +378 -0
- package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
- package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
- package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
- package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
- package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
- package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
- package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
- package/src/lint/rules/comp/index.ts +37 -0
- package/src/lint/rules/comp/support.ts +173 -0
- package/src/lsp/lexicon-providers.test.ts +55 -0
- package/src/lsp/lexicon-providers.ts +13 -3
- package/src/op/activity-registry.test.ts +38 -0
- package/src/op/activity-registry.ts +31 -12
- package/src/op/activity-runtime.ts +73 -0
- package/src/op/builders.ts +92 -0
- package/src/op/index.ts +3 -1
- package/src/ownership.test.ts +24 -26
- package/src/ownership.ts +26 -38
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `verify` capability — the deploy-time signature/provenance gate (#622,
|
|
3
|
+
* epic #551 follow-up to ./sign.ts's keyless signing + SLSA attestation).
|
|
4
|
+
* Mirrors `../../lint/policy.ts`'s `policyGate` shape: a step that runs
|
|
5
|
+
* `cosign verify` (+ `cosign verify-attestation` for provenance) against a
|
|
6
|
+
* configured identity policy and **throws** on any failure, so composing it
|
|
7
|
+
* before an apply phase fails the deploy the same way a thrown capability
|
|
8
|
+
* error already halts `driver.ts`'s step execution — no new failure
|
|
9
|
+
* mechanism, no gate-signal/human-approval semantics (that is `Gate`,
|
|
10
|
+
* ../component.ts, a different, durable-runtime-only concept). `verify` is a
|
|
11
|
+
* synchronous, local-executor-safe capability, exactly like `health-gate`
|
|
12
|
+
* (./wait-verify.ts).
|
|
13
|
+
*
|
|
14
|
+
* **Beginner-safe default policy.** The minimum an identity policy must
|
|
15
|
+
* declare is `expectedIssuer` + `expectedIdentity` (the OIDC issuer/identity
|
|
16
|
+
* that must have signed) — "signature present + from the expected identity."
|
|
17
|
+
* There is no "verify but accept any identity" mode: an identity-less verify
|
|
18
|
+
* would defeat the point of keyless signing (anyone's Rekor-logged signature
|
|
19
|
+
* would pass), so `IdentityPolicy` requires both fields, not an optional
|
|
20
|
+
* pair a beginner could forget to set.
|
|
21
|
+
*
|
|
22
|
+
* Routes `cosign verify`/`cosign verify-attestation` through the injectable
|
|
23
|
+
* `ProcessRunner` (./process-runner.ts), mirroring every other #610/#622
|
|
24
|
+
* real backend: `requireTool` throws `ToolNotAvailableError` if `cosign` is
|
|
25
|
+
* absent (a missing verifier is a hard stop, never a silent pass — the
|
|
26
|
+
* opposite failure mode from ./publish.ts's best-effort referrer attach,
|
|
27
|
+
* because skipping verification on a missing tool would silently let an
|
|
28
|
+
* unsigned/wrongly-signed artifact through the gate).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { Capability } from "../capability";
|
|
32
|
+
import { defaultProcessRunner, q, requireTool, type ProcessRunner } from "./process-runner";
|
|
33
|
+
|
|
34
|
+
// ── identity policy ──────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The configured identity an artifact's signature/provenance must match —
|
|
38
|
+
* "beginner-safe default" per #622: signature present + from the expected
|
|
39
|
+
* identity, nothing more exotic required out of the box. Both fields are
|
|
40
|
+
* required (see this module's doc comment for why an identity-less verify
|
|
41
|
+
* is not offered).
|
|
42
|
+
*/
|
|
43
|
+
export interface IdentityPolicy {
|
|
44
|
+
/** Expected OIDC issuer, e.g. `"https://token.actions.githubusercontent.com"` for GitHub Actions keyless signing. Passed to `cosign verify --certificate-oidc-issuer`. */
|
|
45
|
+
expectedIssuer: string;
|
|
46
|
+
/**
|
|
47
|
+
* Expected signer identity. For keyless CI signing this is typically the
|
|
48
|
+
* workflow identity URI cosign embeds in the Fulcio cert (e.g.
|
|
49
|
+
* `"https://github.com/my-org/my-repo/.github/workflows/release.yml@refs/heads/main"`).
|
|
50
|
+
* Passed to `cosign verify --certificate-identity`. Supports the same
|
|
51
|
+
* `--certificate-identity-regexp` escape hatch via `identityIsRegexp`
|
|
52
|
+
* below, for teams whose identity varies by branch/tag and want one policy
|
|
53
|
+
* to match a pattern rather than enumerating every literal ref.
|
|
54
|
+
*/
|
|
55
|
+
expectedIdentity: string;
|
|
56
|
+
/** Treat `expectedIdentity` as a regexp (`cosign verify --certificate-identity-regexp`) instead of a literal match. Default: false. */
|
|
57
|
+
identityIsRegexp?: boolean;
|
|
58
|
+
/** Optional key-based override — verify against a public key instead of the keyless identity policy, mirroring ./sign.ts's opt-in key-based override. Not the default. */
|
|
59
|
+
key?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── verify (signature gate) ──────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
export interface VerifyInput {
|
|
65
|
+
/** Digest-qualified image reference to verify, e.g. `"@Publish.uri"`/`"@Sign.imageRef"`. */
|
|
66
|
+
imageRef: string;
|
|
67
|
+
/** The identity policy the signature must satisfy. */
|
|
68
|
+
policy: IdentityPolicy;
|
|
69
|
+
/** Also verify the SLSA provenance attestation (`cosign verify-attestation --type slsaprovenance1`) alongside the signature. Default: true — both #622 outputs are expected present once `sign`+`attest-provenance` have run. */
|
|
70
|
+
requireProvenance?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface VerifyOutput {
|
|
74
|
+
/** True once every requested check (signature, and provenance if requested) passed. Always `true` when returned — a failure throws instead, so a caller never has to remember to check this field; it exists for symmetry with other capability outputs and for logging. */
|
|
75
|
+
verified: true;
|
|
76
|
+
/** Which checks actually ran and passed. */
|
|
77
|
+
checked: Array<"signature" | "provenance">;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Thrown when `cosign verify`/`cosign verify-attestation` rejects — wrong
|
|
82
|
+
* identity, missing signature, tampered artifact, or any other verification
|
|
83
|
+
* failure `cosign` itself reports. Distinct from `ToolNotAvailableError` so a
|
|
84
|
+
* caller (and a test) can tell "cosign said no" apart from "cosign isn't
|
|
85
|
+
* installed." Carries `reason` (cosign's own stderr/message) for whoever is
|
|
86
|
+
* debugging a failed gate.
|
|
87
|
+
*/
|
|
88
|
+
export class VerificationFailedError extends Error {
|
|
89
|
+
constructor(
|
|
90
|
+
public readonly imageRef: string,
|
|
91
|
+
public readonly check: "signature" | "provenance",
|
|
92
|
+
public readonly reason: string,
|
|
93
|
+
) {
|
|
94
|
+
super(`verify "${imageRef}": ${check} verification failed — ${reason}`);
|
|
95
|
+
this.name = "VerificationFailedError";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Append the shared identity-policy flags to a `cosign verify`/`verify-attestation` argv, mutating `args` in place — kept in one place so `verify`'s two checks can never drift apart on how the policy is expressed. */
|
|
100
|
+
function appendPolicyFlags(args: string[], policy: IdentityPolicy): void {
|
|
101
|
+
if (policy.key) {
|
|
102
|
+
args.push("--key", q(policy.key));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
args.push("--certificate-oidc-issuer", q(policy.expectedIssuer));
|
|
106
|
+
args.push(policy.identityIsRegexp ? "--certificate-identity-regexp" : "--certificate-identity", q(policy.expectedIdentity));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Build the `cosign verify` argv (as a shell command string) for `input` — factored out so tests can assert on the exact invocation. */
|
|
110
|
+
export function buildVerifySignatureArgs(input: VerifyInput): string {
|
|
111
|
+
const args = ["cosign", "verify"];
|
|
112
|
+
appendPolicyFlags(args, input.policy);
|
|
113
|
+
args.push(q(input.imageRef));
|
|
114
|
+
return args.join(" ");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Build the `cosign verify-attestation` argv (as a shell command string) for `input`. */
|
|
118
|
+
export function buildVerifyAttestationArgs(input: VerifyInput): string {
|
|
119
|
+
const args = ["cosign", "verify-attestation", "--type", "slsaprovenance1"];
|
|
120
|
+
appendPolicyFlags(args, input.policy);
|
|
121
|
+
args.push(q(input.imageRef));
|
|
122
|
+
return args.join(" ");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Gate a deploy on `cosign verify` (signature) + `cosign verify-attestation`
|
|
127
|
+
* (SLSA provenance, unless `requireProvenance: false`) against `input.policy`
|
|
128
|
+
* — the digest-anchored counterpart to `../../lint/policy.ts`'s
|
|
129
|
+
* organizational-policy `policyGate`. Throws `VerificationFailedError` on any
|
|
130
|
+
* failed check, which halts the composition the same way any other thrown
|
|
131
|
+
* capability error does (see ../driver.ts) — no separate "gate" plumbing
|
|
132
|
+
* needed; a `verify` step composed before an `Apply` phase blocks that phase
|
|
133
|
+
* from ever running on verification failure.
|
|
134
|
+
*
|
|
135
|
+
* No rollback: verification is read-only observation of already-published
|
|
136
|
+
* evidence, nothing to compensate — same reasoning as `health-gate`/`wait-*`
|
|
137
|
+
* (./wait-verify.ts).
|
|
138
|
+
*/
|
|
139
|
+
export function createVerifyCapability(
|
|
140
|
+
processRunner: ProcessRunner = defaultProcessRunner(),
|
|
141
|
+
): Capability<VerifyInput, VerifyOutput> {
|
|
142
|
+
return {
|
|
143
|
+
kind: "verify",
|
|
144
|
+
async run(_ctx, input) {
|
|
145
|
+
await requireTool(processRunner, "cosign", `verify ${input.imageRef} against the configured identity policy`);
|
|
146
|
+
|
|
147
|
+
const checked: Array<"signature" | "provenance"> = [];
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
await processRunner.run(buildVerifySignatureArgs(input));
|
|
151
|
+
} catch (err) {
|
|
152
|
+
throw new VerificationFailedError(
|
|
153
|
+
input.imageRef,
|
|
154
|
+
"signature",
|
|
155
|
+
err instanceof Error ? err.message : String(err),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
checked.push("signature");
|
|
159
|
+
|
|
160
|
+
if (input.requireProvenance !== false) {
|
|
161
|
+
try {
|
|
162
|
+
await processRunner.run(buildVerifyAttestationArgs(input));
|
|
163
|
+
} catch (err) {
|
|
164
|
+
throw new VerificationFailedError(
|
|
165
|
+
input.imageRef,
|
|
166
|
+
"provenance",
|
|
167
|
+
err instanceof Error ? err.message : String(err),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
checked.push("provenance");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { verified: true, checked };
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Default `verify` capability, backed by the real `ProcessRunner`. */
|
|
179
|
+
export const verifyCapability: Capability<VerifyInput, VerifyOutput> = createVerifyCapability();
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VEX (Vulnerability Exploitability eXchange) ingestion + suppression (#626).
|
|
3
|
+
* A scanner (./vuln-scan.ts) flags every known CVE in an SBOM, but most aren't
|
|
4
|
+
* actually exploitable in context (unreachable code path, already mitigated,
|
|
5
|
+
* false positive). VEX is where you (or upstream) assert, per CVE, whether it
|
|
6
|
+
* *actually* affects this artifact. Without it a gate is CVE fatigue — 500
|
|
7
|
+
* findings, 5 that matter. This module parses VEX and filters findings, and it
|
|
8
|
+
* **reports** every suppression with its justification — a VEX-suppressed
|
|
9
|
+
* finding is never silently dropped.
|
|
10
|
+
*
|
|
11
|
+
* Two formats: **OpenVEX** (the standalone Sigstore/OpenSSF format) and
|
|
12
|
+
* **CycloneDX**-embedded VEX (`vulnerabilities[].analysis`). Both reduce to the
|
|
13
|
+
* same `VexStatement` shape here.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { VulnFinding } from "./vuln-scan";
|
|
17
|
+
|
|
18
|
+
/** VEX status for a CVE against an artifact. `not_affected`/`fixed` suppress a finding; `affected`/`under_investigation` keep it (conservative — an unresolved status must not hide a real vuln). */
|
|
19
|
+
export type VexStatus = "not_affected" | "affected" | "fixed" | "under_investigation";
|
|
20
|
+
|
|
21
|
+
/** True for statuses that suppress a matching finding from gating. */
|
|
22
|
+
export function suppresses(status: VexStatus): boolean {
|
|
23
|
+
return status === "not_affected" || status === "fixed";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** One VEX assertion: this CVE has this status for this artifact, with an optional human justification. */
|
|
27
|
+
export interface VexStatement {
|
|
28
|
+
cveId: string;
|
|
29
|
+
status: VexStatus;
|
|
30
|
+
/** Why — e.g. `"vulnerable_code_not_in_execute_path"`, `"component_not_present"`. Surfaced on the suppression report so a reviewer sees the reasoning, never a bare "suppressed." */
|
|
31
|
+
justification?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── OpenVEX ──────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
interface OpenVexDoc {
|
|
37
|
+
statements?: Array<{
|
|
38
|
+
// OpenVEX allows `vulnerability` as either a string or an object with `name`.
|
|
39
|
+
vulnerability?: string | { name?: string; "@id"?: string };
|
|
40
|
+
status?: string;
|
|
41
|
+
justification?: string;
|
|
42
|
+
impact_statement?: string;
|
|
43
|
+
action_statement?: string;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function vexStatusFrom(raw: string | undefined): VexStatus | undefined {
|
|
48
|
+
switch ((raw ?? "").toLowerCase()) {
|
|
49
|
+
case "not_affected":
|
|
50
|
+
return "not_affected";
|
|
51
|
+
case "affected":
|
|
52
|
+
return "affected";
|
|
53
|
+
case "fixed":
|
|
54
|
+
return "fixed";
|
|
55
|
+
case "under_investigation":
|
|
56
|
+
return "under_investigation";
|
|
57
|
+
default:
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Parse an OpenVEX JSON document into statements. Unknown statuses / statements missing a vulnerability id are skipped. */
|
|
63
|
+
export function parseOpenVex(bytes: string): VexStatement[] {
|
|
64
|
+
const doc = JSON.parse(bytes) as OpenVexDoc;
|
|
65
|
+
const out: VexStatement[] = [];
|
|
66
|
+
for (const s of doc.statements ?? []) {
|
|
67
|
+
const cveId = typeof s.vulnerability === "string" ? s.vulnerability : (s.vulnerability?.name ?? s.vulnerability?.["@id"]);
|
|
68
|
+
const status = vexStatusFrom(s.status);
|
|
69
|
+
if (!cveId || !status) continue;
|
|
70
|
+
out.push({ cveId, status, justification: s.justification ?? s.impact_statement });
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── CycloneDX-embedded VEX ───────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
interface CycloneDxVexDoc {
|
|
78
|
+
vulnerabilities?: Array<{
|
|
79
|
+
id?: string;
|
|
80
|
+
analysis?: { state?: string; detail?: string; justification?: string };
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Map CycloneDX `analysis.state` to a `VexStatus`. */
|
|
85
|
+
function cdxStateToVex(state: string | undefined): VexStatus | undefined {
|
|
86
|
+
switch ((state ?? "").toLowerCase()) {
|
|
87
|
+
case "not_affected":
|
|
88
|
+
case "false_positive":
|
|
89
|
+
return "not_affected";
|
|
90
|
+
case "resolved":
|
|
91
|
+
case "resolved_with_pedigree":
|
|
92
|
+
return "fixed";
|
|
93
|
+
case "exploitable":
|
|
94
|
+
return "affected";
|
|
95
|
+
case "in_triage":
|
|
96
|
+
return "under_investigation";
|
|
97
|
+
default:
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Parse CycloneDX-embedded VEX (`vulnerabilities[].analysis`) into statements. */
|
|
103
|
+
export function parseCycloneDxVex(bytes: string): VexStatement[] {
|
|
104
|
+
const doc = JSON.parse(bytes) as CycloneDxVexDoc;
|
|
105
|
+
const out: VexStatement[] = [];
|
|
106
|
+
for (const v of doc.vulnerabilities ?? []) {
|
|
107
|
+
const status = cdxStateToVex(v.analysis?.state);
|
|
108
|
+
if (!v.id || !status) continue;
|
|
109
|
+
out.push({ cveId: v.id, status, justification: v.analysis?.justification ?? v.analysis?.detail });
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parse a VEX document of either format — dispatched on shape (`statements` ->
|
|
116
|
+
* OpenVEX, `vulnerabilities` -> CycloneDX). A malformed/unparseable document
|
|
117
|
+
* yields NO statements (returns `[]`) rather than throwing: it therefore
|
|
118
|
+
* suppresses nothing, keeping the gate strict (fail-closed) instead of
|
|
119
|
+
* crashing on attacker- or typo-supplied input.
|
|
120
|
+
*/
|
|
121
|
+
export function parseVexDocument(bytes: string): VexStatement[] {
|
|
122
|
+
let doc: OpenVexDoc & CycloneDxVexDoc;
|
|
123
|
+
try {
|
|
124
|
+
doc = JSON.parse(bytes) as OpenVexDoc & CycloneDxVexDoc;
|
|
125
|
+
} catch {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(doc.statements)) return parseOpenVex(bytes);
|
|
129
|
+
if (Array.isArray(doc.vulnerabilities)) return parseCycloneDxVex(bytes);
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── applying VEX to findings ─────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/** A finding removed from gating by a VEX statement, kept for reporting (never silently dropped). */
|
|
136
|
+
export interface SuppressedFinding {
|
|
137
|
+
finding: VulnFinding;
|
|
138
|
+
status: VexStatus;
|
|
139
|
+
justification?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The result of filtering findings through VEX: what still gates, and what was suppressed (with reasons). */
|
|
143
|
+
export interface VexResult {
|
|
144
|
+
/** Findings that survived VEX — the gate evaluates these. */
|
|
145
|
+
gating: VulnFinding[];
|
|
146
|
+
/** Findings suppressed by a `not_affected`/`fixed` statement, with the statement's justification. */
|
|
147
|
+
suppressed: SuppressedFinding[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Filter `findings` through `statements`. A finding is suppressed only when
|
|
152
|
+
* some statement for its CVE suppresses it (`not_affected`/`fixed`) AND **no**
|
|
153
|
+
* statement for that CVE asserts it is `affected`/`under_investigation`.
|
|
154
|
+
*
|
|
155
|
+
* This is the conservative merge, deliberately NOT "last statement wins":
|
|
156
|
+
* when VEX comes from several sources (repo-local files + referrer-attached),
|
|
157
|
+
* a later `not_affected` must not be able to override an earlier `affected` to
|
|
158
|
+
* unblock a real vulnerability. Merging VEX can only ever make the gate
|
|
159
|
+
* *stricter*, never quietly weaker — so an attacker who can append a statement
|
|
160
|
+
* cannot suppress a finding another source flagged as affected.
|
|
161
|
+
*/
|
|
162
|
+
export function applyVex(findings: VulnFinding[], statements: VexStatement[]): VexResult {
|
|
163
|
+
const byCve = new Map<string, VexStatement[]>();
|
|
164
|
+
for (const s of statements) {
|
|
165
|
+
const list = byCve.get(s.cveId);
|
|
166
|
+
if (list) list.push(s);
|
|
167
|
+
else byCve.set(s.cveId, [s]);
|
|
168
|
+
}
|
|
169
|
+
const gating: VulnFinding[] = [];
|
|
170
|
+
const suppressed: SuppressedFinding[] = [];
|
|
171
|
+
for (const f of findings) {
|
|
172
|
+
const list = byCve.get(f.cveId) ?? [];
|
|
173
|
+
const contested = list.some((s) => s.status === "affected" || s.status === "under_investigation");
|
|
174
|
+
const suppressor = contested ? undefined : list.find((s) => suppresses(s.status));
|
|
175
|
+
if (suppressor) {
|
|
176
|
+
suppressed.push({ finding: f, status: suppressor.status, justification: suppressor.justification });
|
|
177
|
+
} else {
|
|
178
|
+
gating.push(f);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { gating, suppressed };
|
|
182
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for #626's VEX suppression (./vex.ts), license policy
|
|
3
|
+
* (./license-policy.ts), the `vuln-gate` capability (./vuln-gate.ts), and the
|
|
4
|
+
* `vulnPolicy` config resolver (../../config.ts). All hermetic: the gate runs
|
|
5
|
+
* over supplied findings or an inline fake scanner — no live scanner, network,
|
|
6
|
+
* or vuln DB.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, test, expect } from "vitest";
|
|
10
|
+
import { parseOpenVex, parseCycloneDxVex, parseVexDocument, applyVex } from "./vex";
|
|
11
|
+
import { extractLicenses, evaluateLicensePolicy } from "./license-policy";
|
|
12
|
+
import { createVulnGateCapability, VulnGateFailedError, type VulnGateInput } from "./vuln-gate";
|
|
13
|
+
import type { VulnFinding, VulnScanner } from "./vuln-scan";
|
|
14
|
+
import type { SbomDocument } from "./sbom-generator";
|
|
15
|
+
import { resolveVulnPolicy } from "../../config";
|
|
16
|
+
|
|
17
|
+
const ctx = { env: "prod", component: "search-service" };
|
|
18
|
+
|
|
19
|
+
const CRIT_FIXABLE: VulnFinding = { cveId: "CVE-1", severity: "critical", package: "a", installedVersion: "1", fixedVersion: "2", fixable: true };
|
|
20
|
+
const CRIT_UNFIXABLE: VulnFinding = { cveId: "CVE-2", severity: "critical", package: "b", installedVersion: "1", fixable: false };
|
|
21
|
+
const HIGH_FIXABLE: VulnFinding = { cveId: "CVE-3", severity: "high", package: "c", installedVersion: "1", fixedVersion: "2", fixable: true };
|
|
22
|
+
|
|
23
|
+
const SBOM_SPDX: SbomDocument = {
|
|
24
|
+
format: "spdx",
|
|
25
|
+
mediaType: "application/spdx+json",
|
|
26
|
+
bytes: JSON.stringify({ packages: [{ name: "gpl-lib", licenseConcluded: "GPL-3.0" }, { name: "mit-lib", licenseConcluded: "MIT" }, { name: "no-lic", licenseConcluded: "NOASSERTION" }] }),
|
|
27
|
+
generator: "lockfile",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ── VEX ──────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
describe("VEX parsing", () => {
|
|
33
|
+
test("parseOpenVex handles vulnerability as string and as object", () => {
|
|
34
|
+
const doc = JSON.stringify({
|
|
35
|
+
statements: [
|
|
36
|
+
{ vulnerability: "CVE-1", status: "not_affected", justification: "vulnerable_code_not_in_execute_path" },
|
|
37
|
+
{ vulnerability: { name: "CVE-3" }, status: "affected" },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
const stmts = parseOpenVex(doc);
|
|
41
|
+
expect(stmts).toHaveLength(2);
|
|
42
|
+
expect(stmts[0]).toEqual({ cveId: "CVE-1", status: "not_affected", justification: "vulnerable_code_not_in_execute_path" });
|
|
43
|
+
expect(stmts[1].status).toBe("affected");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("parseCycloneDxVex maps analysis.state", () => {
|
|
47
|
+
const doc = JSON.stringify({
|
|
48
|
+
vulnerabilities: [
|
|
49
|
+
{ id: "CVE-1", analysis: { state: "not_affected", justification: "code_not_reachable" } },
|
|
50
|
+
{ id: "CVE-9", analysis: { state: "resolved" } },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
const stmts = parseCycloneDxVex(doc);
|
|
54
|
+
expect(stmts[0].status).toBe("not_affected");
|
|
55
|
+
expect(stmts[1].status).toBe("fixed");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("parseVexDocument dispatches on shape", () => {
|
|
59
|
+
expect(parseVexDocument(JSON.stringify({ statements: [{ vulnerability: "CVE-1", status: "fixed" }] }))[0].status).toBe("fixed");
|
|
60
|
+
expect(parseVexDocument(JSON.stringify({ vulnerabilities: [{ id: "CVE-1", analysis: { state: "exploitable" } }] }))[0].status).toBe("affected");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("applyVex suppresses not_affected/fixed, keeps affected, reports justification", () => {
|
|
64
|
+
const { gating, suppressed } = applyVex(
|
|
65
|
+
[CRIT_FIXABLE, HIGH_FIXABLE],
|
|
66
|
+
[{ cveId: "CVE-1", status: "not_affected", justification: "not reachable" }],
|
|
67
|
+
);
|
|
68
|
+
expect(gating.map((f) => f.cveId)).toEqual(["CVE-3"]);
|
|
69
|
+
expect(suppressed).toHaveLength(1);
|
|
70
|
+
expect(suppressed[0].justification).toBe("not reachable");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ── licenses ─────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
describe("license policy", () => {
|
|
77
|
+
test("extractLicenses reads SPDX and CycloneDX", () => {
|
|
78
|
+
expect(extractLicenses(SBOM_SPDX).find((l) => l.package === "gpl-lib")?.license).toBe("GPL-3.0");
|
|
79
|
+
const cdx: SbomDocument = {
|
|
80
|
+
format: "cyclonedx",
|
|
81
|
+
mediaType: "application/vnd.cyclonedx+json",
|
|
82
|
+
bytes: JSON.stringify({ components: [{ name: "x", licenses: [{ license: { id: "Apache-2.0" } }] }] }),
|
|
83
|
+
generator: "lockfile",
|
|
84
|
+
};
|
|
85
|
+
expect(extractLicenses(cdx)[0].license).toBe("Apache-2.0");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("deny flags a denied license; NOASSERTION is skipped", () => {
|
|
89
|
+
const violations = evaluateLicensePolicy(SBOM_SPDX, { deny: ["GPL-3.0"] });
|
|
90
|
+
expect(violations).toHaveLength(1);
|
|
91
|
+
expect(violations[0]).toEqual({ package: "gpl-lib", license: "GPL-3.0", reason: "denied" });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("allowlist flags anything not on it (case-insensitive)", () => {
|
|
95
|
+
const violations = evaluateLicensePolicy(SBOM_SPDX, { allow: ["mit"] });
|
|
96
|
+
expect(violations.map((v) => v.package)).toEqual(["gpl-lib"]);
|
|
97
|
+
expect(violations[0].reason).toBe("not-allowed");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ── the gate ─────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
function gate(input: VulnGateInput) {
|
|
104
|
+
return createVulnGateCapability().run(ctx, input);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
describe("vuln-gate", () => {
|
|
108
|
+
test("passes on a clean set, reporting high findings as warnings (default policy)", async () => {
|
|
109
|
+
const out = await gate({ sbom: SBOM_SPDX, findings: [HIGH_FIXABLE] });
|
|
110
|
+
expect(out.passed).toBe(true);
|
|
111
|
+
expect(out.warnings.map((f) => f.cveId)).toEqual(["CVE-3"]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("FAILS on an unsuppressed critical + fixable finding", async () => {
|
|
115
|
+
await expect(gate({ sbom: SBOM_SPDX, findings: [CRIT_FIXABLE] })).rejects.toBeInstanceOf(VulnGateFailedError);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("a VEX not_affected statement suppresses the blocking finding (gate passes)", async () => {
|
|
119
|
+
const vex = JSON.stringify({ statements: [{ vulnerability: "CVE-1", status: "not_affected", justification: "not reachable" }] });
|
|
120
|
+
const out = await gate({ sbom: SBOM_SPDX, findings: [CRIT_FIXABLE], vex: [vex] });
|
|
121
|
+
expect(out.passed).toBe(true);
|
|
122
|
+
expect(out.suppressed[0].finding.cveId).toBe("CVE-1");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("critical but NOT fixable does not block under the default fixableOnly, it warns", async () => {
|
|
126
|
+
const out = await gate({ sbom: SBOM_SPDX, findings: [CRIT_UNFIXABLE], policy: { warnSeverity: "critical" } });
|
|
127
|
+
expect(out.passed).toBe(true);
|
|
128
|
+
expect(out.warnings.map((f) => f.cveId)).toEqual(["CVE-2"]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("license: report-only by default (no block), blocks when failOnLicense", async () => {
|
|
132
|
+
const reportOnly = await gate({ sbom: SBOM_SPDX, findings: [], policy: { license: { deny: ["GPL-3.0"] } } });
|
|
133
|
+
expect(reportOnly.passed).toBe(true);
|
|
134
|
+
expect(reportOnly.licenseFindings).toHaveLength(1);
|
|
135
|
+
|
|
136
|
+
await expect(
|
|
137
|
+
gate({ sbom: SBOM_SPDX, findings: [], policy: { license: { deny: ["GPL-3.0"] }, failOnLicense: true } }),
|
|
138
|
+
).rejects.toBeInstanceOf(VulnGateFailedError);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("scans via the injected scanner when findings are not supplied", async () => {
|
|
142
|
+
const fake: VulnScanner = { async scan() { return [CRIT_FIXABLE]; } };
|
|
143
|
+
await expect(createVulnGateCapability(fake).run(ctx, { sbom: SBOM_SPDX })).rejects.toBeInstanceOf(VulnGateFailedError);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ── audit hardening (#628) ───────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
describe("audit hardening (#628)", () => {
|
|
150
|
+
const CRIT: VulnFinding = { cveId: "CVE-X", severity: "critical", package: "p", installedVersion: "1", fixedVersion: "2", fixable: true };
|
|
151
|
+
const UNKNOWN: VulnFinding = { cveId: "CVE-U", severity: "unknown", package: "q", installedVersion: "1", fixable: true };
|
|
152
|
+
|
|
153
|
+
test("VEX conservative merge: an `affected` statement blocks a later `not_affected` from suppressing", () => {
|
|
154
|
+
const { gating, suppressed } = applyVex(
|
|
155
|
+
[CRIT],
|
|
156
|
+
[
|
|
157
|
+
{ cveId: "CVE-X", status: "affected" },
|
|
158
|
+
{ cveId: "CVE-X", status: "not_affected", justification: "attacker-appended" },
|
|
159
|
+
],
|
|
160
|
+
);
|
|
161
|
+
expect(gating.map((f) => f.cveId)).toEqual(["CVE-X"]);
|
|
162
|
+
expect(suppressed).toHaveLength(0);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("malformed VEX does not crash and suppresses nothing (gate stays strict)", async () => {
|
|
166
|
+
await expect(gate({ sbom: SBOM_SPDX, findings: [CRIT], vex: ['{"bad json'] })).rejects.toBeInstanceOf(VulnGateFailedError);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("SPDX license expression: deny matches a license hidden in an OR expression", () => {
|
|
170
|
+
const sbom: SbomDocument = {
|
|
171
|
+
format: "spdx",
|
|
172
|
+
mediaType: "application/spdx+json",
|
|
173
|
+
generator: "lockfile",
|
|
174
|
+
bytes: JSON.stringify({ packages: [{ name: "z", licenseConcluded: "GPL-3.0 OR MIT" }] }),
|
|
175
|
+
};
|
|
176
|
+
const v = evaluateLicensePolicy(sbom, { deny: ["GPL-3.0"] });
|
|
177
|
+
expect(v).toHaveLength(1);
|
|
178
|
+
expect(v[0].package).toBe("z");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("unknown-severity is warned by default (never silently dropped), and blocks when opted in", async () => {
|
|
182
|
+
const warned = await gate({ sbom: SBOM_SPDX, findings: [UNKNOWN] });
|
|
183
|
+
expect(warned.passed).toBe(true);
|
|
184
|
+
expect(warned.warnings.map((f) => f.cveId)).toEqual(["CVE-U"]);
|
|
185
|
+
await expect(gate({ sbom: SBOM_SPDX, findings: [UNKNOWN], policy: { failOnUnknownSeverity: true } })).rejects.toBeInstanceOf(VulnGateFailedError);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// ── config resolver ──────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
describe("resolveVulnPolicy", () => {
|
|
192
|
+
test("empty for no config; passes through set fields", () => {
|
|
193
|
+
expect(resolveVulnPolicy({})).toEqual({});
|
|
194
|
+
expect(resolveVulnPolicy({ vulnPolicy: { failSeverity: "high", fixableOnly: false, failOnLicense: true, license: { deny: ["GPL-3.0"] } } })).toEqual({
|
|
195
|
+
failSeverity: "high",
|
|
196
|
+
fixableOnly: false,
|
|
197
|
+
failOnLicense: true,
|
|
198
|
+
license: { deny: ["GPL-3.0"] },
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|