v1.0 Draft Specification

Evidence Pack Specification

A standard format for packaging, signing, and distributing assurance evidence as portable, verifiable ZIP archives.

Evidence Packs solve a fundamental problem in compliance: evidence is scattered across vendors, formats, and portals with no standard way to verify authenticity or track provenance. This spec defines how to bundle evidence into a single archive that anyone can verify without vendor-specific tools.

Conformance Levels

Not every tool needs to implement the full spec. A simple validator only needs Level 1 (pack format). A signing tool adds Level 2 (attestations). This layered approach lets you implement exactly what you need.

Level Description Requirements
Level 1 Pack format only - create and verify pack structure and integrity Parse packs, validate paths, verify digests, reject unsafe archives
Level 2 Add attestation support - sign and verify Sigstore attestations + JCS canonicalization (RFC 8785), Sigstore bundle verification, identity verification
Design Note
Why two levels? Most compliance tools only need to read and validate packs (Level 1). Signing is a separate concern typically handled by the evidence producer. Packs can be distributed via any mechanism (email, cloud storage, registries, etc.).

Pack Format

An Evidence Pack is just a ZIP file with a specific structure. We chose ZIP because it's universally supported, streamable, and already used everywhere. The format is designed so you can verify a pack's integrity without extracting it to disk.

Design Note
Why not a custom format? ZIP is battle-tested, has streaming support in every language, and tools like unzip -l let you inspect contents without code. The tradeoff is that ZIP has legacy quirks (timestamps, compression ratios, platform-specific attributes) that we explicitly ignore for integrity purposes.
Archive Format
R-001 Archive MUST use .epack extension
R-002 Archive MUST use ZIP64 if any file exceeds 4GB or more than 65,535 entries
R-003 Implementations MUST treat only file paths and file bytes as authoritative; ZIP metadata (timestamps, extra fields, comments) MUST be ignored for integrity verification

Directory Structure

Every pack has the same basic structure: a manifest at the root, artifacts in a folder, and optional attestations. This predictability means any tool can find what it needs without parsing the manifest first.

pack.epack
+-- manifest.json              # REQUIRED - describes the pack
+-- artifacts/                 # REQUIRED - the actual evidence files
|   +-- {artifact-files}       # May use subdirectories
+-- attestations/              # OPTIONAL - cryptographic signatures
    +-- *.sigstore.json        # Direct children only, no subdirs
Structure Requirements
R-004 manifest.json MUST exist at the archive root
R-005 artifacts/ directory MUST exist (may be empty)
R-006 Attestation files MUST be direct children of attestations/ (no subdirectories)
R-007 Attestation files MUST use .sigstore.json suffix
R-008 Verifiers MUST reject any top-level entry other than manifest.json, artifacts/, or attestations/

Path Requirements

Paths in a pack must work identically on Windows, macOS, and Linux. This means being strict about character encoding, normalization, and avoiding platform-specific gotchas like Windows device names or path traversal attacks.

