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 a valid official signature verifies for the immutable content digest using a public key baked into that CLI binary.

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.
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 adversarylabs/tap/adversary   # release binary embeds official-prod
adversary login
adversary pull go/security                # fetches content + signature referrer
adversary run go/security --path .      # verifies signature → host allowed

If you run a package without a valid official signature:

text
Untrusted adversary "…"
Digest: sha256:…

No valid official 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 (public key embedded in the binary)
Who signsCatalog release CI (and local tooling for dev)

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.adversarylabs.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. adversary 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. adversary 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/adversarylabs/adversary@main).
  3. Log in with the service account.
  4. Run adversary 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
adversary sign localhost:8787/go/security:0.0.12 \
  --seed "$ADVERSARY_OFFICIAL_SIGNING_SEED" \
  --key-id official-dev

# Production (CI only)
adversary sign registry.adversarylabs.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.
  • Third-party or private registry packages remain untrusted for host execution until a future authenticated team-trust or enterprise trust store exists.