A verifiable trust record for code changes.
brew install corvidlabs/tap/attestattest records signed attestations (who or what reviewed a change, and at what confidence)
keyed to git commit SHAs and stored in git notes, so the trail travels with your repository
across every git host.
Built for the world where agents write most of the code. augur answers how risky is this
diff, and should a human look?, but that verdict is ephemeral. attest makes it durable: a
portable, optionally-signed record of what vetted a change, and a policy CI and agents can
gate on. augur scores the risk; attest records the trust.
Install (macOS). The fastest path is Homebrew (macOS binary only):
brew install corvidlabs/tap/attestPrefer to build from source? You need Swift 6 and git on PATH:
swift build -c release && install -m 0755 .build/release/attest /usr/local/bin/attestTry it instantly (no setup). This builds the binary, records an attestation against a
throwaway /tmp repo, and reads it back. It touches nothing of yours:
bash examples/01-basic-attestation.shMore runnable, self-contained examples (each against a throwaway repo) are catalogued in
examples/README.md.
The core flow. Generate a key once, sign an attestation on HEAD, read the ledger,
then gate on a policy (output below is real):
$ attest keygen
attest Β· wrote private key to ~/.config/attest/key (0600)
public key: SP19xVbn1MrVF3Ips/aQDR1sHAjHGb9iVzG9ePtluA0=
$ attest sign --commit HEAD --reviewer human:you --confidence 0.9 --tests-passed --sign
attest Β· recorded human:you on 77fe5ac11c (signed)
$ attest log --commit HEAD
attest Β· ledger
commit 77fe5ac11c (1 attestation)
[Β·] human:you verdict:- conf:90% tests:ok human:- signed[ok]Verify against a policy. A floor it clears PASSES (exit 0); a stricter floor FAILS (exit 1), the non-zero exit a CI job or agent loop gates on:
$ echo '{ "requireTestsPassed": true }' > lax.json
$ attest verify --commit HEAD --policy lax.json
attest verify Β· [ok] PASS (1 commit checked) # exit 0
$ echo '{ "minimumConfidence": 0.95 }' > strict.json
$ attest verify --commit HEAD --policy strict.json
attest verify Β· [x] FAIL (1 commit checked)
violations:
x 77fe5ac11c minimumConfidence: highest confidence 0.9 is below floor 0.95
# exit 1Where next:
- Documentation site: the rendered docs.
docs/cli.md: every command and flag (sign,verify,log,export,keygen).docs/policy.md: every policy rule with JSON examples.docs/signing.md: keys, Ed25519, signing, and preventing reviewer spoofing.examples/README.md: the full catalog of live examples.
Agents made code cheap to produce; the scarce resource is now trust. When an agent lands
a change, there is no native, portable record of which agent or human vetted it, and that
context is lost the moment the PR merges. attest is that missing primitive:
- Humans get an auditable trail: who signed off on what, and how sure they were.
- Agents get a gate:
attest verifyexits non-zero when a commit lacks the trust a policy demands, so an agent escalates instead of merging blind.
- Records are
Attestations: a commit SHA, a reviewer (agent:claude,human:leif), a confidence (0...1), an optional verdict (proceed/review/block), tests-passed and human-approved flags, a timestamp, and an optional note. - Storage is git notes under
refs/notes/attest. No service, no database. Multiple attestations can accrue on one commit. Sync them withgit push origin "refs/notes/*". - Signing is optional. Out of the box, attestations are unsigned but valid. Run
attest keygenonce and pass--signto attach an Ed25519 signature over a deterministic canonical serialization (sorted keys, signature field excluded) that anyone can verify. - Policy is plain JSON in
.attest.json. No extra config language.
The recommended install on macOS is Homebrew, which pulls a prebuilt universal binary:
brew install corvidlabs/tap/attestFrom source (Swift 6 and git on PATH):
swift build -c release
install -m 0755 .build/release/attest /usr/local/bin/attest
# or, with fledge:
fledge run installOther options:
# Mint:
mint install CorvidLabs/attest
# SwiftPM experimental install (drops attest into ~/.swiftpm/bin):
swift package experimental-installSigning uses swift-crypto.
# Record an attestation for the current commit (unsigned, zero setup):
attest sign --commit HEAD --reviewer agent:claude --confidence 0.92 --tests-passed
# Pipe augur straight in; verdict and confidence are auto-filled from its JSON:
augur check --json | attest sign --commit HEAD --reviewer agent:claude --from-augur -
# Optional: generate a key once, then sign records cryptographically:
attest keygen
attest sign --commit HEAD --reviewer human:leif --confidence 0.7 --human-approved --sign
# Read the ledger:
attest log # all attested commits
attest log --commit HEAD --json # one commit, machine-readable
attest log --range main..HEAD
# Gate in CI / an agent loop (exits non-zero on any violation):
attest verify --range origin/main..HEAD --policy .attest.json
# Export the whole range as one stable JSON audit document (for archival):
attest export --range origin/main..HEAD --policy .attest.jsonattest sign --from-augur <file|-> reads augur check --json and merges it: augur's
verdict is copied, and its riskScore (0...100) becomes confidence = 1 - riskScore/100.
Explicit --verdict / --confidence flags override the augur-derived values.
augur check --range main..HEAD --json \
| attest sign --commit HEAD --reviewer agent:claude --from-augur - --tests-passedAll rules are optional with permissive defaults. An empty {} still requires one
attestation per commit and passes any commit that has one.
Trust-model warning, read first. A gate is only as strong as the rule that ties a record to a key:
requireSignature: truealone does NOT bind identity. It proves only that some key signed, not whose. An attacker can sign with their own key while claimingreviewer: human:leifand pass. To actually stop reviewer spoofing, usesignerPinning(binds a reviewer to a key) ortrustedKeys+requireSignaturetogether.trustedKeysalone does not force signing;requireSignaturealone does not check the key.minimumConfidenceandrequireTestsPassedare satisfied by any attestation on the commit, including an injected one. Combine them withallowedReviewersorsignerPinningto bind that evidence to reviewers you trust.See
docs/policy.mdfor the full trust model.
{
"requireAttestation": true,
"requireTestsPassed": true,
"requireHumanApprovalWhenVerdictAtLeast": "review",
"requireSignature": false,
"minimumConfidence": 0.6,
"allowedReviewers": ["human:", "agent:claude"],
"requireSignatureWhenVerdictAtLeast": "block",
"requireTestsPassedWhenVerdictAtLeast": "review",
"trustedKeys": ["BASE64_PUBKEY_A", "BASE64_PUBKEY_B"],
"signerPinning": { "human:leif": "BASE64_LEIF_PUBKEY" },
"maxAgeDays": 90
}| Rule | Fails a commit when⦠|
|---|---|
requireAttestation |
the commit has no attestations. |
requireTestsPassed |
no attestation reports testsPassed. |
requireHumanApprovalWhenVerdictAtLeast |
some attestation's verdict is at/above the level but no attestation on the commit is humanApproved. The human sign-off can be a separate attestation (e.g. attest sign --reviewer human:leif --human-approved); it need not restate the verdict. |
requireSignature |
no valid signed attestation exists. |
minimumConfidence |
the highest recorded confidence is below the floor. |
allowedReviewers |
any attestation on the commit has a reviewer outside the allow-list. Matching per pattern: an exact match against the full reviewer string, or (when the pattern ends with :, e.g. "human:") a prefix match, so "human:" allows any human:* reviewer while "agent:claude" matches only exactly. A nil/empty list disables the rule. |
requireSignatureWhenVerdictAtLeast |
some attestation's verdict is at/above the level but no attestation on the commit is validly signed. The signature can be a separate attestation. Not triggered when every verdict is below the level. |
requireTestsPassedWhenVerdictAtLeast |
some attestation's verdict is at/above the level but no attestation on the commit reports testsPassed. The passing-tests record can be a separate attestation. Not triggered when every verdict is below the level. |
trustedKeys |
any signed attestation on the commit fails to verify, or carries a publicKey that is not in this list of trusted base64 Ed25519 keys. Unsigned attestations are unaffected: trustedKeys constrains which keys count as trusted, it does not force signing (use requireSignature for that). A nil/empty list disables the rule. |
signerPinning |
an attestation whose reviewer is a key in this {reviewer: base64 pubkey} map is unsigned, or signed with a different key (or tampered). Reviewers absent from the map are unaffected. This binds identity to a key, and it is what stops a spoofed reviewer: human:leif that allowedReviewers (a string-only gate) cannot. A nil/empty map disables the rule. |
maxAgeDays |
the commit's newest attestation is older than this many whole days (or the commit has no attestations at all). A single fresh record clears the commit even alongside older ones; age is measured against an injected reference time, so a stale block from months ago can no longer rubber-stamp today's commit. A nil value disables the rule. |
allowedReviewers gates the reviewer string, but nothing stops anyone from filing an
attestation that simply claims reviewer: human:leif. To bind an identity to a
cryptographic key, use signerPinning (pin specific reviewers to specific keys) and/or
trustedKeys (restrict which keys count as trusted at all):
{
"requireSignature": true,
"trustedKeys": ["BASE64_LEIF_PUBKEY", "BASE64_CI_PUBKEY"],
"signerPinning": { "human:leif": "BASE64_LEIF_PUBKEY" }
}# leif generates a key once; keygen prints the PUBLIC key to copy into the policy:
attest keygen
# attest Β· wrote private key to ~/.config/attest/key (0600)
# public key: BASE64_LEIF_PUBKEY <- copy this into signerPinning / trustedKeys
# a genuine, signed sign-off as human:leif PASSES:
attest sign --commit HEAD --reviewer human:leif --confidence 0.95 \
--verdict review --human-approved --sign
attest verify --commit HEAD # exit 0
# a spoof (claiming human:leif unsigned, or signed with someone else's key) FAILS:
attest sign --commit HEAD --reviewer human:leif --confidence 0.95 # unsigned claim
attest verify --commit HEAD # exit 1: reviewer human:leif is pinned but unsignedThe two rules compose:
signerPinningis per-reviewer: only reviewers in the map are constrained, and each must be signed with its exact pinned key. This is the rule that stopshuman:leifspoofing.trustedKeysis global: it does not force signing (an unsigned record passes it, so pair it withrequireSignature: trueto require a signature), but any record that is signed must verify and use a key from the trusted set. It bounds the universe of acceptable signers.
The full pinned lifecycle (correct-key PASS, then wrong-key/unsigned FAIL) is demonstrated
end-to-end in examples/07-signer-pinning.sh.
Trust decays. A block/review verdict, or any sign-off, from months ago should not silently
keep clearing today's commit. maxAgeDays makes the commit's newest attestation prove
recency: the commit passes only when some attestation is within maxAgeDays whole days of the
verification time (a single fresh record clears it, even alongside older ones), and a commit with
no attestations at all fails too.
{ "maxAgeDays": 30 }The reference "now" is injected into the engine (defaulted to the current epoch at the CLI),
not read from the system clock inside the verifier, so verification is deterministic and testable.
A fresh-PASS β stale-FAIL lifecycle is demonstrated end-to-end in
examples/08-freshness.sh. See docs/policy.md for
the exact semantics.
A default .attest.json ships at the repo root. It is intentionally permissive
(it gates nothing yet) so it demonstrates the schema without breaking a repo that
has no attestations:
{
"requireAttestation": false,
"requireTestsPassed": false,
"requireSignature": false,
"requireHumanApprovalWhenVerdictAtLeast": "block"
}Tighten the rules as a repo starts recording attestations.
Run the binary directly:
- run: attest verify --range origin/main..HEAD --policy .attest.jsonOr drop in the GitHub Action (CorvidLabs/attest) from any repo. It
installs a prebuilt attest for the runner (macOS universal or Linux x86_64)
from the matching release, then runs attest verify against your checkout β no
Swift toolchain required. On other platforms it falls back to building attest
from its own source (which needs Swift on the runner):
jobs:
trust:
runs-on: ubuntu-latest # or macos-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # range needs history
- uses: CorvidLabs/attest@v0 # pin to the major tag, not @main
with:
range: origin/main..HEAD # default
policy: .attest.json # default
working-directory: . # defaultPin to the moving @v0 tag to track the latest 0.x release, or to an exact tag
(e.g. @v0.3.0) to lock a specific version.
| Input | Default | Description |
|---|---|---|
range |
origin/main..HEAD |
Git range to verify (needs full history). |
policy |
.attest.json |
Path to the policy file (relative to working-directory). It must exist: a missing or misspelled policy fails the gate rather than silently passing under the permissive default. |
working-directory |
. |
Directory to run attest verify in. |
version |
(action ref) | attest release to install (v0.3.0 or latest); defaults to the pinned tag, else latest. |
| Output | Description |
|---|---|
binary |
Absolute path to the attest binary used. |
The gate's contract is the exit code: a policy violation propagates
attest's non-zero exit and fails the job.
Platform. Prebuilt binaries cover GitHub-hosted macOS and Linux x86_64 runners. Other runners (e.g.
windows-latest, Linux arm64) need a Swift toolchain so the action can build from source. Windows is out of scope.
augur check --range main..HEAD --json | attest sign --commit HEAD --reviewer agent:claude --from-augur -
attest verify --commit HEAD || echo "trust policy not satisfied, escalating to a human"attest log --commit HEAD --json:
[
{
"attestations": [
{
"commit": "9f2c1a7b04...",
"confidence": 0.92,
"humanApproved": false,
"publicKey": "base64...",
"reviewer": "agent:claude",
"signature": "base64...",
"testsPassed": true,
"timestamp": 1700000000,
"verdict": "proceed"
}
],
"commit": "9f2c1a7b04..."
}
]attest verify --json:
{ "checkedCommits": 1, "passed": false, "violations": [ { "commit": "β¦", "detail": "β¦", "rule": "requireTestsPassed" } ] }attest log is a human/diagnostic listing. attest export is its archival
counterpart: a single, stable JSON document covering the complete
provenance trail across a commit range: every commit, every attestation, each
record's cryptographic verification status, and (with --policy) a
per-commit pass/fail. Output is deterministic (sorted keys; commits oldest-first,
the order git rev-list --reverse returns), so it diffs cleanly and is suitable
for compliance archival.
attest export --range origin/main..HEAD # whole range
attest export --commit HEAD # one commit
attest export --range main..HEAD --policy .attest.json # with per-commit verdicts
attest export --range main..HEAD --no-pretty > audit.json # compact, for storageIt is always JSON (no --json flag); pass --no-pretty for compact output.
Each record's verification says whether it is signed and, for signed
records, whether the embedded signature verified against its embedded public
key (a tampered or wrong-key record reports verified: false); unsigned records
omit verified.
{
"allPassed": true,
"commitCount": 1,
"commits": [
{
"commit": "9f2c1a7b04...",
"policyPassed": true,
"records": [
{
"attestation": {
"commit": "9f2c1a7b04...",
"confidence": 0.92,
"humanApproved": false,
"publicKey": "base64...",
"reviewer": "agent:claude",
"signature": "base64...",
"testsPassed": true,
"timestamp": 1700000000,
"verdict": "proceed"
},
"verification": { "signed": true, "verified": true }
}
]
}
],
"policyApplied": true,
"recordCount": 1,
"version": 1
}How it complements log/verify. log is for a human reading the ledger;
verify is an exit-code gate for CI / agent loops; export is the durable
record an auditor keeps. It folds in verify's policy verdict and the
per-record signature checks log only badges, across the full range, in one
machine-stable file. A natural CI / pre-commit archival step:
# archive the trust trail for this PR alongside the build artifacts
attest export --range origin/main..HEAD --policy .attest.json --no-pretty > audit.jsonThe full mixed (signed/unsigned, human/agent) lifecycle is demonstrated
end-to-end in examples/05-audit-export.sh.
In-depth docs live in docs/:
docs/architecture.md: theAttestKitvs CLI split, canonical serialization, git-notes storage, the signing model, and the verify / export flow.docs/policy.md: every policy rule (all 11, includingmaxAgeDays) with JSON examples, theWhenVerdictAtLeastsemantics, and signer pinning.docs/cli.md: every command and flag (sign,verify,log,export,keygen) with examples and exit codes.docs/signing.md:keygen, Ed25519, optional signing,trustedKeys/signerPinning, and preventing reviewer spoofing.docs/ci-integration.md: macOS and Linux CI, theattest-verifyaction, the augur β attest trust pipeline, and audit export for compliance.docs/dogfooding.md: proof: attest attests attest. Real captured output of attest recording + verifying provenance on its own commits (a PASS and a FAIL), plus the growing CI provenance ledger. Reproduce withexamples/dogfood.sh.
attest uses itself: every commit on main gets an agent:ci attestation recorded by attest,
on attest's own history, gated against β.attest.json in CI, with the ledger
pushed to refs/notes/attest so it accumulates over time. Run examples/dogfood.sh
to see attest attest its real HEAD and both pass a lax policy and fail a strict one. Full
captured proof and the CI wiring live in docs/dogfooding.md.
fledge run check # build + test + spec check
fledge run test
fledge run spec # spec-sync alignment
fledge run examples # run the example scripts against scratch reposThe engine (AttestKit) is fully testable without git via the AttestationStore
protocol and an InMemoryStore fake. It depends only on Apple's swift-crypto; the CLI
uses swift-argument-parser.
augur is the upstream risk scorer:
augur check emits proceed | review | block with a risk score. That verdict is
ephemeral. attest makes it durable: it records that verdict (and anything
else a reviewer asserts) as a portable, optionally-signed artifact, then gates on
it. augur scores the risk; attest records the trust. They compose over a
pipe; attest never links augur.
augur check --json | attest sign --from-augur ---from-augur copies augur's verdict and maps its riskScore (0...100) to
confidence = 1 - riskScore/100. A worked, verified run (a risk-45 review diff
becomes a 0.55-confidence attestation):
$ echo '{"verdict":"review","riskScore":45.0}' \
| attest sign --commit HEAD --reviewer agent:claude --from-augur - --tests-passed
attest Β· recorded agent:claude on 9f2c1a7b04
$ attest log --commit HEAD
attest Β· ledger
commit 9f2c1a7b04 (1 attestation)
[!] agent:claude verdict:review conf:55% tests:ok human:- unsigned
The full signed lifecycle (keygen β --sign β signed[ok] β verify against
{"requireSignature": true}) is demonstrated end-to-end in
examples/04-signed-lifecycle.sh.
- macOS and Linux. CI runs on both platforms (
build-test-macosandbuild-test-linux). The Homebrew prebuilt binary and the compositeaction.ymlcurrently target macOS. Windows is out of scope. - The composite action (
action.yml) buildsattestfrom its own checkout. Cross-repo packaging (shipping a prebuilt binary and installing it into other repos without a Swift toolchain) is a deferred later step. attestuses a single local Ed25519 key and embeds the public key on each record. It is not a CA: key distribution / web-of-trust and signer pinning are roadmap items.
MIT Β© CorvidLabs