Character Set
R-009 Paths MUST be valid UTF-8
R-010 Paths MUST NOT contain NUL bytes, ASCII control characters (U+0000-U+001F, U+007F), or backslashes
R-011 UTF-8 decoding errors are fatal; implementations MUST NOT replace invalid sequences
R-012 artifacts[].path MUST match ZIP entry name exactly (codepoint-for-codepoint)
Design Note
Why NFC normalization? macOS HFS+ stores filenames in NFD (decomposed), while Windows and Linux use NFC (composed). The same visual filename can have different byte representations. By requiring NFC, a pack created on macOS will verify identically on Windows. Without this, you'd get "file not found" errors that are nearly impossible to debug.
Normalization
R-013 Paths MUST be in NFC (Canonical Composition) form
R-014 Implementations MUST reject paths where NFC(path) != path
Structural Rules
R-015 Paths MUST use forward slashes (/) only, no backslashes
R-016 Paths MUST NOT contain .. or . segments, or empty segments (//)
R-017 Paths MUST NOT start with /, Windows drive letters, or UNC paths
R-018 Path length MUST NOT exceed 240 bytes (UTF-8)
R-019 Path segment length MUST NOT exceed 80 bytes (UTF-8)
R-020 Windows device names MUST be forbidden on ALL platforms: con, prn, aux, nul, com1-9, lpt1-9
Why ban Windows device names everywhere? A pack created on Linux with a file named con.json would fail to extract on Windows. By rejecting these names on all platforms, we ensure packs are portable.
Trailing Dots and Spaces
R-020a Path segments MUST NOT end with a dot (.) or a space ( )
Why ban trailing dots and spaces? Windows automatically strips trailing dots and spaces from filenames. This causes path collisions where distinct ZIP entry names resolve to the same filesystem path: report.report, file file. This creates integrity ambiguity during extraction and can be exploited for path confusion attacks.
Directory Entries
R-021 Path ending with / = directory entry (size MUST be 0)
R-022 If platform attributes present, they MUST be consistent with trailing slash
R-023 Directory entries are NOT required; implementations MUST NOT require them

Manifest

The manifest is the table of contents for your pack. It lists every artifact with its digest, declares the overall pack digest, and optionally tracks where the evidence came from. Think of it as the pack's "bill of materials."

JSON Requirements:
  • Duplicate keys: MUST reject at any nesting level. Detection MUST occur during or before parsing.
  • Numbers: MUST be finite (no NaN, Infinity). Integer fields MUST be in range 0..253-1.
  • Unknown fields: MUST reject manifests containing fields not defined in this specification. New fields are introduced through spec version bumps, not silently ignored.
JSON Parsing
R-024 manifest.json MUST be valid UTF-8
R-025 Implementations MUST reject duplicate keys at any nesting level
R-026 Numbers MUST be finite (no NaN, Infinity, -Infinity)
R-027 Integer fields MUST be in range 0..253-1
R-027a Implementations MUST reject manifests containing fields not defined in this specification
{
  "spec_version": "1.0",
  "stream": "published/acme/prod",
  "generated_at": "2026-01-20T15:30:00Z",
  "pack_digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "sources": [
    {
      "name": "github",
      "version": "1.0.0",
      "source": "github.com/locktivity/epack-collector-github",
      "commit": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
      "binary_digest": "sha256:abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
      "artifacts": 5
    }
  ],
  "artifacts": []
}

Manifest Fields

Field Type Required Description
spec_version String Yes MUST be "1.0"
stream String Yes Unique identifier for this evidence stream (opaque)
generated_at String Yes Exact format: YYYY-MM-DDTHH:MM:SSZ (no offsets, no fractions)
pack_digest String Yes sha256: + 64 lowercase hex chars
sources Array Yes List of collectors that contributed artifacts (MAY be empty; informational only)
artifacts Array Yes Index of all artifacts (MAY be empty)
provenance Object No Origin and attestation chain for merged packs
profile String No Reserved for semantic validation. See Semantic Extensibility.
overlays Array No Reserved for semantic validation. See Semantic Extensibility.
profile_lock Array No Reserved for semantic validation. See Semantic Extensibility.
Required Fields
R-028 spec_version MUST be exactly "1.0"
R-029 stream MUST be a non-empty string
R-030 generated_at MUST match format YYYY-MM-DDTHH:MM:SSZ (no offsets, no fractions)
R-031 pack_digest MUST be sha256: + 64 lowercase hex characters

Source Element

Each element in the sources array describes a collector that contributed artifacts. Sources are informational and do not affect verification or pack_digest computation.

FieldTypeRequiredDescription
name String Yes Collector identifier (e.g., github, aws)
version String No Version of the collector that generated the artifacts
source String No Repository path where the collector source code is hosted (e.g., github.com/locktivity/epack-collector-aws)
commit String No Git commit SHA that built the collector binary
binary_digest String No SHA256 digest of the collector binary (sha256: + 64 hex chars)
artifacts Integer No Number of artifacts contributed by this collector
Supply chain provenance: The source, commit, and binary_digest fields enable cryptographic verification of which exact collector binary produced the artifacts. The source field specifies the repository where the source code is hosted, allowing verifiers to locate and inspect the code. When combined with SLSA Level 3 attestations on collector binaries, verifiers can establish a complete chain from source code to evidence output.

