Skip to content

Build Verification

Capsem’s release pipeline signs, notarizes, attests, and hash-verifies every artifact from source to installed binary.

graph LR
    A["Source<br/>(tagged commit)"] --> B["Build<br/>(per-arch)"]
    B --> C["Test<br/>(audit + coverage)"]
    C --> D["Codesign<br/>(Developer ID)"]
    D --> E["Notarize<br/>(Apple)"]
    E --> F["SBOM<br/>(SPDX 2.3)"]
    F --> G["Attest<br/>(SLSA + SBOM)"]
    G --> H["Sign manifest<br/>(minisign)"]
    H --> I["Publish<br/>(GitHub release)"]

Every step is automated in .github/workflows/release.yaml. A preflight job validates signing credentials before any build starts.

All host binaries are codesigned with a Developer ID certificate. The com.apple.security.virtualization entitlement is required for Apple Virtualization.framework.

BinaryPurposeEntitlement
capsemCLI clientcom.apple.security.virtualization
capsem-serviceBackground daemoncom.apple.security.virtualization
capsem-processPer-VM processcom.apple.security.virtualization
capsem-mcpMCP servercom.apple.security.virtualization
capsem-gatewayHTTP gatewaycom.apple.security.virtualization
capsem-traySystem traycom.apple.security.virtualization
Capsem.appTauri desktop appcom.apple.security.virtualization
ContextSigningCommand
DevelopmentAd-hoc (--sign -)just build (automatic)
ReleaseDeveloper ID certificatecodesign --sign "$APPLE_SIGNING_IDENTITY" --entitlements entitlements.plist --force

Ad-hoc signing is sufficient for local development. The justfile handles this automatically on macOS.

Release builds are submitted to Apple for notarization, which scans for malware and validates the signature:

xcrun notarytool submit Capsem-$VERSION.pkg \
--key $APPLE_API_KEY_PATH \
--key-id $APPLE_API_KEY \
--issuer $APPLE_API_ISSUER \
--wait --timeout 30m
xcrun stapler staple Capsem-$VERSION.pkg

Stapling embeds the notarization ticket in the artifact so macOS can verify it offline.

A Software Bill of Materials is generated for every release using cargo-sbom:

cargo sbom --output-format spdx_json_2_3 > capsem-sbom.spdx.json
FieldValue
FormatSPDX 2.3 JSON
ScopeAll Rust crate dependencies
Published ascapsem-sbom.spdx.json in GitHub release
AttestationSBOM attested against .pkg and .deb artifacts

This release SBOM currently describes the Rust host workspace. Profile-derived guest package/tool SBOMs are tracked separately in the profile-admin image verification sprint and must be produced from the signed Profile V2 package contract before they are treated as release evidence.

capsem-admin image sbom produces SPDX 2.3 guest-image SBOMs from the typed per-architecture image inventories. Those SBOMs carry the profile id, revision, and package-contract identity in the document name/namespace and use package-manager purl external references for apt, Python, and node packages.

Profile-derived image verification also accepts capsem-doctor --bundle archives as in-VM probe evidence. The admin verifier reads the bundled JUnit result without extracting the tar archive and fails the image verification report when the booted VM diagnostics have failures or errors.

The release-image boot gate uses the profile-backed E2E path: reconcile the selected profile assets, boot the host-arch image, run capsem-doctor --fast --bundle, then pass the generated doctor bundle and host-arch image-inventory.json through capsem-admin image verify. When artifact-gated tests run, the host-arch image inventory is required so this proof cannot silently downgrade to an asset-only boot.

Release artifacts receive SLSA build provenance attestation via actions/attest-build-provenance@v4:

ArtifactAttestation
.pkg (macOS installer)Build provenance
.deb (Linux package)Build provenance
rootfs.squashfs (arm64)Build provenance
rootfs.squashfs (x86_64)Build provenance
.pkg, .debSBOM (SPDX 2.3)

Attestations are published to the GitHub Attestations API and can be verified with gh attestation verify.

VM assets (kernel, initrd, rootfs) are verified via BLAKE3 hashes at every stage from build to boot.

graph TD
    A["Build<br/>generate_checksums()"] --> B["manifest.json<br/>(BLAKE3 hashes + sizes)"]
    B --> C["Release<br/>sign with minisign"]
    C --> D["Download<br/>capsem setup"]
    D --> E["Verify hashes<br/>BLAKE3 per-file check"]
    E --> F["Boot<br/>assets loaded from verified dir"]
{
"latest": "0.16.1",
"releases": {
"0.16.1": {
"assets": [
{
"filename": "vmlinuz",
"hash": "2c0bd752db929642...",
"size": 7797248
}
]
}
}
}
FieldTypeDescription
lateststringMost recent version
releasesmapVersion -> release entry
assets[].filenamestringAsset filename (validated: no path separators or ..)
assets[].hashstring64-character hex BLAKE3 hash
assets[].sizeintegerFile size in bytes

BLAKE3 hashes are computed in 256 KB chunks:

pub fn hash_file(path: &Path) -> Result<String> {
let mut hasher = blake3::Hasher::new();
loop {
let n = file.read(&mut buf)?;
if n == 0 { break; }
hasher.update(&buf[..n]);
}
Ok(hasher.finalize().to_hex().to_string())
}

Validation rules:

  • Hash must be exactly 64 hex characters
  • Filenames must not contain /, \, or .. (path traversal prevention)
  • Version strings must not contain .., /, or \
  • Empty releases are rejected

The manifest accumulates entries across releases. Each release merges its new version entry with the previous manifest from the latest GitHub release. This allows capsem setup to download assets for any supported version.

Release manifests are signed with minisign:

minisign -S -s /tmp/manifest-sign.key -m release-artifacts/manifest.json
ArtifactPurpose
manifest.jsonAsset hashes and version index
manifest.json.minisigminisign signature

Both files are published in every GitHub release.

ControlImplementation
Rust toolchainStable, pinned via dtolnay/rust-toolchain@stable
Dependency auditcargo audit in CI test stage
npm auditpnpm audit in CI test stage
Docker base imagesPinned in guest config Dockerfiles
Compiler warningsTreated as errors (#[deny(warnings)] in all crates)
Auditable buildscargo-auditable embeds dependency info in binaries
Build context validationcapsem.builder.doctor.check_source_files() verifies completeness before release
Rootfs binary verificationRelease pipeline checks all required guest binaries exist in rootfs before packaging

The release pipeline verifies these binaries exist in the rootfs before packaging:

BinaryPurpose
capsem-pty-agentPTY bridge and control channel
capsem-net-proxyHTTPS proxy bridge
capsem-mcp-serverGuest MCP relay
capsem-doctorIn-VM diagnostics
capsem-benchPerformance benchmarks
snapshotsSnapshot management