Cloud services

Blog post

Upload and purge should never share a badge

Both jobs begin in the same GitHub run, but they act on very different parts of the platform. Separate workload identities keep a compromised publisher inside one storage prefix and reserve the shared-edge permission for code we own.

Two separate operator workspaces use different identity badges for prefix-scoped blob upload and guarded global-edge invalidation
  1. Uploader Belongs one-to-one with a tenant repository and may modify blobs only beneath that site's project prefix.
  2. Service call The tenant invokes our nested purge workflow at the same library tag; it does not switch identities itself.
  3. Purger A stack application receives a token only while our exact workflow file is running, while GitHub still identifies the tenant as caller.
  4. No shared credential Tenant repositories never inherit the purge application and it has no client secret.

The split in one table

UploaderPurger
ExecutionTenant deploy jobOur reusable purge workflow only
Identifier on tenantIts own application idNone
Azure accessBlob write/delete under $web/<PROJECT_ID>/*; no Front DoorFront Door route/domain read plus endpoint purge; no blob write
OIDC trustThat repository and its provisioned branchesCaller remains in sub; trusted code is pinned by job_workflow_ref

The subject claim uses immutable identity

GitHub's customized subject is immutable-id aware: repo:org@id/name@id:ref:…. The flexible credential matches that exact form—including @<org-id> before the slash—and independently checks repository_owner_id. A looser expression such as repo:org/* does not match the token shape and would rely too heavily on mutable display names.

The public contract therefore names numeric organization and repository ids as part of the trust boundary. Tenant uploaders hold no Front Door role; only the stack application can receive the purge token under this workflow-bound expression.

A real limitation in nested reusable workflows

The parent reusable workflow can pin its composite staging action with github.workflow_sha. A nested reusable workflow cannot use that expression in uses:; GitHub rejects the workflow at parse time. Both the nested purge workflow and its composite action therefore use the same moving library tag as the parent. It is less elegant than a single immutable commit reference, but the relationship is explicit and tested.

A client id is not a key

The stack purger has no password credential. Its client id may appear in our trusted workflow because it is only an identifier; seeing it does not satisfy the federated identity credential. A tenant copy of the YAML also cannot mint a token because its job_workflow_ref points at the wrong repository.

Continue to the public edge these identities protect, or return to all posts.