Provenance (Merged Packs)

When a pack is created by merging other packs (for example, combining evidence from multiple vendors), the provenance field tracks where everything came from.

Provenance is not trusted alone: Provenance helps understand pack lineage but has no security value unless the manifest is signed. Verifiers MUST NOT trust provenance from unsigned packs.

The provenance.source_packs array contains Source Pack objects with the following structure:

FieldTypeRequiredDescription
stream String Yes Stream identifier of the source pack
pack_digest String Yes Pack digest of the source pack (sha256: + 64 hex)
manifest_digest String Yes JCS-canonicalized SHA-256 of source manifest (64 hex, no prefix)
artifacts Integer Yes Number of artifacts from this source
embedded_attestations Array No Array of Sigstore bundles from the source pack (if signed)

Each element of the embedded_attestations array is a complete Sigstore bundle from the source pack, enabling verifiers to validate signatures without fetching the original pack. To verify source pack integrity, verifiers MUST verify each embedded attestation using the Sigstore trusted root and expected signer identities.

Semantic Extensibility (Reserved Fields)

The manifest reserves three fields for future semantic validation: profile, overlays, and profile_lock. These enable a companion specification to define what artifacts a pack should contain and how they should be validated.

Design Note
Why reserve these fields? Profiles, types, and overlays enable semantic validation (e.g., "this pack must contain a SOC 2 report"). However, these concepts are still evolving. By reserving the fields now with clear constraints, we allow the ecosystem to experiment while preventing incompatible implementations from proliferating.
{
  "spec_version": "1.0",
  "stream": "published/acme/prod",
  "profile": "evidencepack/soc2-basic@v1",           // Reserved - identifier string
  "overlays": ["evidencepack/hipaa-overlay@v1"],     // Reserved - array of identifier strings
  "profile_lock": [                                  // Reserved - pinned digests for reproducibility
    { "id": "evidencepack/soc2-basic@v1", "digest": "sha256:abc123..." },
    { "id": "evidencepack/hipaa-overlay@v1", "digest": "sha256:def456..." },
    { "id": "evidencepack/soc2-report@v1", "digest": "sha256:789def..." }
  ],
  ...
}
Reserved Field Handling
R-028a Level 1 validators MUST ignore profile, overlays, and profile_lock fields unless implementing a companion semantic specification
R-028b If profile is present, it MUST be a non-empty string
R-028c If overlays is present, it MUST be an array of non-empty strings
R-028d If profile_lock is present, it MUST be an array of objects with id (string) and digest (string in sha256:<hex> format)
Identifier Shape

Identifiers in profile, overlays, and profile_lock[].id are opaque strings to v1.0 validators. However, to prevent future migration pain, we recommend the following shape:

  • Format: namespace/name@vN (e.g., evidencepack/soc2-report@v1)
  • Lowercase ASCII alphanumeric, hyphens, underscores; 1-63 characters per segment
  • Version: literal v followed by a positive integer with no leading zeros
Identifier Immutability
R-028e Once a manifest declares an identifier in profile, overlays, or profile_lock, tools MUST NOT silently substitute or upgrade that identifier
R-028f If a tool cannot resolve a declared identifier, it MUST fail explicitly rather than substitute an alternative
Non-semantic in v1.0: These fields have no effect on pack validity or integrity verification in this specification. Their semantics are defined by companion specifications. A pack with profile: "foo" is structurally valid even if no tool understands what "foo" means.

Extension Available

The Profiles & Semantic Validation draft defines how to use these fields for declaring and validating pack requirements.

Artifacts

Artifacts are the actual evidence files in your pack. They come in two flavors: embedded (the bytes are in the ZIP) and referenced (a pointer to an external URL). Most artifacts are embedded, but references are useful for large files or gated content like SOC 2 reports behind NDA portals.

Embedded Artifact

