Cryptographic proof that your build is what it claims to be.
VBW creates tamper-evident records proving that specific source code, built with a specific toolchain, in a specific environment, produced specific artifacts. Every build ships with a signed witness bundle that anyone can independently verify.
When someone downloads your software, they trust that the binary matches the source code. But how would they verify that? Today, most projects ship artifacts with no proof of origin. The gap between "source code on GitHub" and "binary on your machine" is a black box — and attackers know it. Supply chain attacks (SolarWinds, codecov, event-stream) exploit exactly this gap.
The industry has responded with several approaches. Here's what conventional best practice looks like:
| Approach | What It Does | Limitations |
|---|---|---|
| Sigstore / cosign | Signs artifacts using ephemeral OIDC keys; records signatures in a transparency log | Requires an external transparency log service (Rekor). Signatures prove who signed, not how it was built. No environment or dependency capture. |
| SLSA Framework | Defines 4 levels of supply chain security maturity; provenance metadata via in-toto attestations | Provenance is a separate attestation stored in a registry, not alongside the artifact. Achieving Level 3+ requires a hardened build platform. Complex to adopt. |
| in-toto | Defines a layout of expected build steps; each step produces a signed "link" attestation | Powerful but heavyweight — requires defining a full layout of steps, functionaries, and inspection rules before you start. Designed for multi-party pipelines, not single-team builds. |
| Reproducible Builds | Ensures identical source produces byte-identical output | Extremely difficult to achieve in practice. Many build tools embed timestamps, paths, or randomness. Proves reproducibility but not provenance. |
| GPG-signed tags/releases | Developer signs a git tag or release archive with their GPG key | Proves who signed, not what environment built it. No artifact hashing, no dependency capture, no build transcript. Key management is painful. |
These are good tools. VBW doesn't replace them — it addresses a gap they leave open.
VBW is a build-time witness — it wraps your existing build command, captures everything that happened, and packages the evidence into a single portable bundle that ships alongside your artifacts. No external services required.
Here's what makes it distinct:
Conventional tools store provenance in a separate registry, transparency log, or attestation store. VBW produces a vbw/ directory that lives right next to your build output. Anyone with the bundle can verify it — no network calls, no registry lookups, no accounts.
Most signing tools answer "who signed this?" VBW answers six questions: what source was built, what tools compiled it, what OS/container ran the build, what dependencies were locked, what artifacts were produced, and who attested to all of it. Every answer is hashed and signed together.
No layout files to define. No build platform to migrate to. No SLSA level to achieve first. Run scqcs vbw build -- npm run build and you get a complete witness bundle. Works with npm, Cargo, Go, Make, or a plain cp command.
Instead of requiring full determinism (which most projects can't achieve), VBW defines three modes:
- Mode A (Deterministic) — no network, pinned tools, byte-identical output
- Mode B (Locked Network) — dependencies from lockfiles, practical for most teams
- Mode C (Witnessed Non-Deterministic) — full provenance without a reproducibility guarantee
You pick the mode that matches your reality. The bundle honestly records which mode was declared.
Every file in the bundle is plain JSON. You can cat manifest.json and read it. You can diff two bundles with standard tools. You can write your own verifier in any language. The format is not a binary blob or a protobuf — it's designed for humans and machines equally.
If you ship software and can't answer "prove this binary came from that commit" — VBW gives you that answer in one command. If you already use Sigstore or SLSA, VBW complements them by capturing the build context those tools don't record.
The goal is not to replace the ecosystem. It's to make build provenance accessible enough that small teams actually use it instead of treating it as a someday problem.
VBW v1.0 is a working implementation — the CLI builds, signs, and verifies real bundles. The core pipeline (hashing, signing, verification) is production-grade cryptography.
What works today:
- Ed25519 key generation, signing, and verification (real, not demo)
- SHA-256 hashing of all source trees, lockfiles, and output artifacts (real, streaming for large files)
- Canonical JSON signing: signature covers deterministic canonical manifest bytes (sorted keys, compact JSON), not the pretty-printed file on disk
- Git commit/branch/dirty detection (real)
- Build transcript capture with interleaved stdout/stderr and ISO-8601 timestamps
- Strict fail-closed verify pipeline: hash checks, signature verification, bundle completeness, unexpected file detection, path traversal rejection, symlink escape detection
- Enforcement honesty: manifest records what was actually enforced vs. requested. Mode A attempts network namespace isolation via
unshare -rn; Mode B checks lockfile integrity before/after build. - GitHub Actions integration
What is not yet implemented (TODOs):
- Vendor tarball hashing (
archive_sha256/extracted_tree_hashfields are always empty) - Source tree hash re-verification during
verify(the stored hash is checked for integrity but not recomputed from the local repo) - Schema validation of bundle JSON against the published schemas
- Individual dependency artifact verification from lockfiles. Lockfiles are hashed; individual dependency artifact verification is future work.
Known limitations:
- Environment capture requires Unix (
uname,which) — falls back to "unknown" on other platforms - Container detection is heuristic (checks
/.dockerenv,/proc/self/cgroup)
| Question | How VBW Answers It |
|---|---|
| What exact source was built? | Git commit hash + canonical source tree hash |
| What tools compiled it? | Compiler/runtime versions captured in environment.json |
| What OS/container ran the build? | OS, kernel, architecture, container digest |
| Can this build be reproduced? | Reproducibility mode recorded (Mode A/B/C) |
| Has the output been tampered with? | SHA-256 hashes of every artifact in outputs.json |
| Who attested to all of this? | Ed25519 signature over canonical manifest bytes |
Note on reproducibility: VBW attempts to enforce reproducibility modes at build time. Mode A uses
unshare -rnfor network namespace isolation and setsSOURCE_DATE_EPOCH. Mode B snapshots lockfile hashes before and after the build to detect modifications. If enforcement partially fails (e.g., user namespaces unavailable for Mode A), the manifest honestly recordsmode_enforced=falsewith a note explaining what could not be enforced.
- Rust toolchain (1.70+)
- Git
cd tools/scqcs
cargo build --releaseThe binary is at tools/scqcs/target/release/scqcs.
./tools/scqcs/target/release/scqcs vbw keygen --output ~/.scqcsOutput:
Ed25519 keypair generated:
Secret key: /home/you/.scqcs/vbw-builder.sk
Public key: /home/you/.scqcs/vbw-builder.pk
Public key (base64): <your-public-key>
To use in CI, set the secret key as:
SCQCS_VBW_ED25519_SK_B64=<your-secret-key>
Keep vbw-builder.sk secret. Share vbw-builder.pk publicly.
./tools/scqcs/target/release/scqcs vbw build \
--keyfile ~/.scqcs/vbw-builder.sk \
--project my-project \
--output-dir dist \
-- npm run buildThis runs npm run build, then generates a vbw/ directory with the full witness bundle.
./tools/scqcs/target/release/scqcs vbw verify --bundle vbwOutput:
[vbw] Verifying build: a1b2c3d4-...
[vbw] Project: my-project
[vbw] Git commit: abc1234...
[vbw] Manifest hash: OK
[vbw] Builder signature: OK
[vbw] environment.json: OK
[vbw] materials.lock.json: OK
[vbw] outputs.json: OK
[vbw] Policy hash: OK
[vbw] Output artifacts: 12 checked
VERIFIED
After a build, the vbw/ directory contains:
vbw/
manifest.json # The signed statement — links everything together
environment.json # OS, compiler versions, container digest
materials.lock.json # Dependency lockfile hashes
outputs.json # Artifact paths, SHA-256 hashes, sizes
transcript.txt # Full build log (interleaved stdout/stderr with timestamps)
policy.json # Build policy requirements
signatures/
builder.ed25519.sig # Ed25519 signature over canonical manifest bytes
hashes/
manifest.sha256 # SHA-256 of canonical manifest bytes
Canonical signing: The signature and manifest hash are computed over canonical manifest bytes (sorted keys, compact JSON), not the pretty-printed
manifest.jsonfile on disk. The file on disk is human-readable; verification re-canonicalizes the parsed manifest to check the signature. This ensures byte-level signing stability regardless of JSON formatting.
The core document. It doesn't contain data directly — it contains hashes of all other files, creating a single signed root of trust.
// ILLUSTRATIVE EXAMPLE — hashes are shortened placeholders, not real values
{
"vbw_version": "1.0",
"build_id": "a1b2c3d4-e5f6-...",
"created_at": "2026-02-08T12:00:00Z",
"project": { "name": "my-project" },
"git": {
"commit": "abc1234def5678...",
"branch": "main",
"dirty": false
},
"source_commit_tree_hash": "aabbccdd...(64 hex chars total)...",
"materials_lock_hash": "11223344...(64 hex chars total)...",
"environment_hash": "55667788...(64 hex chars total)...",
"outputs_hash": "99aabbcc...(64 hex chars total)...",
"builder_identity": {
"key_id": "builder@ci",
"public_key_ed25519": "Base64EncodedEd25519PublicKey44chars="
},
"policy_ref": {
"path": "vbw/policy.json",
"hash_sha256": "ddeeff00...(64 hex chars total)..."
}
}Captures the build machine state. This is real data captured from the OS at build time.
// ILLUSTRATIVE EXAMPLE — values will differ on your machine
{
"os": {
"name": "Linux",
"version": "6.5.0-44-generic",
"kernel": "#44-Ubuntu SMP ...",
"arch": "x86_64"
},
"tools": [
{ "name": "node", "version": "v20.11.0", "path": "/usr/bin/node" },
{ "name": "npm", "version": "10.2.4", "path": "/usr/bin/npm" }
],
"reproducibility": {
"mode": "B_LOCKED_NETWORK",
"network": { "allowed": true, "allowlist": [] }
}
}Every artifact is hashed and measured. These are real SHA-256 hashes of the actual files.
// ILLUSTRATIVE EXAMPLE — hashes and sizes are placeholders
{
"artifacts": [
{
"path": "dist/index.html",
"sha256": "aabbccdd...(64 hex chars)...",
"size_bytes": 15234,
"mime": "text/html"
},
{
"path": "dist/main.js",
"sha256": "eeff0011...(64 hex chars)...",
"size_bytes": 42891,
"mime": "application/javascript"
}
]
}Generate an Ed25519 keypair for signing builds.
scqcs vbw keygen [--output <dir>]| Option | Default | Description |
|---|---|---|
--output |
. (current dir) |
Directory to write key files into |
Produces two files:
vbw-builder.sk— secret key (keep private, use in CI as a secret)vbw-builder.pk— public key (distribute freely)
Run a build command and generate a witness bundle.
scqcs vbw build [options] -- <build-command...>| Option | Default | Description |
|---|---|---|
--project |
Directory name | Project name in the manifest |
--output-dir |
dist |
Where build artifacts live |
--keyfile |
— | Path to Ed25519 secret key file |
--key-id |
builder@local |
Human-readable key identifier |
--policy |
vbw/policy.json |
Path to policy file |
The signing key can also be provided via the SCQCS_VBW_ED25519_SK_B64 environment variable (preferred for CI).
What happens during build:
- Loads or auto-generates
policy.json - Snapshots the environment (OS, tools, container)
- Detects lockfiles (
package-lock.json,Cargo.lock,go.sum, etc.) - Records git commit, branch, dirty status
- Computes canonical source tree hash via
git ls-tree - Runs your build command, capturing the full transcript
- Hashes every artifact in the output directory
- Assembles the manifest referencing all component hashes
- Signs the manifest with the builder's Ed25519 key
- Writes everything to
vbw/
Verify a witness bundle's integrity and signatures.
scqcs vbw verify [--bundle <dir>]| Option | Default | Description |
|---|---|---|
--bundle |
vbw |
Path to the witness bundle directory |
Verification checks (strict, fail-closed):
- Validates bundle directory exists and is a real directory
- Checks all required files are present (manifest, environment, materials, outputs, transcript, policy, signature, hash)
- Rejects unexpected files in the bundle (strict bundle policy — extra files are an error)
- Checks for symlinks that escape the bundle directory
- Parses manifest, re-canonicalizes to canonical bytes (sorted keys, compact JSON)
- Recomputes manifest hash from canonical bytes and compares to
hashes/manifest.sha256 - Verifies Ed25519 signature against canonical manifest bytes
- Loads each component file, recomputes its SHA-256 hash, compares to manifest reference
- Verifies co-signatures against
trusted_cosigner_keysfrom the policy. Ifrequire_maintainer_cosign_for_releaseis true, at least one valid co-signature must be present. - Checks output artifacts exist and match
outputs.jsonhashes (with path traversal rejection) - Validates enforcement consistency (mode_requested matches policy mode)
- Validates policy compliance (dirty tree warning, mode mismatch, lockfile presence)
What verify does NOT check (TODOs):
- Source tree hash is not recomputed from the local git repo
- JSON files are not validated against the published schemas
Exit codes:
0— Verified (or verified with variance)1— Unverified (integrity failure, missing files, unexpected files, bad signature)
Three verdicts:
| Verdict | Meaning |
|---|---|
| VERIFIED | All hashes match, signature valid, policy satisfied |
| VERIFIED WITH VARIANCE | Signature and hashes OK, but warnings (e.g., dirty tree, missing lockfiles) |
| UNVERIFIED | Hash mismatch, bad signature, or missing files |
Add a maintainer co-signature to an existing bundle.
scqcs vbw attest [--bundle <dir>] [--keyfile <path>] [--key-id <id>]Use this when a second person (a maintainer, auditor, or release manager) independently reviews the bundle and wants to add their own signature.
scqcs vbw attest --bundle vbw --keyfile ~/.scqcs/maintainer.sk --key-id "maintainer@org"This writes a new file: vbw/signatures/maintainer_org.ed25519.sig
Note:
verifychecks co-signatures againsttrusted_cosigner_keyslisted in the policy. If the policy setsrequire_maintainer_cosign_for_release: true, at least one valid co-signature must be present. Co-signer public keys must be declared in the policy for verification to succeed.
VBW defines three levels of build reproducibility with active enforcement.
The strictest mode. Declares that identical inputs produce identical outputs, byte-for-byte.
- Intent: No network access, pinned toolchain,
SOURCE_DATE_EPOCHset - Enforcement: VBW attempts network namespace isolation via
unshare -rn(Linux user namespaces) and setsSOURCE_DATE_EPOCHif not already present. If network isolation succeeds andSOURCE_DATE_EPOCHis set, the manifest recordsmode_enforced=true. Ifunsharefails (e.g., user namespaces disabled), the manifest recordsmode_enforced=falsewith a diagnostic note.
A practical middle ground. Declares that network access is only used for fetching locked, hashed dependencies.
- Intent: Dependencies come from lockfiles with recorded hashes
- Enforcement: VBW snapshots all lockfile hashes (package-lock.json, Cargo.lock, etc.) before the build and compares them after the build completes. If any lockfile was modified during the build,
mode_enforced=falseis recorded. If lockfiles are unchanged,mode_enforced=true.
Provenance and integrity without a reproducibility guarantee.
- Full network access allowed
- Build may not be reproducible
- Still records what happened: who built it, what tools, what outputs
- Useful for complex builds that can't (yet) be made deterministic
mode_enforced=truebecause Mode C makes no reproducibility promises that need enforcement.
The included workflow at .github/workflows/vbw-build.yml automates VBW for every push to main.
On your local machine:
scqcs vbw keygen --output /tmp/vbw-keys
cat /tmp/vbw-keys/vbw-builder.sk- Go to your repo on GitHub
- Settings > Secrets and variables > Actions
- Click New repository secret
- Name:
VBW_BUILDER_SK_B64 - Value: paste the contents of
vbw-builder.sk
The workflow in this repo does the following:
# .github/workflows/vbw-build.yml
name: VBW Build (SCQCS site)
on:
push:
branches: ["main"]
jobs:
build_vbw:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for git tree hashing
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build scqcs CLI tool
run: cargo build --release
working-directory: tools/scqcs
- name: VBW bundle
env:
SCQCS_VBW_ED25519_SK_B64: ${{ secrets.VBW_BUILDER_SK_B64 }}
run: |
./tools/scqcs/target/release/scqcs vbw build \
--project scqcs-site \
--output-dir dist \
-- echo "Static site copied to dist/"
- uses: actions/upload-artifact@v4
with:
name: scqcs-site-dist-and-vbw
path: |
dist/**
vbw/**Note: This workflow will fail until you add the
VBW_BUILDER_SK_B64secret to GitHub. Without it, thescqcs vbw buildcommand will error with "No signing key found."
After each push, the witness bundle is uploaded as a build artifact alongside the site.
Download the artifact from the Actions tab, then:
scqcs vbw verify --bundle vbwVBW works with any build system. Here are common setups:
scqcs vbw build --output-dir dist -- npm run buildVBW auto-detects package-lock.json and records its hash.
scqcs vbw build --output-dir target/release -- cargo build --releaseVBW auto-detects Cargo.lock and records its hash.
scqcs vbw build --output-dir bin -- go build -o bin/myapp ./cmd/myappVBW auto-detects go.sum and records its hash.
If your site is already built (pure HTML/CSS/JS), use a copy step as the build command:
mkdir -p dist && cp -r *.html *.css *.js dist/
scqcs vbw build --output-dir dist -- echo "Static site copied"This is what the SCQCS site itself uses. The build command (echo) is trivial — VBW still captures the full environment and hashes all output artifacts.
The --output-dir flag tells VBW where to find artifacts to hash:
scqcs vbw build --output-dir build/release -- make releaseThe policy file controls what the build is expected to do. VBW auto-generates a default if none exists.
Note: Policy is checked at both build time (enforcement) and verify time (compliance). The build command enforces Mode A (network isolation) and Mode B (lockfile integrity). The verify command checks co-signatures against
trusted_cosigner_keysin the policy.
{
"policy_version": "1.0",
"requirements": {
"network": {
"allowed": true,
"allowlist": []
},
"reproducibility": {
"mode": "B_LOCKED_NETWORK",
"require_source_date_epoch": false
},
"materials": {
"require_lockfile_hashes": true,
"require_vendor_archive_and_tree": false
},
"signing": {
"require_maintainer_cosign_for_release": false
}
}
}For declaring maximum reproducibility intent:
{
"policy_version": "1.0",
"requirements": {
"network": {
"allowed": false,
"allowlist": []
},
"reproducibility": {
"mode": "A_DETERMINISTIC",
"require_source_date_epoch": true
},
"materials": {
"require_lockfile_hashes": true,
"require_vendor_archive_and_tree": true
},
"signing": {
"require_maintainer_cosign_for_release": true
}
}
}Note: Setting
"allowed": falserecords the intent but does not block network. Setting"require_vendor_archive_and_tree": truewill produce a verify warning since vendor tarball hashing is not yet implemented.
To use a custom policy, save it and pass it via --policy:
scqcs vbw build --policy strict-policy.json -- npm run buildVerification rebuilds the chain of trust from the inside out:
manifest.json
|
|-- hashes/manifest.sha256 Does the stored hash match the file?
|-- signatures/builder.ed25519 Does the signature match the public key?
|
|-- environment_hash Recompute hash of environment.json, compare
|-- materials_lock_hash Recompute hash of materials.lock.json, compare
|-- outputs_hash Recompute hash of outputs.json, compare
|-- policy_ref.hash_sha256 Recompute hash of policy.json, compare
|
outputs.json
|-- artifact[0].sha256 Does dist/index.html still match?
|-- artifact[1].sha256 Does dist/main.js still match?
...
If any hash doesn't match, the verdict is UNVERIFIED. This catches:
- Modified artifacts (someone changed a file after the build)
- Modified metadata (someone altered the environment or materials record)
- Forged signatures (someone tried to re-sign with a different key)
What verification does not catch:
- Tampering that occurred during the build (compromised build environment)
- A compromised signing key used to produce a valid-but-malicious bundle
- Builds that violated their declared reproducibility mode
tools/scqcs/
Cargo.toml # Rust project manifest
src/
main.rs # CLI entry point, keygen + attest commands
cli.rs # clap command definitions
hash.rs # SHA-256 hashing utilities
git.rs # Git state detection and tree hashing
sign.rs # Ed25519 key generation, signing, verification
vbw/
mod.rs # Module declarations
model.rs # Serde structs matching all JSON schemas
build.rs # Build workflow (13-step pipeline)
verify.rs # Verification workflow (8-step pipeline)
schemas/vbw/
manifest-1.0.schema.json # JSON Schema for manifest.json
environment-1.0.schema.json # JSON Schema for environment.json
outputs-1.0.schema.json # JSON Schema for outputs.json
policy-1.0.schema.json # JSON Schema for policy.json
materials-lock-1.0.schema.json # JSON Schema for materials.lock.json
.github/workflows/
vbw-build.yml # CI workflow for automated VBW bundles
All VBW files conform to JSON Schemas published in schemas/vbw/. These schemas can be used by editors for autocomplete and validation, or by external tools that consume VBW bundles.
| Schema | Validates |
|---|---|
manifest-1.0.schema.json |
vbw/manifest.json |
environment-1.0.schema.json |
vbw/environment.json |
outputs-1.0.schema.json |
vbw/outputs.json |
policy-1.0.schema.json |
vbw/policy.json |
materials-lock-1.0.schema.json |
vbw/materials.lock.json |
Note: The CLI does not validate bundle files against these schemas. The schemas are published for external tooling and documentation. Runtime schema validation is a TODO.
- The build artifacts match the hashes recorded at build time (SHA-256, streaming for large files)
- The manifest was signed by the holder of the declared Ed25519 key (signature covers canonical manifest bytes)
- The environment, dependencies, and policy files have not been modified since signing
- The git commit and dirty status were accurately recorded at build time
- The bundle has not been tampered with (strict verification rejects unexpected files, symlink escapes, path traversal)
- The enforcement field honestly records what was actually enforced vs. requested
- That the source code is free of vulnerabilities
- That the signing key hasn't been compromised
- That the build environment wasn't itself compromised
- That the build is reproducible (the mode is a declaration, not a proof)
- That dependencies were actually fetched from lockfile-specified sources
VBW is one layer in a defense-in-depth strategy. It answers "what happened during this build?" with cryptographic certainty, but it doesn't answer "should this build be trusted?" — that's a policy decision for humans.
| Method | When to Use |
|---|---|
--keyfile path/to/key.sk |
Local development |
SCQCS_VBW_ED25519_SK_B64 env var |
CI/CD pipelines |
The secret key is a 32-byte Ed25519 seed, base64-encoded. Never commit it to the repository. In CI, store it as a repository secret.
VBW automatically detects and hashes these lockfiles if they exist in the project root:
| Lockfile | Ecosystem |
|---|---|
package-lock.json |
npm |
yarn.lock |
Yarn |
pnpm-lock.yaml |
pnpm |
Cargo.lock |
Rust/Cargo |
go.sum |
Go |
Gemfile.lock |
Ruby/Bundler |
poetry.lock |
Python/Poetry |
composer.lock |
PHP/Composer |
Pipfile.lock |
Python/Pipenv |
VBW targets Unix/Linux environments and CI runners (GitHub Actions, Docker). Environment detection uses uname for OS information and which for tool paths. On non-Unix systems, OS fields will report "unknown" but the core signing and verification workflow functions correctly on any platform where Rust compiles.
For quick reference, every TODO mentioned in this document and in the code:
| TODO | Where | Priority |
|---|---|---|
Source tree hash re-verification during verify |
verify.rs |
Medium |
Vendor tarball hashing (archive_sha256, extracted_tree_hash) |
build.rs, model.rs |
Medium |
| Individual dependency artifact verification from lockfiles | build.rs |
Medium |
| Runtime JSON schema validation | verify.rs |
Low |
| Richer material kind values in schema (cargo, go, ruby) | build.rs, schema |
Low |
| Transparency log integration | Roadmap (VBW-2) | Future |
| Multi-builder consensus (N-of-M signatures) | Roadmap (VBW-2) | Future |
| OIDC identity binding | Roadmap (VBW-2) | Future |
| SBOM integration | Roadmap (VBW-2) | Future |
| Done | Where | Notes |
|---|---|---|
| Canonical JSON signing (RFC 8785-equivalent) | canonical.rs |
Signature covers sorted-key, compact JSON bytes |
| Strict fail-closed verify | verify.rs |
Missing files, extra files, symlink escapes all rejected |
| Enforcement honesty (mode_enforced flag) | model.rs, build.rs |
Manifest records what was actually enforced |
| Interleaved stdout/stderr transcript capture | build.rs |
Timestamped, threaded, arrival-order |
| Streaming SHA-256 for large files | hash.rs |
64 KiB buffered reads, constant memory |
| Path traversal rejection | verify.rs |
Rejects .. in artifact paths, absolute paths, escaping symlinks |