Skip to main content

Provenance transparency log

fremforge signs a provenance attestation for every artifact built on the platform. A signature proves the attestation came from us. It does not, by itself, prove that we have not since replaced it with a different one — the same key would sign the replacement just as convincingly.

The transparency log closes that. Every attestation is entered into an append-only Merkle tree published at log.frem.sh, and the log periodically signs a checkpoint: a statement of the tree’s size and root hash at a moment in time. Because the tree is append-only and each checkpoint must provably extend the last, we cannot remove or alter an attestation after the fact without producing a checkpoint that contradicts one already published.

What is in the log, and what is not

A leaf is a digest, not an attestation. Each entry is the SHA-256 of a DSSE envelope, and nothing else.

This is deliberate, and it is what lets the log be fully public. Holding your own envelope you can prove it is in the log — hash the bytes, fetch an inclusion proof, check it against a checkpoint. Anyone who does not hold your envelope learns only that a 32-byte value was logged at some index. Repository names, commit SHAs, artifact names and build times stay private.

Publishing the envelopes themselves would add nothing to your proof while making every build across every tenant permanently enumerable — in a structure that, by construction, can never delete anything.

What it proves today, and what it does not

Read this before relying on it.

A checkpoint carries fremforge’s signature and one witness cosignature.

Our signature makes the log tamper-evident: if you hold a proof and we later rewrite history, your proof stops verifying and you can demonstrate the contradiction.

The witness cosignature is what our own signature can never be — a third party that refuses to co-sign two inconsistent checkpoints. Since 16 September 2026 every checkpoint is cosigned by witness.stagemole.eu, operated by Mullvad VPN AB. It also carries a timestamp, which a C2SP checkpoint has no field for: without it, a stale but validly signed checkpoint is indistinguishable from a current one.

One witness is not a quorum, and we would rather say that than let the word “witness” do more work than it can. A single cosigner raises the cost of showing you a different tree from everybody else; it does not make it impossible, because it cannot detect collusion between the log and its only witness. The operator also describes this instance as staging rather than production.

Worth knowing if you are assessing independence: the only production-grade witnesses in the European Union — Glasklar Teknik, Mullvad VPN and Tillitis — share a parent company, Amagicom AB, which their own maintainers publish. Adding more of them would buy availability, not independence. We are not going to describe that set as independent parties.

The trust root publishes the current list at transparency_log.witnesses. fremforge attest-verify checks every entry and reports the count it verified.

A missing cosignature now fails verification.

Because the trust root declares a witness, attest-verify requires at least one cosignature to verify. If the witness is unreachable when we publish, the checkpoint goes out with our signature alone and your check fails — the witness check reports 0/1 verified.

That is the intended behaviour: a declared witness that is silently absent is exactly the case you want told about. But it means a failed run is not automatically evidence of tampering. Read which check failed before drawing a conclusion, and see Endpoints to fetch the checkpoint yourself.

Endpoints

PathContents
https://log.frem.sh/checkpointThe current signed checkpoint
https://log.frem.sh/tile/<level>/<index>Merkle tree tiles
https://log.frem.sh/tile/entries/<index>Entry bundles (the logged digests)

The formats are the C2SP community standards tlog-checkpoint, signed-note and tlog-tiles — not a fremforge invention, so existing third-party tooling reads them.

Verify that your attestation is logged

One command, and it needs no fremforge account:

fremforge attest-verify <asset>.intoto.jsonl --human
✓ envelope signatures        6/6 verify under fremforge-slsa-builder-v2
✓ checkpoint signature       signed by log.frem.sh
✓ tiles hash to the checkpoint root
✓ every attestation is in the log
                             6/6 at leaf indices: fremforge-linux-x64@255, …
✓ append-only since last run
✓ 7/7 checks passed (computed on this machine — the server was not asked
  for a verdict)

It exits non-zero if any check fails, so it drops into CI as-is.

A freshly released artefact may not be in the log for a few minutes. The log ingests on a fifteen-minute cycle, so an attestation can be signed and published before it appears in the tree. attest-verify will report NOT LOGGED and exit non-zero during that window — correctly, because it is not there yet. Re-run shortly. If the artefact is older than about fifteen minutes and still reports NOT LOGGED, that is a real failure and worth raising with us.