{
  "type": "embedded",
  "path": "artifacts/aws/iam-policies.json",
  "digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "size": 48213,
  "content_type": "application/json",
  "collected_at": "2026-01-20T14:00:00Z",
  "semantic_type": "evidencepack/iam-policies@v1",  // For profile validation (see Profiles spec)
  "metadata": { "region": "us-east-1" },            // Validated against type's metadata_schema
  "controls": ["AC-2", "AC-6"]
}
Embedded Artifact Requirements
R-032 type MUST be "embedded"
R-033 path, digest, and size are REQUIRED
R-034 All path values MUST be unique across the manifest (using Windows-canonical comparison)
R-034a Path uniqueness MUST use Windows-canonical comparison: strip trailing dots/spaces, lowercase, then compare
R-035 SHA256(bytes) MUST match artifact.digest
R-036 Byte count MUST match artifact.size

Referenced Artifact

Referenced artifacts point to external bytes (gated portals, VDRs). They are informational pointers excluded from pack_digest because the bytes aren't in the pack.

{
  "type": "reference",
  "name": "soc2-type-ii-report",
  "uri": "https://trust.vendor.com/portal/soc2",
  "access": { "policy": "nda_required" },
  "digest": "sha256:..."  // Optional - if present and bytes fetched, MUST validate
}
External URI Handling
R-037 Clients MUST NOT automatically fetch external URIs
R-038 Fetching MUST require explicit user approval or policy allowlist
R-039 URI scheme MUST be https
R-040 Implementations MUST reject URIs with userinfo or fragments
R-041 Clients SHOULD resolve host and reject loopback, link-local, and private network ranges
R-041a Implementations SHOULD normalize IP addresses to canonical form and handle obfuscated representations (decimal, hex, octal, IPv4-mapped IPv6, 6to4)
R-041b For IDN hostnames, implementations SHOULD normalize to ASCII (Punycode) form before validation
R-041c Redirect handling SHOULD re-validate scheme and host restrictions before request body or credentials are sent
R-041d Clients MUST NOT send Authorization headers or secrets to a different origin than the original URI
R-041e Clients MUST redact access tokens or credentials from logs and error messages
Design Note
Why block automatic fetching? A malicious pack could include URIs pointing to internal services (SSRF), tracking pixels, or simply waste bandwidth. The pack creator controls the URIs, so clients must treat them as untrusted input.

Integrity

The pack digest is a fingerprint of all embedded artifacts. If any artifact changes, the digest changes. This lets you verify a pack's contents with a single hash comparison and enables content-addressed storage (two packs with identical content have identical digests).

Pack Digest Computation

The algorithm is designed to be deterministic across implementations:

  1. List all embedded artifacts from the manifest
  2. Verify each artifact's SHA256(bytes) matches artifact.digest
  3. Build canonical list: {path}\t{digest}\n per artifact (tab = 0x09, newline = 0x0A)
  4. Sort lines by byte-wise lexicographic ordering on raw UTF-8 bytes (like memcmp)
  5. Concatenate sorted lines. Each line ends with exactly one \n. No trailing newline after final entry.
  6. Empty artifact list = empty byte string (zero bytes)
  7. Compute SHA256 of concatenated result
  8. Format as sha256:{hex} (lowercase)
Pack Digest
R-042 Lines MUST be sorted by byte-wise lexicographic ordering on raw UTF-8 bytes
R-043 Format MUST be {path}\t{digest}\n (tab = 0x09, newline = 0x0A)
R-044 Digest input MUST NOT include an additional trailing newline or empty line after the final entry
R-045 If the embedded artifact list is empty, digest input MUST be the empty byte string
Excluded from pack_digest: manifest.json (contains the digest itself), all files under attestations/ (added after pack generation), and referenced artifacts (bytes are external).

Verification Steps

Pack Verification
R-046 Verifiers MUST parse manifest.json
R-047 Verifiers MUST reject files under artifacts/ not listed as embedded artifacts
R-048 Verifiers MUST reject embedded artifacts listed in manifest but missing from ZIP
R-049 Verifiers MUST reject duplicate ZIP entry names
R-050 Verifiers MUST recompute pack digest and compare to manifest.pack_digest

