Security Model
How Evidence Packs use cryptographic digests and Sigstore signatures to provide security guarantees.
Core Security Principles
epack is designed with security as a primary concern. The tool handles sensitive compliance evidence and must protect both the integrity of packs and the credentials used during collection.
Design Principles
- Cryptographic Integrity: SHA-256 digests for all artifacts
- Provenance Verification: Sigstore signatures with identity binding
- Least Privilege: Only specified secrets passed to collectors
- Defense in Depth: Multiple validation layers
- Fail Secure: Default to strictest verification mode
Integrity Verification
Every Evidence Pack includes cryptographic integrity guarantees through artifact digests.
Run epack verify --integrity-only to check digests without requiring signatures.
How artifact digests work
Each artifact's SHA-256 hash is computed and recorded in the manifest when you run epack build.
During epack verify, the hash is recomputed and compared. Any modification to the artifact
will produce a different hash, causing verification to fail.
# Verify integrity only (no signature required) $ epack verify --integrity-only evidence.pack ✓ Pack integrity verified Pack digest: sha256:a3f2b8c9d4e5f6... Artifacts: 12 verified No attestations present
{
"artifacts": [
{
"path": "artifacts/soc2-report.pdf",
"digest": "sha256:a3f2b8c9d4e5f6...",
"size": 1048576
}
]
}
Pack digest
The pack_digest is a hash of all artifact paths and their individual digests,
providing a single value that covers the entire pack contents. This is what gets signed,
so a single signature authenticates all artifacts. View it with epack inspect:
$ epack inspect evidence.pack --format json | jq .pack_digest
"sha256:a3f2b8c9d4e5f6a7b8c9d0e1f2..."
What integrity verification detects
- Any modification to artifact content
- Addition or removal of artifacts
- Renaming artifacts
- Manifest tampering (causes pack_digest mismatch)
Sigstore Signing
epack uses Sigstore for
signing via epack sign. Keyless signatures are tied to OIDC identities, eliminating
key management while providing strong provenance guarantees.
# Sign a pack (opens browser for authentication) $ epack sign evidence.pack Opening browser for authentication... Authenticated as: security@vendor.com (Google) ✓ Pack signed successfully Signer: security@vendor.com Issuer: https://accounts.google.com Rekor entry: https://rekor.sigstore.dev/api/v1/log/entries/...
How Sigstore signing works
- User authenticates via OIDC (Google, GitHub, Microsoft, etc.)
- Fulcio issues a short-lived certificate (~10 minutes)
- The certificate embeds the authenticated identity
- The manifest digest is signed with the temporary key
- The signature is recorded in the Rekor transparency log
- The complete bundle is stored in the pack
What's in an attestation bundle
Each attestation stored in the pack includes:
- The digital signature over the manifest digest
- The signing certificate with identity claims
- The certificate chain to Fulcio root
- The Rekor transparency log entry
- Timestamp (optional, for long-term verification)
View attestation details with epack inspect:
$ epack inspect evidence.pack --attestations
Attestations (1):
[0] Sigstore Bundle
Subject: security@vendor.com
Issuer: https://accounts.google.com
Signed: 2024-02-15T10:30:00Z
Rekor: 24296fb24b8ad77a...
CI/CD signing with OIDC
In GitHub Actions, grant id-token: write and run epack sign; epack
mints the Sigstore OIDC token automatically. Other CI systems can still pass
EPACK_OIDC_TOKEN explicitly. The Sigstore certificate embeds additional claims
about the source:
permissions: id-token: write - name: Sign pack run: epack sign evidence.pack
The certificate includes:
- The commit SHA that triggered the workflow
- The repository URL
- The workflow path
- The ref (branch or tag)
This means the signature proves exactly which code generated the evidence. Verifiers can check the exact immutable version that ran.
Signing with a key
A run with nobody at the keyboard can sign with a key instead: epack sign --key or
epack run --key. The attestation then carries the public key rather than a certificate, so the
signature names no person. It is still recorded in the Rekor transparency log unless you sign with
epack sign --no-tlog. A verifier checks it against the signer's public key, and the key that
verifies names the signer by the SHA-256 of its public key:
epack verify evidence.epack --public-key signer.pem
epack key create makes a key for the machine, keeps the private half in
~/.epack/keys/ readable only by you, and registers the public half with the remote, which can hold
it until someone approves it.
Configurations from a Remote
epack remote clone and epack run <name> fetch a project a remote wrote: its
epack.yaml, hooks, and lockfile. That makes the remote an author of what runs on your machine, so
epack holds a fetched configuration to these rules:
- Files are written only inside the folder you named, never into
.epack/collectors/,.epack/remotes/, or.git/ - Every binary the lockfile names is downloaded through the registry and checked against its recorded Sigstore identity before it runs
- A hook script that still matches the delivered template never runs, so a fetched configuration cannot run shell on your machine
- Files you edited are never overwritten without
--force - After a fetch that changed something, epack prints each collector, tool, and remote it will run with its publisher and version, and the environment variables it reads. A variable counts as set when the environment or your keychain holds it for that configuration
- Collectors, tools, and remote adapters may come only from publishers you trust. A publisher is the GitHub owner of a component's source repository
- A run by name checks the revision it fetched before installing anything. Trust in a new publisher is offered only for that revision, never for an earlier one the run falls back to
- A job with no folder signs in for its configuration only through an adapter from a publisher it trusts, checked before the adapter is installed or run
In a terminal, epack asks once for each new publisher and records your answer in ~/.epack/config.yaml.
Signing in to a remote with epack remote login trusts its adapter's publisher. Without a terminal,
the run stops before any download unless EPACK_TRUSTED_PUBLISHERS or --trust-publisher
names the publisher, and --yes never widens trust. A run by name runs the newest earlier revision
that needs no new publisher instead, when the remote has one. Folders without a fetch record, such as a
committed repository, are not affected.
The collectors, tools, and secrets a remote names remain your trust decision. Treat admin access on a remote you sign in to accordingly.
Threat Model
In scope (what epack protects against)
- Tampered packs: Detecting modifications to artifacts or manifest
- Forged signatures: Rejecting invalid or unauthorized attestations
- Malicious collectors: Preventing arbitrary code execution from untrusted sources (via lockfile verification)
- Secret leakage: Protecting credentials from exposure in logs or errors
- Path traversal: Preventing extraction outside target directory
Out of scope
- Physical access to the machine running epack
- Compromise of your OIDC identity provider
- Compromise of Sigstore infrastructure
- Side-channel attacks during cryptographic operations
Security non-goals
- Validating the accuracy of evidence (only integrity)
- Preventing authorized users from creating misleading packs
- Sandboxing collectors beyond environment isolation
Two Editions, Different Security Profiles
epack comes in two editions with different security profiles:
- No subprocess execution
- No binary downloads
- No network requests (except Sigstore)
- Read/write local files only
- Ideal for verification pipelines
- Downloads collector binaries
- Executes collectors as subprocesses
- Manages credentials via environment
- Requires lockfile verification
- Use
--frozenmode in CI
epack-core for verification-only workflows.
Use the full edition only when automated collection is required.
Collector Security
The full edition downloads and executes collector binaries, which requires careful security controls.
Binary verification
- Collectors are downloaded from GitHub releases
- SHA-256 digest is recorded in lockfile
- Digest is verified before execution
- Signer identity is verified via Sigstore
Environment isolation
- Only explicitly listed secrets are passed to collectors
- An entry marked optional is passed through only when set, and the run never requires it
- Reserved environment prefixes are blocked (
EPACK_,LD_, etc.) - Collectors cannot access other environment variables
File artifact containment
- Each collector gets a private staging directory for file artifacts
- File artifact paths must be relative and stay beneath that staging directory
- Symlinks and non-regular files are rejected before bytes enter the pack
- Staged file bytes count toward artifact and pack size limits
Lockfile security
The lockfile (epack.lock.yaml) provides reproducible builds by pinning
exact versions, platform-specific binary digests, and signer identities. Each locked
collector, tool, and remote also records a verification status:
verified when its signature bundle was checked at lock time, or
skipped for external binaries that have no bundle to verify.
--frozen mode in CI/CD to ensure
lockfile verification. This prevents unexpected changes to collector binaries.
Secret Management
Configuration
Secrets and managed credentials are specified in epack.yaml by reference only.
Values come from environment variables or from the Locktivity-managed broker, which resolves short-lived env bundles at runtime.
credential_sets: github_repo: credset_abc123 collectors: github: source: locktivity/epack-collector-github@^0.2.0 credentials: - github_repo # Broker resolves a short-lived GITHUB_TOKEN # Or for manual setups: # secrets: # - GITHUB_TOKEN
Secret protection
- Redaction: Collector and adapter output in error messages has every passed secret value replaced, then secret-looking patterns redacted. Values shorter than six characters are not matched
- Process isolation: On Unix, collectors and adapters run in their own process group, and a timeout kills the group, so nothing a collector started outlives the run with its environment
- Secret scanning: The
buildcommand warns about potential secrets in artifacts - Minimal exposure: Only listed secrets or resolved credential bundles are passed to components
- OIDC isolation: Raw GitHub OIDC request variables are blocked from explicit pass-through to untrusted components
Stored secrets
On a laptop, epack secret set keeps a listed variable in the operating system keychain, scoped to
one configuration on one remote. A run reads the environment first and the keychain second; CI never reads the
keychain. What the keychain protects is encryption at rest and isolation from other user accounts, not
isolation from code running as the same user: on every platform a same-user process can read what epack
stored, as it can read an exported variable.
Each stored secret is approved for the components that read it, as they were when it was stored, with a digest
of each reader's entry in epack.yaml. A revision that changes a reader's entry, or adds a reader,
is shown and must be approved in epack secret approve or epack secret setup before
the value is used again. epack run and epack run --check never prompt and stop with
the command instead. Storing or approving one secret never approves another. The keychain is consulted only
after the publisher trust gate, and the lookup behind a fetch summary never prompts.
What epack records about stored secrets, names, readers, and times but never values, lives in
~/.epack/secrets.yaml, readable only by the user. Values set in the environment stay outside the
approval guarantee; the check shows them with the source env.
Vulnerability Reporting
If you discover a security vulnerability in epack, please report it responsibly. Do not open public GitHub issues for security vulnerabilities.
See Also
- Trust Levels — choosing the right verification level
- CLI Reference: verify — verification command details
- CI/CD Integration — secure automation patterns