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 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
| 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. |
| 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 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 allowedIf you run a package without a valid official signature:
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
| Concept | Mechanism |
|---|---|
| What is signed | Immutable artifact digest (sha256:…) |
| Algorithm | Ed25519 |
| Where signature lives | OCI referrer + local repository after pull |
| Who verifies | adversary CLI (public key embedded in the binary) |
| Who signs | Catalog release CI (and local tooling for dev) |
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.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).
# 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
adversary 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.adversary 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/adversarylabs/adversary@main). - Log in with the service account.
- Run
adversary 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
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-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.
- Third-party or private registry packages remain untrusted for host execution until a future authenticated team-trust or enterprise trust store exists.