Two-Digest Model

Evidence Packs use two digests for different purposes:

Digest Computed From Purpose
pack_digest Canonical list of embedded artifacts Content-addressed identifier; enables deduplication and integrity checks
manifest_digest JCS-canonicalized manifest.json Stable signing target; allows attestations without changing signed content

Format conventions: pack_digest uses the prefixed format (sha256:hex) as a self-describing content identifier. manifest_digest uses raw hex (no prefix) to match in-toto's digest map structure where the algorithm is the key.

Design Note
Why two digests? The pack_digest covers artifact content but not metadata. The manifest_digest covers the entire manifest including metadata. Attestations sign the manifest digest because it's stable: you can add new attestations to a pack without invalidating existing signatures. If attestations signed the pack digest, adding a signature would change the thing being signed.

Security Considerations

ZIP files are a common attack vector. Malicious archives can contain path traversal attacks, symlinks that escape extraction directories, or zip bombs that expand to enormous sizes. These requirements ensure packs can be safely extracted and verified.

Size Limits

Implementations MUST enforce limits. Defaults are recommended; minimums are mandatory floors.

Limit Default Minimum Rationale
Max artifact size 100 MB 1 MB Prevents memory exhaustion
Max pack size 2 GB 10 MB Reasonable upper bound
Max artifact count 10,000 100 Prevents file handle exhaustion
Max compression ratio 100:1 N/A Mitigates zip bomb attacks
Size Enforcement
R-051 Implementations MUST enforce configurable size limits
R-052 Implementations MUST track total uncompressed bytes during extraction
R-053 Implementations MUST abort immediately when limits exceeded (streaming extraction)
Streaming extraction required: Track total uncompressed bytes during extraction and abort immediately when limits exceeded. Do NOT buffer entire content before checking. Account for delta bytes, not cumulative.
Archive Entry Types
R-054 Implementations MUST reject symlinks (check mode bits before extraction)
R-055 Implementations MUST reject device files and special entries
R-056 Implementations MUST NOT attempt to detect hard links via ZIP metadata (unreliable)
R-057 Implementations SHOULD reject AppleDouble entries (__MACOSX/, ._ prefixed files)
R-058 Implementations SHOULD use O_NOFOLLOW when writing extracted content
Additional Security
R-059 Implementations MUST reject archives with duplicate paths (byte-for-byte comparison)
R-060 Implementations SHOULD extract to temp directory and atomically rename after validation
R-061 Implementations SHOULD ignore ZIP permission bits and apply safe defaults (0644/0755)
R-062 Implementations MUST NOT log access tokens, Authorization headers, or credentials in URLs, and MUST redact sensitive query parameters and headers in logs or error messages

Attestation Format

Attestations are cryptographic signatures that prove a pack came from a specific source. Evidence Packs use Sigstore for keyless signing, where the signer authenticates via OIDC (GitHub, Google, Microsoft) and receives a short-lived certificate from Fulcio. All signatures are recorded in Rekor, a public transparency log.

Design Note
We chose Sigstore because it eliminates key management complexity. No long-lived keys to secure, rotate, or distribute. The signer's identity is cryptographically bound to their OIDC account (GitHub user, Google email, CI/CD workflow identity). The transparency log provides non-repudiation and auditability.

Sigstore Bundle Structure

Attestation files use the Sigstore bundle format with media type application/vnd.dev.sigstore.bundle.v0.3+json:

{
  "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
  "verificationMaterial": {
    "x509CertificateChain": {
      "certificates": [{ "rawBytes": "<base64-encoded-certificate>" }]
    },
    "tlogEntries": [{
      "logIndex": "123456",
      "logId": { "keyId": "<base64>" },
      "integratedTime": "1234567890",
      "inclusionProof": {
        "logIndex": "123456",
        "rootHash": "<base64>",
        "treeSize": "1000000",
        "hashes": ["<base64>", "..."]
      }
    }]
  },
  "dsseEnvelope": {
    "payloadType": "application/vnd.in-toto+json",
    "payload": "<base64(in-toto statement)>",
    "signatures": [{ "keyid": "", "sig": "<base64-signature>" }]
  }
}

