Official signatures
Why signatures
Catalog adversaries are executable code. The CLI allows host execution (full user privileges) only when it can cryptographically verify that Adversary Labs endorsed the exact artifact bytes you are about to run.
Trust is not granted by:
- registry hostname alone;
- namespace or catalog path strings (for example
go/securityoradversarylabs/...); - successful login; or
- the fact that a package was previously pulled to disk.
Trust is granted when either a valid official signature verifies for the immutable content digest using a public key baked into the CLI, or a hosted private package has a valid team key delegation and namespace signature.
See also the execution trust model.
What users need to know
| Question | Answer |
|---|---|
| Do I need Cosign or Notation? | No. The adversary CLI verifies signatures itself. |
| What is signed? | The immutable artifact digest (sha256:…). |
| Where does the signature live? | OCI referrer on the registry, plus a local store copy after pull. |
| How do free catalog packages get signed? | Release CI signs with official-prod after each successful publish. |
| How do private team packages get signed? | doomer push requests a server-side signature automatically when publishing into that team's Doomer registry namespace. |
| Does the publisher receive a private key? | No. The platform signs the immutable digest; publishers and runners receive public verification material only. |
| Does copying a signed package to GHCR carry trust? | No. Namespace signatures bind the Doomer registry host, exact repository, and digest. |
| What if there is no signature? | Host execution is untrusted: blocked non-interactively, or confirmed on a TTY / via --allow-unsafe-host-execution. |
Typical flow:
brew install doomerlabs/tap/doomer # release binary embeds official-prod
doomer login
doomer pull go/security # fetches content + signature referrer
doomer run go/security --path . # verifies signature → host allowedIf you run a package without a valid artifact signature:
Untrusted adversary "…"
Digest: sha256:…
No valid artifact signature is available for this package. Host execution will
run unrestricted code with your user privileges (filesystem, network, etc.).
Run anyway? [y/N]Non-interactive environments fail unless you pass
--allow-unsafe-host-execution or use a capable sandbox executor.
Model
| Concept | Mechanism |
|---|---|
| What is signed | Immutable artifact digest (sha256:…) |
| Algorithm | Ed25519 |
| Where signature lives | OCI referrer + local repository after pull |
| Who verifies | adversary CLI (embedded official key or authenticated platform trust root plus team delegation) |
| Who signs | Catalog release CI for official packages; the platform signing service for hosted private packages |
The platform service holds ADVERSARY_NAMESPACE_SIGNING_ROOT_SEED (32 random
bytes encoded as 64 hex characters). It derives domain-separated Ed25519 keys
for the delegation root and each stable team ID. This secret must be provisioned
before releasing a CLI that enables automatic private signing and must never be
placed in a publisher or runner environment.
Dev vs production keys
Dev / default go build | Release (-tags release) | |
|---|---|---|
| Build | no special tags | -tags release (Homebrew / release scripts) |
| Public key in binary | official-dev only | official-prod only |
| Default key id | official-dev | official-prod |
| Signs | local/staging catalogs | registry.doomer.ai catalog releases |
A released CLI cannot verify packages signed only with the dev key, because that public key is not present in the binary. Build tags keep keys out of the wrong artifact (an environment variable alone would still ship both).
# Everyday development (dev key only)
go build -o adversary .
# Release-shaped binary (prod key only) — matches Homebrew builds
go build -tags release -o adversary .Envelope
Media type: application/vnd.adversarylabs.official-signature.v1+json
{
"specVersion": 1,
"subjectDigest": "sha256:…",
"keyID": "official-prod",
"signedAt": "2026-08-01T12:00:00Z",
"signature": "<base64 ed25519 signature>"
}Signed message (exact bytes):
adversarylabs-official-sig-v1
<subjectDigest>
<keyID>
<signedAt>Verify path in the CLI
doomer pullresolves the digest, installs content, fetches the signature referrer, verifies with this binary’s keyring, and stores the envelope underofficial-signatures/in the local repository.doomer runsets official-signed trust when verification succeeds for that digest, then allowsHostExecutor.
Offline runs use the stored signature; re-pull if the package was installed before signatures were published.
Catalog release signing
Official free-catalog packages are signed in Depot release workflows after a successful push:
- Bump manifest and push the OCI artifact.
- Install the CLI (
go install github.com/doomerlabs/doomer@main). - Log in with the service account.
- Run
doomer sign <remote-ref> --digest <subject> --key-id official-prodwith the production signing material from CI secrets.
End users only need a current release CLI.
Signing for maintainers (dev and CI)
Signing material is supplied with --seed or
ADVERSARY_OFFICIAL_SIGNING_SEED.
# Local dev catalog (Doppler injects the dev seed via package Makefile)
make sign-dev REF=localhost:8787/go/security:0.0.12
# Or invoke the CLI directly
doomer sign localhost:8787/go/security:0.0.12 \
--seed "$ADVERSARY_OFFICIAL_SIGNING_SEED" \
--key-id official-dev
# Production (CI only)
doomer sign registry.doomer.ai/go/security:0.0.12 \
--digest sha256:… \
--key-id official-prodSecrets layout (operators)
Same secret name in every environment; values differ by environment:
| Name | Dev | Production CI |
|---|---|---|
ADVERSARY_OFFICIAL_SIGNING_SEED | Dev seed only | Prod seed only |
| Public keys | Embedded in CLI via build tags | Embedded in release CLI |
Notation, Cosign, and TUF
- Notation / Cosign: optional later for CI interop; end users still only need this CLI.
- TUF: later for key rotation without shipping a new binary for every key id.
Migration notes
- Catalog packages that predate official signing must be re-released and re-pulled before host trust applies without overrides.
- Local source projects (explicit path) remain trusted by path selection and do not require signatures.
- Private packages in a team namespace on
registry.doomer.aibecome trusted after their platform-issued namespace signature verifies. External registry copies, including GHCR, remain untrusted by default.