Every hash and signature is recomputed on your machine. We are asked only for bytes: the trust root, the checkpoint, the tiles. Nothing in the output is a verdict we handed you — which is the point, because a verification tool that asks the party being verified whether everything is fine has verified nothing.

A .intoto.jsonl is JSON Lines: one envelope per artefact, so a release bundle carries one per binary plus the SBOM. All of them are checked.

Keep the state file

attest-verify records the tree it saw in ~/.config/fremforge/tlog-state.json (override with --state=<path>) and, on the next run, checks the new tree still contains that one unchanged. This is the check that needs no third party, and it is the one that catches a split view aimed at you specifically — so run it more than once, from somewhere you keep, and do not delete the file to clear a failure. It deliberately does not advance the recorded tree when the check fails, so a detected rewrite stays detected on every subsequent run.

Or do it by hand

The command is a convenience, not a dependency. If you would rather not trust our binary either — a reasonable position for the one tool whose job is to distrust us — the walk below uses nothing but curl, jq and sha256sum, and the formats are community standards that third-party tlog-tiles clients also read.

You need your <asset>.intoto.jsonl envelope, which ships alongside the artifact.

# 1. The log's public key, from the trust root.
curl -sSfL https://www.frem.sh/.well-known/slsa-trust-root.json -o trust-root.json
jq -r '.transparency_log.vkey' trust-root.json

The vkey has the form log.frem.sh+<keyhash>+<base64 key>. Split it on the first two + only — the trailing field is base64 and contains + itself.

# 2. The digest of YOUR envelope. This is the leaf content.
#
# ONE LINE AT A TIME, AND INCLUDING ITS TRAILING NEWLINE — that is the unit
# the log commits to. Hashing the bundle as a whole finds nothing, which
# looks exactly like "your attestation was never logged".
head -c $(( $(head -1 <asset>.intoto.jsonl | wc -c) )) <asset>.intoto.jsonl | sha256sum
# 3. The current checkpoint, and the entries the log has published.
curl -sSfL https://log.frem.sh/checkpoint

A checkpoint is the origin, the tree size, the base64 root hash, a blank line, and one or more signature lines beginning with an em dash (). Your digest appears in one of the entry bundles under tile/entries/; its position is its leaf index, and the tiles under tile/<level>/ give the inclusion proof from that leaf to the root hash the checkpoint commits to.

Any tlog-tiles client can do this walk for you. If you would rather not run one, the useful property is simpler and still worth checking: your digest must appear, and the checkpoint’s tree size must only ever grow.

Verify that the log has not rewritten history

fremforge attest-verify does this for you across runs — see Keep the state file above. By hand:

Keep any checkpoint you have seen. Later, fetch a newer one and ask the log for a consistency proof between the two sizes. If the newer tree does not extend the older one, the proof cannot be produced — and a log that cannot produce it has rewritten history.

This is the check a witness performs continuously, which is why witnesses matter more than any single verification you do by hand.

Relationship to the other integrity mechanisms

fremforge runs three separate things that are easy to confuse:

MechanismCoversWhere
Audit chainAdmin actions on your orgPer-tenant hash chain, WORM-anchored
Keyless commit signingGit commitsSelf-hosted Fulcio + RFC3161 TSA, no Rekor
This logBuild-provenance attestationslog.frem.sh

They are independent. In particular, keyless commit signing still uses no transparency log — its integrity primitive is the RFC3161 timestamp, as documented in the security overview. This log covers provenance attestations only.

Sovereignty

The log runs on T Cloud Public in eu-de, signed with a fremforge-held Ed25519 key. It is not Rekor and does not depend on sigstore.dev, any US-operated log, or a public CDN outside the EU.

Witnesses are a deliberate exception in the making: the value of a witness is that it is not us, so a witness may well be operated elsewhere. A witness sees checkpoints only — an origin, a tree size and a root hash — never your repositories, your artefacts or your builds. Any witness we add will be listed in the trust root, so the set is auditable rather than assumed.