Official signatures

Ed25519 catalog signatures, CLI verification, and host-execution trust.

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/security or adversarylabs/...);
  • 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

QuestionAnswer
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:

sh
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 allowed

If you run a package without a valid artifact signature:

text
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

ConceptMechanism
What is signedImmutable artifact digest (sha256:…)
AlgorithmEd25519
Where signature livesOCI referrer + local repository after pull
Who verifiesadversary CLI (embedded official key or authenticated platform trust root plus team delegation)
Who signsCatalog 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 buildRelease (-tags release)
Buildno special tags-tags release (Homebrew / release scripts)
Public key in binaryofficial-dev onlyofficial-prod only
Default key idofficial-devofficial-prod
Signslocal/staging catalogsregistry.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).

bash
# 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

json
{
  "specVersion": 1,
  "subjectDigest": "sha256:…",
  "keyID": "official-prod",
  "signedAt": "2026-08-01T12:00:00Z",
  "signature": "<base64 ed25519 signature>"
}

Signed message (exact bytes):

text
adversarylabs-official-sig-v1
<subjectDigest>
<keyID>
<signedAt>

Verify path in the CLI

  1. doomer pull resolves the digest, installs content, fetches the signature referrer, verifies with this binary’s keyring, and stores the envelope under official-signatures/ in the local repository.
  2. doomer run sets official-signed trust when verification succeeds for that digest, then allows HostExecutor.

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:

  1. Bump manifest and push the OCI artifact.
  2. Install the CLI (go install github.com/doomerlabs/doomer@main).
  3. Log in with the service account.
  4. Run doomer sign <remote-ref> --digest <subject> --key-id official-prod with 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.

bash
# 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-prod

Secrets layout (operators)

Same secret name in every environment; values differ by environment:

NameDevProduction CI
ADVERSARY_OFFICIAL_SIGNING_SEEDDev seed onlyProd seed only
Public keysEmbedded in CLI via build tagsEmbedded 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.ai become trusted after their platform-issued namespace signature verifies. External registry copies, including GHCR, remain untrusted by default.