in-toto Statement (Decoded Payload)

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [{
    "name": "manifest.json",
    "digest": {"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}
  }],
  "predicateType": "https://evidencepack.org/attestation/v1",
  "predicate": {}
}
Sigstore Bundle Requirements
R-062a mediaType MUST be "application/vnd.dev.sigstore.bundle.v0.3+json" or a later compatible Sigstore bundle media type
R-063a verificationMaterial MUST contain certificate chain and transparency log proof
R-064a dsseEnvelope.payloadType MUST be "application/vnd.in-toto+json"
R-065e dsseEnvelope.signatures array MUST contain at least one signature
R-065a All Base64 fields MUST use RFC 4648 standard Base64 with padding
R-065b URL-safe Base64 variants (- and _) MUST NOT be used
R-065c When decoding, implementations SHOULD accept both padded and unpadded forms
R-065d When encoding, implementations MUST produce canonical RFC 4648 with padding
in-toto Statement Requirements
R-067 _type MUST be "https://in-toto.io/Statement/v1"
R-068a subject array MUST contain exactly one subject
R-068b subject[0].name MUST be "manifest.json"
R-068c subject[0].digest.sha256 MUST be 64 lowercase hex characters (manifest_digest without "sha256:" prefix)
R-068d predicateType MUST be "https://evidencepack.org/attestation/v1"
R-068e predicate MUST be an empty object {}

Attestation File Naming

Attestation files MUST be named {identity-hash}.sigstore.json where identity-hash is the first 8 characters of the SHA-256 hash of the certificate's subject identity.

attestations/
├── a1b2c3d4.sigstore.json    # security@acme.com
├── e5f6g7h8.sigstore.json    # auditor@thirdparty.com
└── i9j0k1l2.sigstore.json    # github.com/org/repo workflow

Signing Process

Signing uses Sigstore for keyless, identity-based signatures. The signer authenticates via OIDC, receives a short-lived certificate from Fulcio, and the signature is recorded in Rekor's transparency log.

Manifest Digest Computation

The manifest digest (used as the attestation subject) is computed using JCS (RFC 8785):

JCS Requirements
R-068 Object keys MUST be sorted recursively by UTF-16 code unit comparison (not UTF-8 byte order)
R-069 Canonicalized JSON MUST use no whitespace between tokens
R-070 Numbers MUST use shortest decimal representation
R-071 Implementations MUST reject non-finite numbers (NaN, Infinity)
R-072 Implementations MUST reject documents with duplicate keys at any nesting level

Signing with Sigstore

Using cosign to sign the manifest:

# Extract manifest from pack
unzip -p pack.epack manifest.json > manifest.json

# Sign with Sigstore (opens browser for OIDC authentication)
cosign sign-blob manifest.json \
  --bundle attestations/signer.sigstore.json \
  --yes

# Add attestation to pack
zip pack.epack attestations/signer.sigstore.json
Signing Requirements
R-073 Signers MUST authenticate via OIDC to obtain Fulcio certificate
R-074 Signatures MUST be recorded in Rekor transparency log
R-075 Sigstore bundles MUST include inclusion proof for offline verification
Design Note
Unlike traditional PKI, Sigstore certificates are short-lived (typically 10 minutes). The transparency log entry's timestamp proves the signature was created during the certificate's validity period. This eliminates the need for key rotation and long-term key storage.

Verification Process

Verification uses the Sigstore trusted root (Fulcio CA, Rekor public key) to validate the bundle. The bundle is self-contained with stapled proofs, enabling offline verification without network access.

