Rekor transparency log
Durable, tamper-evident proof of signing.
At 03:12 UTC someone briefly hijacks the identity your release workflow signs with, signs a backdoored image, and pushes it. That identity comes from OIDC, short for OpenID Connect: the login standard your build system uses to prove who it is without holding a password. The certificate behind it comes from Fulcio, Sigstore's certificate authority. Ten minutes later that certificate has expired and its private key is gone. By the ordinary rules of PKI (public key infrastructure, the machinery of certificates and keys that vouches for who signed what) there is nothing left to point at. The signature survives anyway. It sits in a public ledger nobody can edit or reorder, the way a clerk stamps a filing into a bound book. Your on-call can look it up, prove it happened, and read off the exact minute it was made. That same ledger answers a much duller question you will hit far more often: how do you verify a six-month-old release when the certificate that signed it lived for ten minutes, half a year ago? The ledger is Rekor, Sigstore's transparency log, and it is what makes keyless signing checkable years later and auditable by anyone who cares to look.
Proving a dead certificate was once alive
Here is the problem stated plainly. Fulcio hands out certificates that are valid for roughly ten minutes. That short life is the entire point of keyless signing: there is no durable private key sitting anywhere to be stolen, backed up, or rotated. It also breaks the obvious way of checking a signature. Six months on, the signing certificate is expired, and an expired certificate normally means reject. A transparency log settles this by witnessing the signature at the moment it is made. A transparency log is an append-only record: entries can be added, never changed or removed, and every addition is provable with hashes. It behaves like a postmark. The stamp on an envelope does not care whether the post office is open today, it records that the letter went through on that date. When Rekor accepts an entry it returns a Signed Entry Timestamp (SET), a countersignature made with Rekor's own key over the entry's contents and the time it was folded into the log. That SET is your trusted timestamp. Verification stops asking 'is this certificate valid right now?' and starts asking 'was it valid at the instant Rekor witnessed the signature?' The second question still has an answer in ten years.
$ rekor-cli search --email [email protected]Found matching entries (listed by UUID):24296fb24b8ad77a3f6c1b0e9d2a5c8f4e7b1a06d3c9f52e8b7a41d0c6e35f9a2b8d4c1e70f63a95b24296fb24b8ad77a9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1# rekor-cli talks to the public-good log at rekor.sigstore.dev by default.# Each UUID = <16-hex tree/shard id><64-hex entry hash>, one line per signature.
Notice what you searched by. Not the artifact, the identity that signed it. Searching by email address (or by --sha for a digest) hands back every entry ever recorded under that identity. That list is the raw material for two different jobs. Audit: show me everything my release identity has ever signed. Detection: is there anything on this list I cannot account for? Each UUID (universally unique identifier, the address of one entry) that comes back glues a shard and tree id onto the front of the entry's own hash. That combination is how you point at a single leaf of the log in the next step.
Reading an entry: digest, signature, and the Fulcio cert
$ rekor-cli get --uuid 24296fb24b8ad77a3f6c1b0e9d2a5c8f4e7b1a06d3c9f52e8b7a41d0c6e35f9a2b8d4c1e70f63a95bLogID: c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801dIndex: 148216387IntegratedTime: 2026-05-14T09:41:22ZUUID: 24296fb24b8ad77a3f6c1b0e9d2a5c8f...Body: {"HashedRekordObj": {"data": {"hash": { "algorithm": "sha256", "value": "9f2c8b0e...c1a4" } # the image digest},"signature": {"content": "MEUCIQDb3f...gk=", # the raw signature"publicKey": {"content": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t..." # base64 Fulcio cert}}}}
A cosign image signature is stored as a hashedrekord, and three pieces of it matter. The artifact's SHA-256 digest (Secure Hash Algorithm, 256-bit: a fingerprint of the exact bytes) sits under data.hash. The raw signature sits under signature.content. The signer's Fulcio certificate, base64-encoded, sits under signature.publicKey.content. That certificate is the thread leading back to an identity. Decode the base64 and you get a PEM (Privacy-Enhanced Mail, the BEGIN CERTIFICATE text format certificates travel in) whose Subject Alternative Name field carries the workflow identity, usually a URI (uniform resource identifier, a web-style address) pointing at the release workflow's ref. Sigstore's OIDC-issuer extension, object identifier 1.3.6.1.4.1.57264.1.8, records which login provider authenticated it. One entry is therefore self-contained evidence: this digest, signed by this identity, witnessed at this time. None of that depends on the certificate still being valid today.
Signatures are not the only thing Rekor keeps. When cosign attaches an in-toto attestation, meaning a signed statement about an artifact such as build provenance, a test result, or the SBOM (software bill of materials) you will generate with Syft in the next lesson, it is recorded under the dsse entry type instead. DSSE stands for Dead Simple Signing Envelope, the wrapper Sigstore uses to carry signed statements. The envelope's payload hash and the signer's certificate land in the log the same way a signature does. Your evidence gets the same permanence and the same public audit trail your signatures get.
How you check an inclusion proof without trusting Rekor
The append-only promise is not something you take on faith. It is arithmetic you can redo yourself. Rekor is a Merkle tree, named after Ralph Merkle: every entry is a leaf, and pairs of hashes get hashed together over and over until one value at the top stands for the whole log. The leaf hash is SHA-256(0x00 ‖ entry_bytes). Every internal node is SHA-256(0x01 ‖ left ‖ right). Those leading bytes come from RFC 6962, the Certificate Transparency standard Rekor borrows its hashing rules from, and they keep a leaf hash from ever being mistaken for a node hash. Fold the whole thing up and you get a single root hash that commits to every entry in the log. Change one byte anywhere and the root changes. An inclusion proof hands you the audit path: the short list of sibling hashes you need to rebuild that root starting from your own leaf, and nothing more. You hash your entry into a leaf, fold in each sibling from the proof on the correct side (left or right is fixed by your leaf's index and the current tree size, so there is no guessing), and climb until you are holding a candidate root.
$ curl -s https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77a3f6c1b0e9d2a5c8f4e7b1a06d3c9f52e8b7a41d0c6e35f9a2b8d4c1e70f63a95b \| jq '.[].verification.inclusionProof' # the entries endpoint returns a { uuid: entry } map, so jq '.[]' picks the one entry{"checkpoint": "rekor.sigstore.dev - 1193050959916656506\n148902551\nb0Xq9m4...root...=\n\n— rekor.sigstore.dev wNI9ajBEAiAe...sig...==\n","hashes": ["5e6f8a1c...", # sibling at level 0 ┐"7a8b0c2d...", # sibling at level 1 ├─ audit path"9c0d2e4f...", # sibling at level 2 │"1f3a5b7d..." # sibling at level 3 ┘],"logIndex": 148216387,"rootHash": "a1b2c3d4...e5f6","treeSize": 148902551}# rekor-cli's own --format json prints a getCmdOutput without the proof;# use the API above (or `rekor-cli get --format tle`) to get inclusionProof + SET.
Now hold that candidate root up against the checkpoint. A checkpoint is Rekor's Signed Tree Head: a short signed note saying that at this moment the tree held this many entries and its root hash was this, signed with Rekor's log key. Two things have to be true at once. Your recomputed root has to equal the checkpoint's rootHash, and the checkpoint's signature has to verify against Rekor's public key. That public key does not come from a live call to Rekor's API (application programming interface, the web endpoints the log answers on). cosign takes it from the Sigstore TUF root (The Update Framework, a signed bundle of trust anchors that cosign ships with and keeps refreshed), so trust is anchored in TUF rather than in whatever a host happens to reply with. Recomputing the root yourself is the whole exercise. You never accept Rekor's 'yes, it is in there,' you check it against a key you already hold. Consistency proofs are a separate trick with a separate job: they show that a tree of N entries is a straight append-only extension of an earlier tree of M entries, which is how a monitor confirms the log never rewrote or forked its own history.
Rekor as a tripwire, if somebody is watching it
Because every keyless signature is public and bound to an identity, the log doubles as a place to catch an attacker. An entry signed by your release identity that your pipeline never produced is a loud alarm. The likeliest explanation is that your OIDC identity or your build got abused, which is the 03:12 scenario from the top of this lesson. Rekor detects. It does not prevent. And nobody audits a log by staring at it, so rekor-monitor does the watching for you, with two jobs. Consistency monitoring keeps verifying consistency proofs on a schedule, so a log that quietly forked or rewrote itself gets caught. Identity monitoring raises an alert whenever a new entry matches certificate identities you named up front. In production, keep the verification itself offline and cheap: cosign 2.x bundles the SET and the inclusion proof alongside the artifact, so you verify from the bundle instead of hammering the rate-limited public log on every deploy. Then run a monitor, so the tripwire is actually armed.
# config.yaml — alert on any log entry matching our signing identities.# certSubject and issuers are regular expressions.monitoredValues:certIdentities:- certSubject: [email protected]issuers:- https://token.actions.githubusercontent.com- certSubject: https://github.com/acme/api/.github/workflows/release.yml@refs/heads/mainissuers:- https://token.actions.githubusercontent.com$ rekor_monitor --config-file config.yaml --oncelevel=info msg="verified consistency proof: 148890003 -> 148902551 (append-only OK)"level=warn msg="identity match" [email protected] \uuid=24296fb24b8ad77a9f... index=148902488 integratedTime=2026-05-14T03:12:07Z# 03:12 UTC entry your pipeline has no record of producing — investigate now.
Try this
Work through “Rekor as a tripwire, if somebody is watching it” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.
Takeaway
The trap worth remembering here: switching off tlog verification silently throws the guarantee away. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.