Verifying Commits¶
This guide covers how to verify Git commit signatures using Auths, including single-commit checks, full-history verification, identity bundle verification for CI, and integrating verification into CI pipelines.
How auths verify Works¶
The auths verify command is a unified entry point that detects whether you are verifying a Git commit or an attestation file. When given a Git ref, commit hash, or range, it performs commit verification. When given a file path or - for stdin, it performs attestation verification.
For commit verification, auths verify:
- Reads the SSH signature embedded in the Git commit object
- Looks up the signer's principal against the
--allowed-signersfile - Verifies the signature cryptographically using
ssh-keygen - Optionally verifies the attestation chain (when
--identity-bundleis provided) - Optionally verifies witness signatures (when
--witness-signaturesis provided)
Verifying a Single Commit¶
Verify the latest commit:
This defaults to verifying HEAD. You can specify any commit ref or hash:
The default --allowed-signers path is .auths/allowed_signers. To use a different file:
Output¶
On success:
On failure:
JSON Output¶
For machine-readable output, use the --json flag:
Returns a JSON object with fields: commit, valid, ssh_valid, chain_valid, signer, error, and warnings.
Verifying a Commit Range¶
Verify all commits in a range:
This resolves the range using git rev-list and verifies each commit individually. Output shows one line per commit:
Exit code 0 means all commits are valid. Exit code 1 means at least one commit is invalid or unsigned.
Verifying Full Repository History¶
To verify all commits from the initial commit to HEAD:
# Find the root commit
ROOT=$(git rev-list --max-parents=0 HEAD)
# Verify every commit
auths verify "${ROOT}..HEAD"
For large repositories, this may take time since each commit requires an ssh-keygen call.
Identity Bundle Verification (Stateless / CI)¶
For CI environments that do not have access to identity repositories, you can verify against an identity bundle. The bundle contains the identity's public key and attestation chain, enabling stateless verification.
When an identity bundle is provided:
- A temporary
allowed_signersfile is created from the bundle's public key - The SSH signature is verified against that key
- The attestation chain in the bundle is cryptographically verified
- Bundle freshness is checked (bundles have a
max_valid_for_secsTTL) - Attestation expiry warnings are emitted if any attestation expires within 30 days
Identity Bundle Format¶
An identity bundle is a JSON file with this structure:
{
"identity_did": "did:keri:E...",
"public_key_hex": "abcdef...",
"attestation_chain": [...],
"bundle_timestamp": "2026-01-01T00:00:00Z",
"max_valid_for_secs": 86400
}
Witness Verification¶
Witnesses provide additional assurance by countersigning attestations. To verify witness receipts alongside commit signatures:
auths verify HEAD \
--identity-bundle bundle.json \
--witness-signatures receipts.json \
--witnesses-required 2 \
--witness-keys "did:key:z6Mk...:abcd1234..."
The --witnesses-required specifies how many witness signatures must be valid. If the quorum is not met, verification fails.
CI Integration¶
GitHub Actions¶
Use the Auths verify action to block PRs with unsigned commits:
name: Verify Signatures
on: [pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history required for commit verification
- name: Install Auths
run: cargo install auths-cli
- name: Verify commit signatures
run: auths verify origin/main..HEAD --json --allowed-signers .auths/allowed_signers
The fetch-depth: 0 is required. Shallow clones do not contain the commit objects needed for signature extraction.
Using the Verify Action¶
- uses: auths-dev/auths-verify-action@v1
with:
allowed-signers: '.auths/allowed_signers'
fail-on-unsigned: 'true'
This action runs auths verify across the PR's commit range, writes a results table to the GitHub Step Summary, and fails the check if any commit is unsigned.
GitLab CI¶
verify-signatures:
stage: test
script:
- cargo install auths-cli
- auths verify origin/main..HEAD --allowed-signers .auths/allowed_signers
variables:
GIT_DEPTH: 0
Exit Codes¶
| Code | Meaning |
|---|---|
0 |
All commits verified successfully |
1 |
At least one commit is invalid or unsigned |
2 |
Runtime error (missing ssh-keygen, invalid args, etc.) |
Using the Audit Command¶
For compliance reporting, the auths audit command generates structured reports of signing status across a repository:
# Table format (default)
auths audit --repo . --since 2026-01-01
# JSON format for CI processing
auths audit --repo . --format json --require-all-signed --exit-code
# CSV for spreadsheet import
auths audit --repo . --format csv -o audit-report.csv
# HTML report
auths audit --repo . --format html -o audit-report.html
Audit options:
| Flag | Purpose |
|---|---|
--since |
Start date (YYYY-MM-DD or YYYY-QN for quarter) |
--until |
End date (YYYY-MM-DD) |
--author |
Filter by author email |
--signer |
Filter by signing identity/device DID |
-n / --count |
Maximum number of commits (default: 100) |
--require-all-signed |
Require all commits to be signed |
--exit-code |
Return exit code 1 if any unsigned commits found |
Verifying Attestation Files¶
The auths verify command also verifies attestation JSON files:
# Verify an attestation file
auths verify attestation.json --signer-key abcdef1234...
# Verify from stdin
cat attestation.json | auths verify - --signer did:keri:E...
Verification Library (auths-verifier)¶
For programmatic verification in your own tools, the auths-verifier crate provides the underlying verification functions. It is designed to be lightweight and embeddable, with support for FFI and WASM.
use auths_verifier::{verify_chain, VerificationStatus};
let report = verify_chain(&attestations, &root_public_key).await?;
match report.status {
VerificationStatus::Valid => println!("Chain verified"),
VerificationStatus::Expired { at } => println!("Expired at {}", at),
VerificationStatus::InvalidSignature { step } => {
println!("Bad signature at step {}", step);
}
VerificationStatus::Revoked { at } => println!("Revoked"),
VerificationStatus::BrokenChain { missing_link } => {
println!("Missing link: {}", missing_link);
}
VerificationStatus::InsufficientWitnesses { required, verified } => {
println!("Witnesses: {}/{}", verified, required);
}
}
Capability-Scoped Verification¶
Verify that a device has a specific capability:
use auths_verifier::{verify_with_capability, Capability};
let report = verify_with_capability(&chain, Capability::SignCommit)?;
Troubleshooting¶
"Allowed signers file not found"¶
The default path .auths/allowed_signers does not exist. Generate it:
"Signature from non-allowed signer"¶
The commit was signed with a key that is not in the allowed_signers file. This happens when a teammate signs commits but their key has not been added. See Team Workflows for how to manage shared allowed_signers files.
"No signature found"¶
The commit was never signed. Verify the author's git config:
git config user.signingKey # should be auths:<alias>
git config commit.gpgSign # should be true
git config gpg.ssh.program # should be auths-sign
"GPG signatures not supported"¶
The commit uses a GPG signature instead of SSH. Auths only supports SSH signatures. Reconfigure Git:
"Shallow clone detected" in CI¶
Commit verification requires full commit objects. Add fetch-depth: 0 to your checkout step:
Next Steps¶
- Signing Configuration -- set up Git signing
- Team Workflows -- shared registries and organization policies