Verification Steps
R-076 Verifiers MUST parse the Sigstore bundle JSON
R-076a Verifiers MUST verify certificate chain against Fulcio CA
R-076b Verifiers MUST verify transparency log inclusion proof against Rekor public key
R-077 Verifiers MUST recompute manifest digest from manifest.json using JCS and compare to subject[0].digest.sha256
R-078 Verifiers MUST verify pack_digest per pack specification
R-079 Verifiers MUST verify DSSE signature over the payload
R-080 If expected identities are specified, verification MUST fail when certificate identity does not match

Verification Failures

Failure Conditions
R-084 Verification MUST fail if Sigstore bundle is malformed
R-085 Verification MUST fail if certificate chain is invalid or untrusted
R-086 Verification MUST fail if transparency log proof is invalid
R-087 Verification MUST fail if manifest digest doesn't match subject[0].digest.sha256
R-088 Verification MUST fail if signature verification fails
R-088c Verification MUST fail if payloadType is not "application/vnd.in-toto+json"
R-088d Verification MUST fail if decoded statement is not valid JSON
R-088e Verification MUST fail if _type is not "https://in-toto.io/Statement/v1"
R-088a Verification MUST fail if predicateType is not "https://evidencepack.org/attestation/v1"
R-088f Verification MUST fail if subject[0].name is not "manifest.json"
R-088g Verification MUST fail if predicate is not an empty object {}
R-088b Verification MUST fail if expected identity specified but doesn't match certificate

Identity Verification

Sigstore certificates contain the signer's identity from their OIDC provider. Verifiers specify which identities they trust, such as specific email addresses or CI/CD workflow identities.

Common Identity Types

OIDC Provider Identity Example
GitHub Actions https://github.com/org/repo/.github/workflows/release.yaml@refs/heads/main
Google user@example.com
Microsoft user@example.com
GitHub (personal) user@users.noreply.github.com
Identity Requirements
R-089 Verifiers MUST explicitly configure which identities to trust
R-090 Verifiers MUST NOT treat a valid Sigstore signature alone as trusted signer identity
R-091 Organizations SHOULD use CI/CD workload identities where possible
Identity trust is policy: Verifiers must explicitly configure which identities to trust. A valid signature only proves the signer authenticated via OIDC; it doesn't mean you should trust them.
Design Note
Unlike traditional PKI with long-lived keys, Sigstore's keyless model means identity compromise (OIDC account) is the attack vector. Organizations should use CI/CD workload identities where possible, enable MFA on OIDC accounts used for signing, and monitor Rekor for unexpected signatures from their identities.

Test Vectors

Test vectors let you verify your implementation is conformant. Each vector includes a pack (valid or invalid) and the expected result. Run them against your code to catch edge cases before they become production bugs.

Conformance Level Test Vector Categories Key Requirements
Level 1 Pack format, paths, manifest, integrity R-001 - R-062
Level 2 Attestation format, signing, verification R-062a, R-063a, R-064a, R-067 - R-091

Level 1: Pack Format

valid-minimal-pack
Minimal valid pack with manifest and empty artifacts directory
valid-single-artifact
Pack with one embedded artifact, valid digest and size
invalid-path-traversal
Pack with ../ in artifact path - MUST reject
invalid-non-nfc-path
Pack with NFD-normalized path - MUST reject
invalid-symlink
Pack containing a symlink entry - MUST reject
invalid-duplicate-keys
Manifest with duplicate JSON keys - MUST reject
invalid-digest-mismatch
Pack where artifact bytes don't match declared digest - MUST reject
invalid-windows-device-name
Pack with artifacts/con.json path - MUST reject

Level 2: Attestation

valid-sigstore-bundle
Valid Sigstore bundle with certificate chain and inclusion proof
invalid-certificate-chain
Sigstore bundle with invalid certificate chain - MUST reject
invalid-manifest-digest
Attestation where subject digest doesn't match actual manifest - MUST reject
jcs-key-ordering
Verify JCS uses UTF-16 code unit sorting, not UTF-8 byte order
identity-mismatch
Valid bundle but certificate identity doesn't match expected - MUST reject
Test vector files: Complete test vector packs and expected results are available in the test-vectors directory of the reference implementation.

References

Sigstore & Signing

RFCs

Evidence Pack Specification v1.0 - Draft