Transparency log
Updated
This page explains how every hushrun command checks hushrun's server instead of trusting it. The server keeps each repository's events in an append-only log, and signs what it says about that log with a key hushrun.yaml pins.
The log key in hushrun.yaml
Every write to a repository on hushrun is a leaf of an append-only Merkle log. The server signs these with the repository's log key:
- the log's checkpoints;
- each listing it answers;
- its answer about who counts.
hushrun.yaml names that key at its top level, so the server can't vouch for itself:
transparency_log:
origin: api.hushrun.com/l/acme/api
key: "ed25519:…"npx hushrun initwrites these lines when the server tells it the key. Otherwise,hushrun doctorprints them.originis the repository's URL on hushrun without its scheme, andkeyis the log's Ed25519 public key.- hushrun reads them as committed, so changing them is a reviewed change.
What every command checks
With the key pinned, every command that reads or writes the repository's events checks what the server answers:
- the receipt of each write;
- the signed statement of each listing;
- the signers answer, which says who counts;
- that every checkpoint it meets, the server's own and those recorded inside events, belongs to one history.
A check that fails ends the command with exit code 6. Until hushrun.yaml names the key, hushrun checks none of this, and warns on every command.
hushrun plan --start records the listings its decision read in its started event, for monitors to check. hushrun mark deployed refuses an attempt without that event, so every deploy has one.
Monitor the log
A listing that leaves an event out could hide a failure from a plan. hushrun repository monitor finds it afterwards:
hushrun repository monitor --state monitor.json- It replays the repository's log against the server's latest signed checkpoint, which must extend the one the last run kept in
--state. - It checks every checkpoint and listing plans recorded, every deletion against an org admin's signed statement, and every map the server committed to.
- It exits 5 when the server contradicts its own log, and prints the server's signed statements as proof. It exits 0 when everything agrees.
- It needs only read access, and the log key in
hushrun.yaml.
The monitor that counts is one the server's operator doesn't run. Run it from your own CI, every hour:
name: Monitor the hushrun repository
on:
schedule:
- cron: "17 * * * *"
workflow_dispatch:
concurrency:
group: hushrun-monitor
cancel-in-progress: false
jobs:
monitor:
runs-on: ubuntu-latest
# id-token: write lets hushrun ask for an ID token whose audience is the repository's URL.
permissions: { contents: read, id-token: write }
steps:
- uses: actions/checkout@v4 # hushrun.yaml names the log key
- uses: hushrun/hushrun/action/setup@main # pin to a release commit
# The newest hushrun release; `with: { version: 0.6.0 }` pins one.
# The newest checkpoint the last run verified: a server that rolls back between runs
# contradicts it.
- uses: actions/cache/restore@v4
with:
path: hushrun-monitor.json
key: hushrun-monitor-${{ github.run_id }}
restore-keys: hushrun-monitor-
- run: hushrun repository monitor --state hushrun-monitor.json --format json > monitor.json
- if: always()
run: jq '{ok, checkpoint: .checkpoint.size, checked, findings}' monitor.json
- uses: actions/cache/save@v4
if: success()
with:
path: hushrun-monitor.json
key: hushrun-monitor-${{ github.run_id }}
- uses: actions/upload-artifact@v4 # the findings and their proofs, if any
if: failure()
with: { name: hushrun-monitor, path: monitor.json }Who can read and write
A pipeline that could delete or overwrite an event could remove a failed result, and unblock a digest that never passed. Signatures can't prove an event is missing, so hushrun's server keeps events append-only:
| Who | Events | Delete |
|---|---|---|
| CI, with its ID token or a writer token | Append and read | Never |
| A member or an agent, from a registered machine | Append what that machine signed, and read | Never |
| Org admins and owners | Read | Only with hushrun repository gc |
- A token has a role:
readerreads,writeralso appends events, andadminalso deletes. hushrun creates each event only if it doesn't exist yet, and the roles keep a client that misbehaves from overwriting or deleting one. - A GitHub Actions job with
id-token: writegets the writer role for the repository its ID token names. Other CI uses an agent token and its machine key (GitLab CI). - An org admin makes repository tokens,
hrl_…, for monitors and scripts, on the repository's page in the dashboard. They go inHUSHRUN_REPOSITORY_TOKEN, or come from the command inHUSHRUN_REPOSITORY_TOKEN_COMMAND. - hushrun reads tokens only from the environment, never from the repository, and never logs them.
HUSHRUN_REPOSITORY_TOKENgoes only to a URL thatHUSHRUN_REPOSITORY_URLor--repository-urlnames. A URL that onlyhushrun.yamlnames gets a CI job's ID token. Outside CI, it gets the login you made for its origin, or an agent token when it's on hushrun's own host.
Delete old events
An org admin or owner deletes old events from their signed-in machine, first with --dry-run:
hushrun repository gc --older-than 180d --dry-runIt keeps the newest deployment of each scope, and the events of its digest. On a repository that keeps a log, it signs each deletion with the admin's machine. A deletion is then never the server's own doing, and monitors report any deletion without such a signature.
Next
- Who counts: whose signed events count.
- Commands: every flag of the monitor.