In one line. assayward evaluates attestations and workload identities against declarative policy and returns an explainable allow, deny, or audit decision at the moment of admission, so "verified" and "permitted" finally mean the same thing.
You can now verify that a container image was built from the source it claims, by a CI pipeline whose identity you trust, with no critical unmitigated CVEs. That verification is real and it matters. But verification alone is passive: it produces a result that still has to be acted on, consistently, everywhere your workloads run, in your Kubernetes cluster, in a Cloudflare Worker, in a GitHub Action, in a script. If different surfaces enforce different rules, or enforce them inconsistently, or fail open when a check is inconclusive, the supply-chain trust you built collapses at the admission boundary. assayward is that boundary: a portable, deterministic gate that consumes the outputs of forgeseal (what is running) and svidmint (who is running it) and answers whether to let it run.
Figure 1. Left: attestations exist but enforcement is surface-specific, inconsistent, or silent-fail. Right: one declarative policy evaluated identically at every surface, with an explainable decision at every admission point.
Why this matters
- For platform and security teams: a verified artifact that can still be admitted by a surface without checking its provenance is a supply-chain control with a hole in it; assayward closes that hole by making policy evaluation and admission a single, auditable step rather than something each team wires up separately.
- For teams operating under the EU Cyber Resilience Act: the CRA (Regulation EU 2024/2847) requires not only that you produce SBOMs and vulnerability records, but that you demonstrate active management of software risk; automated, auditable admission gates with machine-readable decision records are the operational evidence of that management (reporting obligations apply from 11 Sep 2026, full applicability 11 Dec 2027, status as of June 2026).
- For open-source and cloud-native ecosystems: the OpenSSF and CNCF have invested heavily in signing and attestation primitives (Sigstore, SLSA v1.2, in-toto) but the "evaluate and enforce" layer has remained fragmented across incompatible tools; assayward targets that gap with a single portable evaluator that speaks the same language as those primitives.
🧭 If you only read this far: assayward turns a stack of verified attestations into a single, auditable yes-or-no decision that works the same way in your cluster, your edge function, your npm script, and your CI pipeline.
Terms in 30 seconds
Domain engineers: skip ahead, this is orientation for everyone else.
- Attestation: a signed statement about a software artifact: "this image was built by this pipeline at this commit, with these dependencies." forgeseal produces attestations; assayward consumes them.
- Workload identity: a cryptographic credential issued to a running process (not a human), proving who is running the code. svidmint produces these via SPIFFE SVIDs; assayward verifies them.
- SLSA v1.2 (Supply-chain Levels for Software Artifacts): an OpenSSF framework that defines levels of build integrity, from basic provenance (L1) to hermetic, reproducible builds (L3). (slsa.dev)
- Sigstore: keyless signing infrastructure: short-lived certificates from Fulcio, transparency log in Rekor, all OpenSSF Graduated. (sigstore.dev)
- SPIFFE (Secure Production Identity Framework for Everyone): a CNCF standard for issuing cryptographic identities to workloads, independent of where they run. (spiffe.io)
- VEX / OpenVEX (Vulnerability Exploitability eXchange): a record that says whether a known CVE is actually exploitable in your specific build. assayward can gate on unmitigated severity.
- CycloneDX (ECMA-424): a full-stack BOM standard now published as Ecma ECMA-424, maintained by OWASP and Ecma TC54. assayward can require a conformant SBOM as a policy condition. (cyclonedx.org)
- OPA / Rego: Open Policy Agent and its policy language; assayward's built-in policies are augmentable with embedded Rego for custom logic. (openpolicyagent.org)
- EU CRA: Cyber Resilience Act, Regulation (EU) 2024/2847; in force 10 Dec 2024; vulnerability/incident reporting from 11 Sep 2026, full applicability 11 Dec 2027 (status as of June 2026). Mandates active software risk management. (European Commission)
The problem, for engineers who don't live in this space
Imagine you manage the door policy for a concert venue. Over months, your team has built a meticulous ticketing and ID-verification system. Every ticket is cryptographically signed. Every attendee's identity is checked. But then you discover that the side entrance, the loading dock, and the VIP lounge each have their own door staff, each following a slightly different rule sheet, and two of them will let someone through if the verification system is slow to respond. The investment in tickets and IDs is real, but the admission layer undoes it.
That is the state of software supply-chain enforcement today. A mature team might use forgeseal to generate a signed SBOM, a SLSA provenance record, and a VEX document, and svidmint to issue a SPIFFE identity to the workload. The verification artifacts exist. But enforcing them requires wiring up separate tools per surface: cosign for CLI verification, Kyverno or OPA-Gatekeeper policies in Kubernetes, custom middleware in an edge function, a bespoke script in a GitHub Action. Each integration re-implements the policy logic. Each one fails differently under error conditions. None of them share a policy document, so a change to your SLSA level requirement has to be propagated across every surface by hand.
Figure 2. The admission gap: attestations exist and are verifiable, but enforcement is re-implemented per surface, fails inconsistently, and shares no policy layer (red gap).
Why this is genuinely hard. A unified evaluator has to speak the wire formats of Sigstore bundles, SPIFFE SVIDs, CycloneDX SBOMs, OpenVEX documents, and ORAS-distributed policy bundles, and it has to produce identical decisions whether it runs as a native binary, a Wasm module inside a Cloudflare Worker, or a Kubernetes validating webhook. Identical decisions means deterministic: no implicit clock, no ambient network access, no filesystem side-effects, all external inputs injected, so the same inputs always produce the same output regardless of where the evaluator is hosted.
What exists today (and the gap)
| Tool / Approach | Verifies Sigstore bundles | Checks SLSA level | Evaluates VEX/SBOM | Validates SPIFFE identity | Portable: CLI + Wasm + K8s + edge + Action | Shared declarative policy | Explainable deny with machine-readable codes |
|---|---|---|---|---|---|---|---|
| cosign verify | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Kyverno | ⚠️ | ⚠️ | ❌ | ❌ | ❌ (K8s only) | ✅ | ⚠️ |
| OPA-Gatekeeper | ⚠️ | ⚠️ | ❌ | ❌ | ❌ (K8s only) | ✅ | ⚠️ |
| sigstore-policy-controller | ✅ | ⚠️ | ❌ | ❌ | ❌ (K8s only) | ⚠️ | ❌ |
| assayward | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
No existing tool combines forgeseal-and-svidmint-compatible attestation verification with shared declarative policy, SPIFFE identity evaluation, SBOM and VEX awareness, and deterministic portability across CLI, Wasm, npm, Kubernetes, edge, and GitHub Action surfaces. That combined surface, one portable, explainable, fail-closed gate that consumes the full zero-trust trilogy, is the gap assayward fills.
How it works
assayward is a deterministic policy engine that ingests attestation bundles and workload identities, evaluates them against a versioned TrustPolicy document, and emits a structured allow, deny, or audit decision. It has three parts:
- Core engine (
pkg/core), a pure Go evaluator with no network, filesystem, or implicit clock access; all external inputs (attestations, trust roots, workload identities, current time) are injected interfaces, so the engine is side-effect free and its outputs are byte-identical whether called from the native binary or the Wasm build. - TrustPolicy documents: versioned YAML (
apiVersion: assayward.dev/v1alpha1) that declare what a workload must prove: signature requirements, SLSA minimum level, allowed builders, VEX severity ceiling, SBOM requirement, and SPIFFE identity constraints. Three built-in policies ship today:baseline,slsa-l3, andserverless-edge. Custom logic is expressible in embedded Rego. - Six deployment surfaces: the same core is exposed as a CLI (
cmd/assayward), a Wasm module with a stable JSON ABI (core/wasm, targeting bothwasip1andjs/wasm), a TypeScript npm wrapper (@sns45/assayward), a Cloudflare Workers template, a Kubernetes validating admission webhook, and a GitHub Action.
Figure 3. One deterministic core, one shared TrustPolicy document, six surfaces. Attestation bundles and SPIFFE SVIDs flow in; allow, deny, or audit decisions with stable violation codes flow out.
Core engine
The engine is a pure Go package that accepts attestation bundles (verified via sigstore-go), SPIFFE SVIDs (via go-spiffe/v2), CycloneDX SBOMs (via cyclonedx-go), and OpenVEX documents (via openvex/go-vex) as injected inputs. It evaluates them against a compiled TrustPolicy and emits a structured decision that includes the policy version, a pass/fail status per check, and stable machine-readable violation codes. The no-side-effects constraint is deliberate: it is what makes the Wasm and native evaluators produce identical output, and what makes decisions independently reproducible and auditable.
TrustPolicy documents
A TrustPolicy is a YAML document that specifies mode (enforce, audit, or warn), signature requirements (keyless.issuer, keyless.identityPattern, rekor.required), SLSA constraints (slsa.minLevel, allowedBuilders), VEX constraints (vex.maxUnmitigatedSeverity), SBOM requirements, and SPIFFE identity constraints (identity.trustDomain, identity.idPattern). The assayward policy validate command checks a policy document for schema correctness; assayward policy test runs it against a fixture image and bundle with an expected outcome, enabling policy-as-code practices with testable gates. Policies can be packaged as signed OCI bundles and distributed via ORAS for centralized management.
Deployment surfaces
The six surfaces share a common interface to the core engine; none re-implement the evaluation logic. The Kubernetes webhook uses the same policy document as the CLI: a change to slsa.minLevel: 3 propagates everywhere at once. The Wasm surface's stable JSON ABI means the TypeScript npm wrapper and the Cloudflare Workers template can evaluate decisions in the edge runtime without a network round-trip to a policy server.
The decisions that actually mattered
Fail-closed admission with deterministic Wasm parity
The fork. Admission gates can fail open (allow if the evaluator errors) or fail closed (deny if anything is uncertain). Separately, the Wasm evaluator could be a best-effort approximation of the native evaluator, or it could be required to produce byte-identical decisions. These two questions are related: if Wasm and native can diverge, fail-closed is a weaker guarantee.
Options. Fail-open (operationally easier, common in advisory tools) vs. fail-closed (conservative, blocks on uncertainty). Approximate Wasm (simpler to build) vs. byte-identical parity (requires the side-effect-free constraint throughout the engine).
Chosen. Fail-closed, with byte-identical native-and-Wasm parity enforced by the no-side-effects design of pkg/core. Trade-off accepted. Fail-closed means that a misconfigured or unreachable trust root blocks admission rather than silently allowing it. That is operationally disruptive during misconfiguration. The alternative, silently allowing when uncertain, defeats the purpose of a security gate entirely. Byte-identical parity requires that every external dependency (time, network, filesystem) is injected rather than called directly, which adds interface indirection throughout the codebase. The payoff is that "the gate is closed" is a guarantee that holds across every deployment surface, not just the one you tested.
Figure 4. The fail-closed admission gate. Unverified inputs or policy failures route to the red blocked path. All checks passing routes to the green allowed path. An inconclusive evaluator state also routes to deny, not allow.
One shared TrustPolicy vs. per-surface configuration
The fork. Each deployment surface could have its own policy format (Kyverno ClusterPolicy for K8s, a JSON config for the CLI, environment variables for the Action). Or every surface could consume the same assayward.dev/v1alpha1 TrustPolicy YAML.
Chosen. One shared policy document across all six surfaces. Trade-off accepted. The TrustPolicy schema has to be expressive enough to cover the full range of surface-specific requirements (K8s namespaces, edge runtime constraints, CI context), which adds schema complexity relative to a purpose-built per-surface format. The payoff is that a policy change is a single commit, validated by assayward policy test, and deployed consistently, no risk of Kubernetes enforcing SLSA L2 while the GitHub Action still accepts L1 because someone forgot to update a second config file.
Why this is new, and why it's significant
What's novel. assayward is, to my knowledge, the first tool to provide a deterministic, fail-closed admission gate that evaluates Sigstore attestation bundles, SPIFFE workload identities, CycloneDX SBOMs, and OpenVEX documents against a single shared declarative policy, with byte-identical decisions across native, Wasm, npm, Kubernetes, edge, and GitHub Action surfaces. The novelty is not in any one verification primitive, sigstore-go, go-spiffe, cyclonedx-go, and openvex/go-vex all exist, but in the portable, side-effect-free evaluation layer that makes a single policy document enforceable everywhere.
Why it's significant to the field. The OpenSSF and CNCF have invested years in signing and attestation primitives: Sigstore graduated from OpenSSF, in-toto graduated from CNCF in 2025, SLSA reached v1.2 in November 2025. The acknowledged gap is that these primitives produce artifacts that are not automatically enforced, each team wires up enforcement separately, inconsistently, and with different failure semantics. That gap is not theoretical; it is the reason a signed artifact can still be deployed by a surface that never checked the signature. A deterministic, portable evaluation layer that consumes all four artifact types and enforces a shared policy is the missing complement to the signing ecosystem, and the wider community has named that complement as an open problem.
Standards and ecosystem alignment. This work implements and aligns with:
- SLSA v1.2 (OpenSSF, November 2025), the
slsa.minLevelpolicy field evaluates against SLSA Build Track levels;slsa-l3is a built-in policy. - Sigstore (OpenSSF Graduated): attestation verification uses sigstore-go; Rekor inclusion is policy-configurable via
rekor.required. - SPIFFE (CNCF workload-identity standard), SPIFFE SVID validation uses go-spiffe/v2; trust domain and identity pattern matching are first-class policy fields.
- CycloneDX (ECMA-424): SBOM parsing uses cyclonedx-go; SBOM presence is enforceable via the
sbom.requiredpolicy field. - OpenVEX (OpenSSF), VEX document consumption uses openvex/go-vex;
vex.maxUnmitigatedSeveritygates on unmitigated vulnerability severity. - OPA / Rego: custom policy logic beyond built-in fields is expressible as embedded Rego, giving teams an escape hatch for domain-specific rules without forking the evaluator.
- OCI + ORAS: signed policy bundles are distributed as OCI artifacts via ORAS, enabling centralized policy management with the same supply-chain integrity guarantees applied to the code being gated.
📌 Honest scope. assayward is v0.1 pre-release: the core engine and all six deployment surfaces are complete and end-to-end dogfooded, but the tool has not yet been independently adopted by other projects or organizations. The
assayward.dev/v1alpha1policy API is explicitly alpha and subject to change. Package-manager and marketplace distribution (Homebrew, Docker Hub, GitHub Marketplace) are planned but not yet available. The novelty claim is about the portable, unified evaluation layer; the individual verification libraries are established prior art.
See it run
# Build from source (cross-platform binaries, Homebrew, Docker planned)
go build -o assayward ./cmd/assayward
# Evaluate an image against the slsa-l3 built-in policy
assayward verify \
--image ghcr.io/example/app@sha256:abc123... \
--bundle app.sbom.sigstore.json \
--bundle app.slsa.sigstore.json \
--sigstore-trust-root trusted_root.json \
--policy slsa-l3
✓ signature keyless issuer=https://token.actions.githubusercontent.com # ← identity verified
✓ slsa level=3 builder=https://github.com/slsa-framework/... # ← L3 provenance confirmed
✓ sbom cyclonedx components=142
✓ vex openvex unmitigated_critical=0
✓ identity spiffe spiffe://example.org/workload/app
Decision: ALLOW policy=slsa-l3 version=assayward.dev/v1alpha1
# Explain the decision in human-readable form
assayward explain \
--image ghcr.io/example/app@sha256:abc123... \
--bundle app.sbom.sigstore.json \
--bundle app.slsa.sigstore.json \
--sigstore-trust-root trusted_root.json \
--policy slsa-l3
# Test a policy document against a fixture before deploying it
assayward policy test \
--policy slsa-l3 \
--expect deny \
--image ghcr.io/example/app@sha256:def456... \
--bundle app.slsa.sigstore.json
🔬 Go deeper. The side-effect-free core engine that makes Wasm parity possible lives in
pkg/core. The injected-interface pattern, how time, network, and filesystem are excluded from the evaluator, is the architectural decision that everything else depends on. (Pin to a specific commit SHA when linking the source directly.)
What's next
Near-term: Homebrew and Docker distribution, the GitHub Action marketplace listing, and graduating assayward.dev/v1alpha1 toward a stable API based on dogfood experience. assayward is the third piece of a zero-trust supply-chain trilogy: forgeseal seals what is running, svidmint certifies who is running it, and assayward decides whether to let it run. The larger aim is to make verifiable, identity-bound, policy-enforced deployment a default rather than an expert assembly job, a goal shared by the OpenSSF, CNCF, and the regulatory momentum behind the EU CRA.
Appendix / references
- Repository: github.com/sns45/assayward
- Related in this series: forgeseal · svidmint
- Standards referenced: SLSA v1.2 (slsa.dev) · Sigstore, OpenSSF Graduated (sigstore.dev) · SPIFFE (spiffe.io) · CycloneDX/ECMA-424 (cyclonedx.org) · OpenVEX (github.com/openvex) · OPA/Rego (openpolicyagent.org) · in-toto, CNCF Graduated 2025 (in-toto.io)
- Regulatory reference: EU CRA, Regulation (EU) 2024/2847 (European Commission)
- Discussion / feedback: github.com/sns45/assayward/issues