skip to content

Why must a signature acceptance rule pin the expected issuer and not only the signer identity string?

level: middleimportance: should knowfreq 46%

answer

  1. an identity is a pair, not a string
  2. a claim needs an asserter
  3. the issuer is the namespace
  4. another issuer can mint the same subject
  5. anchor both ends of the pattern

basics

~20 s

A subject string is only a claim, and it means something solely because a particular issuer asserted it. Accept any issuer and a different one can mint an identical-looking identity, turning your identity check into a name check.

solid answer

~40 s

In identity-based verification a signing identity is a pair: the issuer that attested it, plus the subject it attested. The subject — something like `https://github.com/acme/checkout/.github/workflows/release.yml@refs/heads/main` — is just a string carried in the signing certificate; it has weight only because a specific issuer put it there. If the policy matches the subject but accepts any issuer the trust root knows, then any issuer willing to assert that string satisfies the rule, including a self-hosted identity provider an attacker controls. The second half of the same problem is matching semantics: a loose or unanchored subject pattern matches more than you meant, so `acme/checkout` also admits `acme/checkout-fork` or `evil-acme/checkout`. Pin the issuer exactly, anchor the subject, and treat every wildcard as a widening you have to defend.

go deeper

for a junior

Know that a signing identity has two halves — who asserted it and what was asserted — and that a policy naming only the second half is incomplete.

for a middle

Be ready to explain why the issuer is the namespace for the subject, and to spot the widening in an unanchored or wildcarded identity pattern when shown one.

for a senior

Demonstrate that you keep the identity string literal and per-artifact, and that a repository or workflow rename ships together with the policy change rather than being patched afterwards.

for a principal

Frame the identity expression as a release contract between producer and consumer, and own the standard that every wildcard in it needs a written justification and an owner.

## An identity is a pair, not a string When a build signs an artifact under identity-based signing, it authenticates to an OIDC issuer, receives a token describing the workload, and exchanges it for a short-lived signing certificate. That certificate carries the workload identity as a URI (typically in a subject alternative name) and separately records which issuer asserted it. Verification policy therefore has two knobs, and only using both makes the rule mean anything: - **expected issuer** — the URL of the identity provider whose assertion you accept, for example the CI provider's OIDC issuer (`https://token.actions.githubusercontent.com` for GitHub Actions) - **expected subject** — the workload identity string that issuer asserted, for example a repository plus workflow file plus ref The intuition to internalise: *the issuer is the namespace*. A subject string on its own is a name that anyone can write down. It becomes an identity only when you also say who is entitled to hand that name out. ## Failure one: the unpinned issuer Suppose the rule reads 'accept any signature whose subject is `https://github.com/acme/checkout/.github/workflows/release.yml@refs/heads/main`'. The subject looks satisfyingly specific. But if the policy does not also demand the issuer, then it will accept that subject from *any* issuer the trust root will honour: a self-hosted identity provider stood up by an attacker, a second CI tenant with its own issuer, a platform migration nobody documented. None of these are constrained to tell the truth about a string that happens to look like a GitHub URL — it is a claim about their own workloads, and they can shape it however they like. This is the same class of mistake as trusting a claim because it looks authoritative rather than because you checked who signed for it. Pinning the issuer is what converts a bare name into a namespaced identity. ## Failure two: matching semantics The second failure is that the subject match is usually written as a pattern, and patterns are widening devices. Three variants show up repeatedly: - **unanchored patterns** — a match on `github.com/acme/checkout` that is not anchored at the end also matches `github.com/acme/checkout-fork`, and one that is not anchored at the start can match a subject that merely contains that text, such as a repository under an attacker-registered organisation whose name embeds yours - **over-broad wildcards** — `^https://github.com/acme/.*$` is anchored and still accepts every repository, every workflow file, and every ref in the organisation - **forgotten components** — matching the repository and workflow file while ignoring the ref accepts a run of the release workflow from any branch, including one an attacker pushed The practical rule: prefer a literal, fully specified subject. Where a pattern is genuinely necessary, anchor both ends and treat each `.*` as a deliberate scope expansion that someone has to justify — because that is exactly what it is. ## Writing the rule so it stays honest A few habits keep identity policy from decaying: **One rule per artifact.** The expected identity for the checkout image is not the expected identity for the payments image, even when both build in the same organisation. Sharing one rule across many artifacts means the union of everything that can sign any of them. **Treat the identity string as part of the release contract.** The subject encodes a repository path, a workflow file path, and a ref. All three are things engineers rename during ordinary refactors. When they change, the policy changes in the same change, the way a schema migration ships with the code that needs it — not afterwards, under outage pressure. **Record why each component is what it is.** 'We accept this ref because releases only ever run from it' is a sentence that survives review. 'This was the pattern that made the pipeline green' is not. ## What this does not decide Expressing whose signature you accept is separate from where the check runs and what happens when it fails, and it is separate from whether the identity's issuing infrastructure deserves your trust in the first place. Those are their own problems. The narrow point here is that an acceptance rule which names a subject without naming an issuer, or which names it with a pattern looser than the thing it describes, has not actually restricted anything to your build — it has only made the policy file look like it did.

  • What goes wrong with an expected-subject pattern like `^https://github.com/acme/.*`?
    It accepts every repository in the organisation, every workflow file inside them, and every ref, so anyone able to run a workflow anywhere in the org produces a passing signature. It is also unanchored at the end, which widens it further. Prefer a literal subject; where a pattern is unavoidable, anchor both ends and justify each wildcard.
  • If your CI provider is the only issuer you use, does pinning it add anything?
    Yes. Pinning stops the same subject string from being honoured when it arrives from somewhere else — a self-hosted provider, another tenant, or a platform migration nobody told the security team about. It costs one line and it is what makes the subject a namespaced identity rather than a bare name.
  • Why does leaving the ref out of the expected subject matter?
    Because the same workflow file can run from any branch. If the rule names the repository and workflow but not the ref, a run triggered from a branch someone just pushed produces an identity that matches. Releases run from a known ref, so say so — and prefer a ref whose write access is itself controlled.

A name badge only means something if you also check which front desk printed it.

saying these in an interview costs you the question

  • Matches the repository name and ignores the issuer entirely
  • Assumes only one issuer could ever assert that subject
  • Uses an unanchored regex for the expected identity
  • Treats a certificate subject as verified fact rather than an attested claim
  • Leaves the ref out of the expected identity

context