skip to content

Job Identity & Tokens

A build's blast radius is whatever its credentials reach: the default repository token, the cloud role it federates into, and the secrets a job can read. Interviewers price it in permissions.

on this pageshow

questions

11

Why should a CI build's automatic job token be scoped per job rather than per repository?

level: juniorimportance: must knowfreq 63%

answer

  1. one grant, many jobs
  2. ceiling, not floor
  3. the job is the boundary, not the step
  4. deny by default, add per job

basics

~20 s

A repository-wide grant is the ceiling for every job in it, so a docs-preview job inherits whatever the deploy job needs. Per-job scoping means code running in a low-value job cannot touch the release path.

solid answer

~50 s

Most CI platforms hand every job an automatically issued token that authenticates the run to the hosting platform. If the permission set is configured once for the repository, it becomes the union of everything any job might need, and every job gets it: the lint job, the docs preview, the job that builds an untrusted contributor's branch. That matters because the token is readable by anything running inside the job, not only the steps you wrote — build plugins, test frameworks and a dependency's install script all run in the same process tree. So the boundary is the job, not the step. The shape I want is deny-by-default at the platform or repository level, with each job additively granted the narrow verb set it actually needs, and any job that would need two powerful verbs at once split into two jobs.

go deeper

for a junior

Be ready to say what the automatically issued job token is and that a repository-wide grant applies to every job in that repository. Know the standard shape: nothing by default, with each job adding the verbs it needs.

for a middle

Explain that anything in the job's process tree can read the token — plugins, test code, a dependency's install script — so the job, not the step, is the security boundary. Be able to name what each verb would let an attacker do.

for a senior

Show how you would tighten an existing estate: set the default to deny, watch which jobs break, and split any job that needs two powerful verbs at once. Talk about verifying by removal rather than by reading the pipeline.

for a principal

Own the argument that the default grant is a platform-level setting: flipping it organisation-wide shrinks every repository's blast radius in one change, and the cost is a wave of broken builds you have to sequence and staff.

## What the job token is Nearly every CI platform issues each run an automatically provisioned credential so builds do not need a human's personal access token. It authenticates the job back to the platform that hosts the code: reading the repository, reporting status, opening or commenting on a change, publishing a package, sometimes writing to the source repository itself. It is convenient precisely because nobody has to manage it — which is why its permissions are so often left at whatever the platform or the organisation set once, long ago. ## A repository-wide grant is a ceiling, not a floor If the grant is configured for the repository rather than per job, it has to satisfy the most demanding job in it. One release job needs to publish a package and push a tag, so the grant includes publish and write. Now the lint job, the docs-preview job, the flaky integration job and the job that builds a contributor's branch all carry publish and write too, because they are jobs in that repository. Nobody decided to give the docs job the ability to publish; it was inherited. ## Why that is not theoretical The token is not used only by the lines you wrote in the pipeline definition. Once it is present in the job — in the environment, in a credential file, or persisted into the checked-out workspace — any process the job spawns can read it. A build tool plugin, a test framework, a code generator, a dependency's post-install or build script, or a third-party build step you referenced by a mutable name all execute inside that job with the same access. The unit of trust is therefore the **job**, not the step: asking "which of my steps uses the token" is the wrong question; ask "what could run in this job." That reframing is what makes per-job scoping worth the effort. A job whose token can only read is a job you can afford to run on code you have not reviewed. The same job with write is a job that hands a contributor your release path. ## What per-job scoping actually buys - **Blast radius.** Code execution in a low-value job stops at that job's verbs instead of reaching the highest-value verb in the repository. - **Reviewability.** The permission set becomes a declaration of intent that sits next to the job. A reviewer reading the job can see what it can do, without cross-referencing a repository setting. - **Meaningful trigger questions.** "Who can cause this job to run?" is only answerable once you know what the job can do. Read-only plus an untrusted trigger is usually fine; write plus an untrusted trigger is not. ## How to get there 1. **Set the default to nothing.** Make the platform, organisation or repository default read-only or no permissions, so a new job starts with no reach and has to ask. 2. **Grant additively, per job.** Each job declares only the verbs it needs, on the narrowest resource it needs them on. 3. **Split jobs that need the union.** If a job needs two powerful capabilities at once, that is a design smell: separate them into two jobs with two identities and pass the artifact between them. 4. **Verify by removal.** A permission you cannot tie to a job that visibly fails without it is a permission you do not need. Remove it and run the pipeline. ## What it does not solve Scoping the token does not stop untrusted code from executing — that is a different control (isolation, and pinning what the job pulls in). It also does not help if the same job holds other credentials you injected explicitly: a read-only platform token next to a long-lived cloud key is still a job with full reach. Least privilege applies to *every* credential visible in the job. Finally, a repository-scoped token may still have reach outside the repository — organisation-level read, or rights over sibling projects on a shared CI tenant — which is a separate boundary to check. ## Common mistakes Granting write repository-wide because one job kept failing and nobody wanted to debug it. Assuming a read-only token is harmless, when read of a private repository is exactly how source code walks out. Believing that "only maintainers write the pipeline definition" bounds the risk, when the pipeline's whole purpose is to execute other people's code.

  • One step out of twelve in a job needs write access. What do you do?
    Split that step into its own job with its own narrow grant and pass the build output between them. The other eleven stay read-only. If the platform cannot scope below the job, the job is the unit you split — that is the point of the exercise, not a workaround.
  • Your CI platform only lets you set the token grant per repository. What now?
    Then the repository is the blast radius, so move the privileged work out of it: give the release pipeline its own repository or project with its own identity, keep the shared repository read-only, and trigger the privileged one through a reviewed event rather than by sharing a credential.
  • Doesn't a read-only token make a job safe to run on untrusted code?
    Safer, not safe. Read of a private repository is source-code exfiltration, and the job still holds whatever other secrets you injected into it. A narrow platform token is one credential among several; least privilege has to cover all of them.

