Check against your doze
Checking against doze
How doze decides to run something
doze up downloads programs and runs them on the machine that holds your source code and your credentials. So every byte is checked against a key doze already carries before it runs, and once a project is pinned, nothing on the network can change what it runs.
Six steps from source to a running Postgres
Every arrow assumes the step before it might be hostile. The example is the postgres module, release 0.2.4, on an Apple silicon Mac.
The engine is built
Postgres, Kvrocks and Valkey for macOS are built from upstream source in public CI; engines upstream already ships are re-hosted. Published once, never rebuilt.
The module is built
CI cross-compiles the plugin for three platforms with the SDK's release tool and uploads the archives and an unsigned index.yaml to a GitHub release. doze AWS and doze Kafka ship their modules the same way from their own repos.
The registry signs it
The one place the private key lives (a CI secret). It signs each archive's sha256 with ed25519, then the whole index: module, releases, engine majors, protocol, channels. validate re-checks every signature before deploy.
The network. registry.doze-dev.com serves the signed index; GitHub releases serve the archives. Neither is trusted: they can be slow or down, and nothing they say is believed without a signature.
untrusted by designCheck the index
doze fetches the index and checks its signature against the key compiled into doze. Only then does it pick a release: newest that speaks its plugin protocol, supports every Postgres major you declared, and runs on this doze.
Check the archive
The download's sha256 must equal the one in the signed index, and the archive's own signature over that sha256 must verify. Nothing is unpacked until both pass.
Pin it, then run offline
The lock records the module release, protocol, engine majors and every platform's sha256, plus the engine's exact version and hashes. From then on a cached project resolves with no network, not even the index. Only doze modules upgrade moves a pin.
modules: doze/postgres: version: 0.2.4 # the module release, chosen for you protocol: 1 # doze plugin protocol it speaks engines: - "14" - "15" - "16" - "17" - "18" hashes: # every platform, from the signed index aarch64-apple-darwin: sha256:a07134d07cf988862f5b2d1a288cc43a40ace5b96e88e190b5d66ca8b207b89a aarch64-unknown-linux-gnu: sha256:f29c48e5dab175d6f1b2991f274e2e5aedb55a9993eff90128feb423380fd2a2 x86_64-unknown-linux-gnu: sha256:7c920599a5a52f4ed4306963797e88de7ed6beb0be130b8b1039f7c7d0354f68
The registry's key itself is not in the lock: for the doze namespace it is compiled into doze, so there is nothing to pin and no key is ever trusted on first use. Every module is signed with that one key: modules arrive as pull requests to doze-modules.
What each check stops
Signatures prove who published, not that the code is harmless. These are the attacks the chain is built for, and what is left over.
| If someone… | What stops it | What is left |
|---|---|---|
| Changes an archive on GitHub | The archive's signature covers its sha256, and doze.lock pins the same hash. Changed bytes fail before anything is unpacked. | Nothing runs. The host can only make the download fail. |
| Swaps or edits an index | The index signature covers every release, engine major, protocol and channel. Claiming Postgres 19 support, adding a release, or editing a URL breaks it. | The oldest-doze field sits outside the signed part today. See known gaps. |
| Rolls a release back | The stable channel is inside the signature. The registry's CI refuses any change that drops or alters a published release. A pinned project never reads the channel. | A host can replay an older, validly signed index to a fresh resolve: indexes carry no expiry yet. |
| Takes over the mirror or CDN | Everything it serves is checked against the key in doze, so it cannot forge an index or an archive. | It can refuse service. Locked and cached projects never contact it. |
| Changes the key | doze never fetches a key: it is a constant in the doze binary. A new keys.json on the host changes nothing; anything signed by another key fails. | A real rotation takes a doze release. See the rotation question. |
| Steals the registry key | They could sign new releases. They could not change what a locked project runs: locks pin checksums, and pins move only on doze modules upgrade. | A fresh resolve or an upgrade could pick the forged release until the key is rotated. |
What a failure looks like
doze stops and says which check failed. These are the messages in the code, filled in for postgres 0.2.4.
module index for doze/postgres: index signature does not match the publisher keysignature check failed for postgres 0.2.4 (aarch64-apple-darwin): signature does not match the publisher keychecksum mismatch for postgres 0.2.4 (aarch64-apple-darwin): locked sha256:a07134d0…b89a, got sha256:[the hash of what arrived]module doze/postgres 0.2.4 (pinned in doze.lock) is no longer published — run 'doze modules upgrade postgres'module doze/postgres 0.3.1 (pinned in doze.lock) needs doze 0.3.0 or newer; this doze is 0.2.0. Update doze (in the app: Settings › About); the lock was written by a newer dozemodule index for doze/postgres: module index is unsigned[The prefix doze up prints before these lines is not shown.] The "needs doze 0.3.0" example is a future release, to show the shape; doze 0.2.0 and later print it, with three-part versions.
Verify it yourself
Everything doze checks, you can check with stock tools. These commands were run against postgres 0.2.4 and pass.
Save the registry's key
The same 32 bytes as RegistryKey in doze's source, wrapped as PEM.
-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEANu5y8mbjYv7XEtNFBXwPVl+C8SBteVApIROefBwGWjw= -----END PUBLIC KEY-----
Download an archive and hash it
Compare with releases.0.2.4.artifacts.aarch64-apple-darwin.sha256 in the index.
$ curl -sLO https://github.com/doze-dev/doze-modules/releases/download/postgres/postgres-plugin-0.2.4-aarch64-apple-darwin.tar.gz $ shasum -a 256 postgres-plugin-0.2.4-aarch64-apple-darwin.tar.gz a07134d07cf988862f5b2d1a288cc43a40ace5b96e88e190b5d66ca8b207b89a postgres-plugin-0.2.4-aarch64-apple-darwin.tar.gz
Check the signature over that hash
The signed message is the lowercase hex sha256, with no newline. The signature is the artifact's sig.
$ printf %s a07134d07cf988862f5b2d1a288cc43a40ace5b96e88e190b5d66ca8b207b89a > sum.txt $ echo MiZYLas0hSExnTF4jGo17Do0qjJ+O4XbkzBxqs2oRSJn5FuVo6rl6jC069xgafSKSP/AI4mUAJitsNcsdbDyBQ== | base64 -d > sig.bin $ openssl pkeyutl -verify -pubin -inkey doze.pem -rawin -in sum.txt -sigfile sig.bin Signature Verified Successfully
Check every index and every archive at once
The index signature is over a canonical JSON form of the index, which is fiddly by hand. The registry's own validator does exactly what doze does; validate:remote also re-downloads every archive.
$ git clone https://github.com/doze-dev/doze-registry && cd doze-registry $ npm install $ npm run validate # structure + every signature, offline $ npm run validate:remote # + each archive's sha256 as served
Ask your project
Runs the real client path, signatures included, and changes nothing. Exits 1 if a newer compatible release exists.
$ doze modules upgrade --checkNamespace doze
Nu5y8mbjYv7XEtNFBXwPVl+C8SBteVApIROefBwGWjw=- In use since
- 26 Jun 2026
- Signs
- All 8 modules, every release
- Private half
- One CI secret in doze-registry. Never on the archive host, the module repos or a build machine.
- Public half
- Compiled into doze; also at /doze/keys.json for people and the validator.
- History
- One key, no rotations
Key rotation policy
Decided so far: no rotation on a calendar; rotate on suspected compromise. A rotation is a doze release carrying the new key, then the registry is re-signed. Locked and cached projects keep working throughout; only fresh contact with the registry fails until doze is updated.
On compromise, in order: rotate, audit what the old key signed (the registry's git history), republish clean indexes, announce on the publisher's notices.
- Should doze carry the next key ahead of time, so a rotation does not need an emergency release? [to decide]
- Should the old key sign a statement naming the new one? [to decide]
- How long a superseded key stays listed, and what doze says when it sees it. [to decide]
Where the files live
The registry is static files and signatures: no server, no database, no login. A browser that opens registry.doze-dev.com is sent here.
| Path | What it holds | Trust |
|---|---|---|
| registry.doze-dev.com/doze/<module>/index.yaml | Releases, engine majors, protocol, archive URLs, each sha256 and signature, channels. | signed, twice |
| registry.doze-dev.com/doze/<module>/meta.yaml | The docs on each module page, generated from the module's Describe(). | unsigned, prose only |
| registry.doze-dev.com/doze/keys.json | The namespace and its public key. doze does not read it; its copy is compiled in. | reference copy |
| registry.doze-dev.com/index.json | The catalog this site's search reads, generated from the indexes at build. | derived |
| github.com/doze-dev/doze-modules/releases/download/<module>/…tar.gz | The plugin archives, one per platform. doze AWS and doze Kafka host theirs in their own repos. | untrusted host |
| github.com/doze-dev/doze-binaries/releases | Engine archives (Postgres 14.0 to 18.6 and the rest), pinned by sha256 in doze.lock. | checksum only |
doze 0.2 and later fetch from registry.doze-dev.com. Older doze (0.1.1) has doze.nerdmenot.in/registry compiled in; that address keeps serving the same signed files, so it keeps working.
Known gaps
What this chain does not do yet. None of them lets a host change what a locked, cached project runs.
The oldest-doze field is not signed
Each release can say which doze it needs (doze: "0.2.0"). It sits beside the signed part so that a doze from before the field still verifies. A host could strip it; the worst case is an older doze choosing a release it then cannot run. The next index schema moves it inside the signature.
fix designedNo transparency log
There is no public append-only log of what the key signed. The ledger is the doze-registry git history, and its CI refuses a change that drops or edits a published release.
[not planned yet]Indexes do not expire
A signed index carries no timestamp, so a hostile host can serve last month's valid index to a fresh resolve and hide a newer release. Pinned projects are unaffected.
[to decide]Engine binaries are checked, not signed
Postgres and the other engines are verified by sha256 from the doze-binaries index and pinned in doze.lock, but the index itself is not signed. Some engines are re-hosted upstream builds, not rebuilt from source.
[to decide]No build provenance
Releases are built in public CI, but no release links to the exact CI run that built it, and there is no signed build attestation.
new: link each release to its runOne key for everything
A single ed25519 key signs every module. There is no second signer and no threshold. Rotating it takes a doze release.
see rotation, above