Registry Signing Identity
Syllago’s Model for Origin Attestation and Trust (MOAT) implementation verifies every registry manifest against a pinned cryptographic identity before content transitions into your library. The identity is pinned at registry-add time — not at every verify — so an attacker who later gains control of the registry’s repository or CI workflow cannot silently swap the source of your content.
This page covers the operator workflow for syllago registry add and the three ways to pin an identity. The protocol detail is captured in an Architecture Decision Record (ADR) — see ADR 0007 in the syllago repo.
Why pinning matters
Section titled “Why pinning matters”GitHub lets an organization rename or transfer a repository at any moment, and the new owner inherits the old URL. Sigstore Fulcio certificates embed a numeric repository ID and owner ID alongside the human-readable URL. Pinning those numeric IDs at registry add time means:
- A repository rename or transfer flips the numeric IDs, verification fails loudly, and syllago refuses to add content.
- Two orgs with identical URLs (
example/toolvsexample/toolunder a different parent after a transfer) are cryptographically distinguishable. - You commit to a specific publishing identity the day you add the registry. No TOFU (trust-on-first-use) surprises months later.
The identity is persisted to ~/.syllago/config.json in a signing_profile object on the registry entry. Subsequent syncs and adds verify against that pinned profile.
Three paths to pin an identity
Section titled “Three paths to pin an identity”1. Well-known registries (nothing to do)
Section titled “1. Well-known registries (nothing to do)”Syllago ships with a bundled allowlist covering the public meta-registry and a small number of other well-known syllago ecosystem registries. For those, syllago registry add <url> just works — the allowlist supplies the signing profile automatically.
syllago registry add https://github.com/OpenScribbler/syllago-meta-registry Trust: signed (registry OpenScribbler/syllago-meta-registry, root: bundled)If the URL matches an allowlist entry, syllago silently adopts its pinned profile. You do not need to pass --signing-* flags.
2. GitHub-hosted registries (pass numeric IDs)
Section titled “2. GitHub-hosted registries (pass numeric IDs)”For a GitHub-hosted registry not in the bundled allowlist, pass the identity explicitly:
syllago registry add https://github.com/acme/tools-registry \ --signing-identity 'https://github.com/acme/tools-registry/.github/workflows/moat-registry.yml@refs/heads/main' \ --signing-repository-id 1234567890 \ --signing-repository-owner-id 987654321The --signing-identity value is the full GitHub Actions workflow identity that publishes the registry’s manifest. The numeric IDs come from the GitHub REST API:
gh api repos/acme/tools-registry --jq '{id, owner_id: .owner.id}'# → {"id": 1234567890, "owner_id": 987654321}3. Non-GitHub OIDC issuers (slice-2 scope)
Section titled “3. Non-GitHub OIDC issuers (slice-2 scope)”For registries signed against a non-GitHub OIDC issuer, pass --signing-issuer alongside --signing-identity:
syllago registry add https://git.internal.example/team/registry \ --signing-issuer https://oidc.internal.example \ --signing-identity 'ci@internal.example'Numeric IDs are optional for non-GitHub issuers at slice-2 scope. Additional issuer support (GitLab, Azure, etc.) arrives in later slices.
The trust label
Section titled “The trust label”Every successful syllago add --from <registry> against a MOAT registry emits a trust label:
Trust: signed (registry acme/tools-registry, root: bundled)The three-state label is intentional:
| Label | Meaning |
|---|---|
signed | Manifest signature valid, pinned identity matched, trusted root fresh. |
unsigned | Registry has no pinned profile and no manifest — slice-1 legacy mode. |
invalid | Verification attempted but rejected. The add aborts and surfaces a MOAT_* error. |
The word verified is deliberately not used at slice 2. It is reserved for when revocation checking lands in a later slice.
Escape hatch: --trusted-root
Section titled “Escape hatch: --trusted-root”For enterprise or air-gapped deployments that pin a specific Sigstore trusted root, pass --trusted-root on syllago add or persist a trusted_root path on the registry in config.json:
syllago add loadout/daily --from acme/tools-registry \ --trusted-root /etc/syllago/corp-trusted-root.jsonPrecedence: the CLI flag wins over the per-registry config, which wins over the bundled default. Syllago emits an auditor-visible marker on stderr when an override is in use:
moat.trusted_root_path=/etc/syllago/corp-trusted-root.json (registry=acme/tools-registry)Override roots are not staleness-gated — the 365-day cliff only applies to the bundled root that ships with the syllago binary. Operators who supply their own trusted root take responsibility for refreshing it.
When verification fails
Section titled “When verification fails”If any --signing-* flag is passed but verification cannot be satisfied, syllago returns a structured MOAT_* error and refuses to add content. No silent fall-back to unsigned mode — a pinned profile means the operator has opted into cryptographic gating.
| Code | Meaning |
|---|---|
MOAT_001 | No allowlist match and no --signing-identity supplied. |
MOAT_002 | --signing-* flags are incomplete or malformed. |
MOAT_003 | Manifest certificate does not match the pinned profile. |
MOAT_004 | Manifest or bundle is malformed, missing, or unreadable. |
MOAT_005 | Bundled trusted root exceeded its 365-day cliff. Run syllago update. |
MOAT_006 | Pinned profile but no manifest/bundle in the checkout. |
MOAT_007 | --trusted-root or reg.trusted_root path cannot be loaded. |
Why not prompt interactively?
Section titled “Why not prompt interactively?”Hard-fail is defense-in-depth. An interactive prompt trains operators to mash y without actually confirming the numeric IDs out-of-band. A CI pipeline also cannot respond to a prompt — the only way to pass --signing-* flags is to hard-code the identity somewhere, which is exactly what you want. The failure is also CI-safe: the exit code is non-zero, so pipelines fail loudly rather than silently proceeding unsigned.
What happens on registry rename?
Section titled “What happens on registry rename?”Numeric IDs do not change on rename; they change on transfer to a new owner. After a rename, verification continues to succeed. After a transfer to a different owner, verification fails with MOAT_003 and the operator must re-add against the new numeric IDs out-of-band.
Can I change the pinned identity later?
Section titled “Can I change the pinned identity later?”Yes. Remove the registry and re-add it with updated --signing-* flags:
syllago registry remove acme/tools-registrysyllago registry add https://github.com/acme/tools-registry --signing-identity '…' …There is no in-place edit by design — re-adding forces the operator to re-confirm the identity.
Contributing to the bundled allowlist
Section titled “Contributing to the bundled allowlist”If you operate a public registry and want bundled inclusion so users can add your registry without --signing-* flags, open an issue on the syllago repo with:
- Your registry’s public repository URL.
- The numeric repository ID and owner ID.
- The full signing identity (workflow path + ref).
- A link to at least one published manifest signed with that identity.
Allowlist inclusion is reviewed against the same ADR 0007 criteria that apply to ad-hoc pinning — the goal is to reduce friction for well-known registries, not to shortcut the trust model.