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.
- Uploader Belongs one-to-one with a tenant repository and may modify blobs only beneath that site's project prefix.
- Service call The tenant invokes our nested purge workflow at the same library tag; it does not switch identities itself.
- Purger A stack application receives a token only while our exact workflow file is running, while GitHub still identifies the tenant as caller.
- No shared credential Tenant repositories never inherit the purge application and it has no client secret.
The split in one table
| Uploader | Purger | |
|---|---|---|
| Execution | Tenant deploy job | Our reusable purge workflow only |
| Identifier on tenant | Its own application id | None |
| Azure access | Blob write/delete under $web/<PROJECT_ID>/*; no Front Door | Front Door route/domain read plus endpoint purge; no blob write |
| OIDC trust | That repository and its provisioned branches | Caller 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.