A repository-wide grant is a master key cut for the whole building because one room needs it. Per-job scoping cuts a separate key for each room.

saying these in an interview costs you the question

  • Assumes only the steps you wrote can read the job token
  • Grants write at repository level so no job ever fails
  • Says the token is safe because only maintainers edit the pipeline
  • Thinks read-only access to a private repository is harmless

context

open as a page

Which claims must a cloud role's OIDC trust policy validate before it issues credentials to a CI job?

level: juniorimportance: must knowfreq 64%

basics

~20 s

A cloud trust policy checks the issuer that signed the token, the audience naming the intended recipient, and the subject identifying which repository, branch or environment the job ran in - after verifying the signature and expiry.

open as a page

A registry password is passed as a container image build argument — why is it still recoverable from the published image?

level: juniorimportance: must knowfreq 66%

basics

~20 s

Build arguments are recorded in the image's build metadata, and any file written from one stays in that layer. Layers are immutable and ship whole, so deleting the file later hides it from a running container without removing it.

open as a page

A release job's token can push to the org registry and commit to the default branch. What is the blast radius, and how do you split it?

level: seniorimportance: must knowfreq 53%

basics

~10 s

Any code executing in that job can both publish a malicious artifact and rewrite the source and tags that would have contradicted it. Separate publishing from source writes into two jobs with two identities.

open as a page

A production deploy role's OIDC trust matches the subject `repo:acme/*:*`. What has that delegated?

level: seniorimportance: must knowfreq 58%

basics

~20 s

That wildcard delegates production deployment to anyone who can create or push to any repository in the organisation. It turns the authorization decision into "is this repo ours", and creating a repository is usually a widely granted right.

open as a page

In a fan-out matrix build, why is one run-scoped token weaker than a credential minted per job?

level: middleimportance: should knowfreq 44%

basics

~20 s

A run-scoped token is shared by every parallel leg and stays valid for the whole run, so a credential stolen in one leg works for every other job until the run ends. A per-job credential expires with its job.

open as a page

Why must a cloud workload identity pool pin the audience of a CI OIDC token to its own value?

level: middleimportance: should knowfreq 44%

basics

~20 s

The audience claim is the only thing naming who may consume a bearer identity token. If the pool accepts a generic default that other relying parties also accept, any of them can replay a token the build handed them.

open as a page

Your CI masks secret values in job logs, yet a credential still escaped the build. Which build outputs carried it out?

level: middleimportance: should knowfreq 55%

basics

~20 s

Masking only redacts one job's log stream. A secret leaves just as easily inside uploaded artifacts and diagnostic bundles, cache entries restored into later jobs, image layers, test reports and crash dumps, and generated config rendered from templates.

open as a page

Your production warehouse credential is readable by every pull-request build. What does moving it behind an approval-gated environment actually stop?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Approval gating stops the credential from ever being injected into jobs that run unreviewed code. A poisoned build step cannot read a value that was never placed in its process — an authorization decision, not a redaction.

open as a page

On a shared CI estate, each project's build identity can read and queue sibling projects' pipelines. Where do you draw the isolation boundary?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Draw it by reachable blast radius, not by org chart: identities that can reach production or another team's source get a real boundary. Start deny-by-default for new projects, then inventory and expire existing cross-project grants.

open as a page

A self-hosted CI server's OIDC subject is the job's folder path, and CI admins can rename folders. What do you require before federating production?

level: principalimportance: nice to knowfreq 27%

basics

~20 s

Require a stable, non-reusable identifier in the subject, a tenant-specific audience, one role per environment, and change control over the issuer itself - because whoever administers that CI server now effectively administers the cloud roles it can assume.

open as a page