1. Introduction

In this blog post, we introduce in-toto Policies and show how they can turn multiple in-toto Attestations across different Supply-chain Levels for Software Artifacts (SLSA) tracks into evidence-backed, end-to-end (E2E) narratives about security and compliance.

We begin by walking through a concrete demo the reader can fork and run at home that dives deep into a hypothetical yet realistic supply chain in three easy pieces. First, we show how to generate your own signed Attestations about the Source and Build tracks among others. Second, we use a Policy Verifier which checks whether these Attestations meet an E2E Policy that correlates information across tracks, and outputs a Policy Verification Summary Attestation (VSA) as a result. Third, we show how end-users can verify Artifacts using simply these Policy VSAs.

We end by discussing how the new in-toto Policy Framework is designed to be flexible so that implementers and users can use any Policy data format, language, and engine of their choice so long as Policy Verifiers meet a few requirements. Thus, the Policy Framework is able to accommodate a variety of diverse Policy Verifiers (including other submissions to the SLSA E2E RFE depending on their conformance). Our goal is that SLSA users will be able to use—and even swap—any one of these Policy Verifiers, and still be interoperable.

We assume the reader is familiar with the in-toto Attestation Framework as well as SLSA v1.2 RC1 (which introduces the Source track). We define new in-toto concepts as we introduce them in the text.

2. Attestations: a trail of evidence [sigstore-js]

figure-1

Our hypothetical supply chain, as depicted in the figure above, consists of three named Steps: Source, Build, and Release. Each Step corresponds to a human or machine role which, in turn, produces a signed Attestation of what happened at that stage of the supply chain—including which inputs and outputs were read and written. Note that while the Attestations here are technically synthetic, they are still realistic (i.e., they use real values from sigstore-js).

Specifically, each Attestation has a signed Statement. The subject of this Statement precisely identifies one or more Artifacts (either inputs or outputs), while the predicate is categorized by a predicate type and contains details specific to the Step. This Statement is then enclosed in a DSSE envelope and digitally signed by a trusted entity.

The reader can generate their own attestations like so:

$ go run slsa-e2e-rfp/commands/verify-policy/*.go

Generating the Source Verification Summary Attestation (VSA)...
Key ID: 937101f8dcc7be06fd1f8c35dbbe9855d784c690e9598a5f11c9b70a0bf46f1f / Public Key: cfec494a7cc842d8340f2c33be5ed55334cba63e5515a8c0f30677211cfaced4
...done.

Generating the Build Provenance Attestation...
Key ID: 8c249ff34cddc2181a14e679577a2acf38216dc01b95be2c218a9faa79c4e2d0 / Public Key: 46a4405f239de13e9db05bdd706ccb329c0b19a9e2880c9e52bfe196641cb876
...done.

Generating the Release Attestation...
Key ID: 0973a4a0155ab13d35e3cfdd3476eaba91044a8693c5aa5e017d97313bd9dc84 / Public Key: 7da885ef5c5cc1255a327fa688474ec9baeb25cfcbb47930361da73e52875cd1
...done.

2.1 Source: which code was used [GitHub]

For the first Step, we suppose GitHub generates using gittuf a realistic Source VSA that records which source code repository and commit were verified, under which source policy, when, and with what result. We construct a Statement whose subject points to the exact repository (sigstore/sigstore-js) and git commit (3a57a741bfb9f7c3bca69b63e170fc28e9432e69), optionally including branch refs that contain that commit. The predicate type is https://slsa.dev/verification_summary/v1 and its predicate records the source policy it was checked against, the resource URL, the verification timestamp and the verdict. In our example, the verdict shows the source met SLSA Source Level 3 controls (for e.g., branch protection, mandatory code review, and tamper resistant history).

_type: https://in-toto.io/Statement/v1
subject:
- uri: https://github.com/sigstore/sigstore-js/commit/3a57a741bfb9f7c3bca69b63e170fc28e9432e69
  digest:
    gitCommit: 3a57a741bfb9f7c3bca69b63e170fc28e9432e69
  annotations:
    source_refs:
    - refs/heads/main
    - refs/heads/release_1.0
predicateType: https://slsa.dev/verification_summary/v1
predicate:
  policy:
    uri: https://example.com/slsa_source.policy
  resourceUri: git+https://github.com/sigstore/sigstore-js
  timeVerified: '1985-04-12T23:20:50.520Z'
  verificationResult: PASSED
  verifiedLevels:
  - SLSA_SOURCE_LEVEL_3
  verifier:
    id: https://github.com/gittuf/gittuf

2.2 Build: which code was built where [GitHub Actions]

For the second Step, we suppose GitHub Actions generates a Build Provenance Attestation that describes how the Artifact(s) were built from the verified source. We construct a Statement whose subject identifies the build Artifact (sigstore-3.0.0.tgz) and includes its SHA2-512 digest. The predicate type is https://slsa.dev/provenance/v1 and the predicate describes the build process including the the Github Actions workflow that ran, the builder that executed it, a link to the exact invocation ID, and most importantly the resolved dependencies pointing back to the precise source commit used to generate the Artifact.

_type: https://in-toto.io/Statement/v1
subject:
- name: pkg:npm/sigstore@3.0.0
  digest:
    sha512: 3c73227e187710de25a0c7070b3ea5deffe5bb3813df36bef5ff2cb9b1a078c3636c98f31f8223fd8a17dc6beefa46a8b894489557531c70911000d87fe66d78
predicateType: https://slsa.dev/provenance/v1
predicate:
  buildDefinition:
    buildType: https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1
    externalParameters:
      workflow:
        path: ".github/workflows/release.yml"
        ref: refs/heads/main
        repository: https://github.com/sigstore/sigstore-js
    internalParameters:
      github:
        event_name: push
        repository_id: '495574555'
        repository_owner_id: '71096353'
    resolvedDependencies:
    - digest:
        gitCommit: 3a57a741bfb9f7c3bca69b63e170fc28e9432e69
      uri: git+https://github.com/sigstore/sigstore-js/commit/3a57a741bfb9f7c3bca69b63e170fc28e9432e69
  runDetails:
    builder:
      id: https://github.com/actions/runner/github-hosted
    metadata:
      invocationId: https://github.com/sigstore/sigstore-js/actions/runs/11331290387/attempts/1

2.3 Release: which Artifact was released where [NPM]

For the third Step, we suppose NPM generates a Release Attestation that records which Artifact was uploaded to which project on NPM. We construct a Statement whose subject identifies the release Artifact (sigstore-3.0.0.tgz) with its SHA2-512 digest and the distribution URI. The predicate type is https://in-toto.io/attestation/release/v0.1 and the predicate captures the release metadata such as the package URL (pkg:npm/sigstore@3.0.0).

_type: https://in-toto.io/Statement/v1
subject:
- name: pkg:npm/sigstore@3.0.0
  digest:
    sha512: 3c73227e187710de25a0c7070b3ea5deffe5bb3813df36bef5ff2cb9b1a078c3636c98f31f8223fd8a17dc6beefa46a8b894489557531c70911000d87fe66d78
predicateType: https://in-toto.io/attestation/release/v0.1
predicate:
  releaseId: '1234567890'

3. Verifying Attestations with Policies

figure-2

Now we show how to verify the aforementioned Attestations using an in-toto Policy. As illustrated above, a Policy Verifier takes a Policy (including optional runtime Parameters) as well as Attestation Bundle, and produces a Policy VSA that indicates whether or not they were successfully verified.

3.1 Policy

figure-3

In the Policy Framework, a Policy is the blueprint that connects Steps in the supply chain together, especially how inputs and outputs should flow and transform (or not) between them. In the rest of this section, we describe our specific Policy for our hypothetical supply chain as illustrated in the figure above and listed in the code snippet below. This particular policy defines three named Steps (steps): source, build, and release corresponding to the three aforementioned Attestations. Each Step prescribes:

  • A name (e.g., source, build, release)
  • A list of expected inputs (expectedMaterials)
  • A list of expected outputs (expectedProducts)
  • An expected predicate type (expectedPredicates.predicateType)
  • A list of Predicate Validators that check for expected data inside predicates (expectedPredicates.expectedAttributes)
  • A threshold of public keys (functionaries)
steps:
  - name: "source"
    expectedMaterials:
      - "DISALLOW *"
    expectedProducts:
      - "CREATE https://github.com/{github_repository}/*"
      - "DISALLOW *"
    expectedPredicates:
      - predicateType: "https://slsa.dev/verification_summary/v1"
        expectedAttributes:
          - rule: "predicate.verifier.id == 'https://github.com/gittuf/gittuf'"
  - name: "build"
    expectedMaterials:
      - "MATCH https://github.com/{github_repository}/* IN git+ WITH PRODUCTS FROM source"
      - "DISALLOW *"
    expectedProducts:
      - "CREATE {package_name}-{package_version}.tgz"
      - "DISALLOW *"
    expectedPredicates:
      - predicateType: "https://slsa.dev/provenance/v1"
        expectedAttributes:
          - rule: "predicate.buildDefinition.buildType == 'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1'"
  - name: "release"
    expectedMaterials:
      - "MATCH * WITH PRODUCTS FROM build"
      - "DISALLOW *"
    expectedPredicates:
      - predicateType: "https://in-toto.io/attestation/release/v0.1"

Expected inputs and outputs are defined by I/O Filters and Connectors that define which patterns of Artifacts are allowed or not. Combining I/O Filters and Connectors with Predicate Validators establishes connectivity within and between Steps:

  1. The source Step says that there are no expected inputs (the DISALLOW Filter), and the expected outputs are only GitHub URIs to some verified source code somewhere (the CREATE followed by DISALLOW Filters). Additionally, we check the source code verifier was gittuf (using expectedAttributes).
  2. The build Step says that the expected input is the GitHub commit referenced by the source Step (the MATCH Connector followed by the DISALLOW Filter), and the expected output is the package that it built (the CREATE followed by DISALLOW Filters). Additionally, we check that the CI was a Github Actions workflow (using expectedAttributes).
  3. The release Step says that the expected input is the Artifact produced by the build Step (the MATCH Connector followed by the DISALLOW Filter), and there is no expected output.

In other words, our demo tells the E2E story (Source ← Build ← Release) where the sigstore-js source code from the Source-L3-compliant Source Control System (GitHub) was what was actually built by the Build-L3-compliant CI (GitHub Actions) and released by the package registry (NPM).

3.2 Parameters

Sometimes, you cannot specify in advance all the expected values to a Policy. For example, if you want to use the same Policy for two different repositories, then either you make two separate Policies with slightly different hardcoded values, or you can reuse the same Policy but supply during verification runtime different Parameters (e.g., source code repository names and commit trees, or package names and versions). Therefore, we allow users to supply additional Parameters that the Policy Verifier can use to fill in Policies during runtime.

package_name: sigstore
package_version: 3.0.0
github_ref: refs/heads/main
github_repository: sigstore/sigstore-js
github_repository_id: '495574555'
github_repository_owner_id: '71096353'
github_workflow_ref: "{github_repository}/{entry_point}@{github_ref}"
config_source: git+https://github.com/{github_repository}@{github_ref}
entry_point: ".github/workflows/release.yml"

3.3 Policy Verifier

Our Policy Verifier is based on the attestation-verifier, which first substitutes the provided Parameters into the Policy, and checks its expiration as well. If the Policy has expired, it records the error, skips all verification Steps, and the overall result is FAILED. Otherwise, it uses the Policy to verify the Attestations produced by each Step in order (Source ← Build ← Release). For each Step it enforces: (1) cryptographic authenticity by verifying a threshold of signatures, (2) expected inputs and outputs, and (3) expected predicate type and data. If there is no failure, the result is PASSED; otherwise, it is FAILED.

$ go run slsa-e2e-rfp/commands/verify-policy/*.go

Verifying Attestations against Policy...
INFO[0000] Verifying layout expiry...                   
INFO[0000] Done.                                        
INFO[0000] Substituting parameters...                   
INFO[0000] Done.                                        
INFO[0000] Fetching verifiers...                        
INFO[0000] Creating verifier for key 8c249ff34cddc2181a14e679577a2acf38216dc01b95be2c218a9faa79c4e2d0 
INFO[0000] Creating verifier for key 0973a4a0155ab13d35e3cfdd3476eaba91044a8693c5aa5e017d97313bd9dc84 
INFO[0000] Creating verifier for key 937101f8dcc7be06fd1f8c35dbbe9855d784c690e9598a5f11c9b70a0bf46f1f 
INFO[0000] Done.                                        
INFO[0000] Loading attestations as claims...            
INFO[0000] Done.                                        
INFO[0000] Verifying claim for step 'source' of type 'https://slsa.dev/verification_summary/v1' by '937101f8dcc7be06fd1f8c35dbbe9855d784c690e9598a5f11c9b70a0bf46f1f'... 
INFO[0000] Applying material rules...                   
INFO[0000] Evaluating rule `DISALLOW *`...              
INFO[0000] Applying product rules...                    
INFO[0000] Evaluating rule `CREATE https://github.com/sigstore/sigstore-js/*`... 
INFO[0000] Evaluating rule `DISALLOW *`...              
INFO[0000] Applying attribute rules...                  
INFO[0000] Evaluating rule `predicate.verifier.id == 'https://github.com/gittuf/gittuf'`... 
INFO[0000] Done.                                        
INFO[0000] Verifying claim for step 'build' of type 'https://slsa.dev/provenance/v1' by '8c249ff34cddc2181a14e679577a2acf38216dc01b95be2c218a9faa79c4e2d0'... 
INFO[0000] Applying material rules...                   
INFO[0000] Evaluating rule `MATCH https://github.com/sigstore/sigstore-js/* IN git+ WITH PRODUCTS FROM source`... 
INFO[0000] Evaluating rule `DISALLOW *`...              
INFO[0000] Applying product rules...                    
INFO[0000] Evaluating rule `CREATE sigstore-3.0.0.tgz`... 
INFO[0000] Evaluating rule `DISALLOW *`...              
INFO[0000] Applying attribute rules...                  
INFO[0000] Evaluating rule `predicate.buildDefinition.buildType == 'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1'`... 
INFO[0000] Done.                                        
INFO[0000] Verifying claim for step 'release' of type 'https://in-toto.io/attestation/release/v0.1' by '0973a4a0155ab13d35e3cfdd3476eaba91044a8693c5aa5e017d97313bd9dc84'... 
INFO[0000] Applying material rules...                   
INFO[0000] Evaluating rule `MATCH * WITH PRODUCTS FROM build`... 
INFO[0000] Evaluating rule `DISALLOW *`...              
INFO[0000] Applying product rules...                    
INFO[0000] Applying attribute rules...                  
INFO[0000] Done.                                        
INFO[0000] Verification successful!                     
...done.

Generating the Policy VSA...
Key ID: 64b5e39bbf527b383a758f40501005c470df4d5463c3183486da1a04a0cce755 / Public Key: 1f81c9eedcb9325243706a10cd24cf672e5b6f31cd419a4976ea7b6c166181f9
...done.

3.4 Policy Verification Summary Attestation (VSA)

Post verification, the Policy Verifier emits a signed Policy Verification Summary Attestation (VSA) that records:

  • The Policy (and any Parameters),
  • The Attestation Bundle,
  • The final Artifact (sigstore-3.0.0.tgz) as the sole subject
  • The verifier identity,
  • The verification timestamp,
  • The SLSA levels verified (SLSA_SOURCE_LEVEL_3 and SLSA_BUILD_LEVEL_3), and
  • The final verificationResult (PASSED or FAILED).
_type: https://in-toto.io/Statement/v1
subject:
- name: pkg:npm/sigstore@3.0.0
  digest:
    sha512: 3c73227e187710de25a0c7070b3ea5deffe5bb3813df36bef5ff2cb9b1a078c3636c98f31f8223fd8a17dc6beefa46a8b894489557531c70911000d87fe66d78
predicateType: https://slsa.dev/verification_summary/v1
predicate:
  inputAttestations:
  - digest:
      sha2-256: 1cc9c70f6080b3b5cf09092302301c46852c66c7e1a6d42f840e9809b7975aaa
    uri: slsa-e2e-rfp/attestations/build.8c249ff3.json
  - digest:
      sha2-256: 432046ba94d9d86415fa079a0a120aedb4147c75d027c9799f814feeddaf311c
    uri: slsa-e2e-rfp/attestations/release.0973a4a0.json
  - digest:
      sha2-256: e5585d627a08a6eedd384a4ce799a42e3e9518c8860d738621eaaf5273d67d15
    uri: slsa-e2e-rfp/attestations/source.937101f8.json
  - digest:
      sha2-256: 23d65aaf63e5e79440fbdb25e0145f4629778b397e13f6a5c3bce6a8adf074bc
    uri: slsa-e2e-rfp/parameters/source-build-release.json
  policy:
    digest:
      sha2-256: 373c216d5fe49a0177660152824b0b0e3de87ea292522221cfa088c8dbef5579
    uri: slsa-e2e-rfp/policies/source-build-release.yaml
  timeVerified: '2025-09-23T08:16:36.374263393Z'
  verificationResult: PASSED
  verifiedLevels:
  - SLSA_SOURCE_LEVEL_3
  - SLSA_BUILD_LEVEL_3
  verifier:
    id: https://github.com/in-toto/attestation-verifier

This Policy VSA is a portable, auditable record which can be validated on its own. This Policy VSA is implementation-agnostic which means that given the same Policy (including Parameters) and Attestation Bundle, any conformant Policy Verifier must arrive at the same conclusions and produce a semantically equivalent Policy VSA.

4. Verifying Artifacts against Policy VSAs

figure-4

The end-user uses the Policy VSA Verifier to check a given Artifact against the Policy VSA without rerunning the potentially more expensive policy verification. It reads the Artifact (sigstore-3.0.0.tgz) and its Policy VSA, verifies the VSA’s signature, confirms its predicate type (https://slsa.dev/verification_summary/v1), compares the hash of the Artifact against that in the VSA, checks that the policy verification did indeed pass, and enforces freshness by rejecting VSAs older than a configured max age (for e.g., 24 hrs).

$ go run slsa-e2e-rfp/commands/verify-vsa/main.go

Policy VSA verified by public key with ID 64b5e39bbf527b383a758f40501005c470df4d5463c3183486da1a04a0cce755
Artifact name sigstore-3.0.0.tgz and hash 3c73227e187710de25a0c7070b3ea5deffe5bb3813df36bef5ff2cb9b1a078c3636c98f31f8223fd8a17dc6beefa46a8b894489557531c70911000d87fe66d78 is one of the subjects of the Policy VSA
Policy VSA verification: PASSED
Policy VSA is timely: less than a day old

Since the subject in the VSA may list multiple ResourceDescriptors, the local artifact must match at least one ResourceDescriptor with the same filename and digest. If there is a ResourceDescriptor with the same filename but different digest, or the same digest but different filename, then verification fails.

One can think of Policy Verifiers as “compressing” work on behalf of end-users. However, an end-user may instruct the Policy VSA Verifier to independently try to reproduce the Policy VSA given the recorded Artifact, Policy, Parameters, and Attestation Bundle by running policy verification all over again.

5. The big picture: Policy Framework

In summary, we have demonstrated how we can use in-toto Policies to weave E2E stories about software supply chains that connect different Attestations across different SLSA tracks.

We deliberately designed the Policy Framework to be flexible so that implementers and users can use any Policy data format, language, and engine of their choice so long as Policy Verifiers meet a few requirements, namely:

  1. Policies MUST be able to correlate information within and between different Attestations;
  2. Verifiers MUST output Policy Verification Summary Attestations (VSAs) (or equivalent);
  3. Policies MUST be semantically equivalent (i.e., a Policy for one Verifier can be rewritten as another Policy for another Verifier without losing meaning);
  4. Policy VSAs MUST be reproducible (i.e., given an Artifact, Policy, Attestation Bundle, and Policy VSA, the same Verifier or another MUST be able to reproduce essentially the same VSA).

figure-5

Thus, the Policy Framework is able to accommodate a variety of diverse Policy Verifiers, beginning with attestation-verifier, and hopefully more in the near future. This is because different organisations are already using different policy engines of their choice for different applications (e.g., Kubernetes admission controllers) that they would ideally like to reuse for verifying software supply chains. By making the Policy Framework so flexible, SLSA users will be able to use—and even swap—any one of many Policy Verifiers, and still be interoperable. In other words, as illustrated above, a Policy P1 for Verifier V1 can be rewritten as semantically equivalent Policy P2 for Verifier V2. Then, when given the same Artifact and Attestation Bundle, each Verifier would output a reproducible Policy VSA which should cause downstream systems to behave the same.

We want to know whether this vision fits your own use cases, so please help us by reviewing the nascent Policy Framework and sending us feedback there.

This is a guest post. The views expressed are not official positions of the SLSA community or any parent organization. The author has requested and incorporated reviewer feedback whenever possible, but the opinions presented are the author’s alone.