When a pipeline step can reach more secrets, hosts and networks than its task needs, any code that runs in that step inherits all of it. OWASP lists this as CICD-SEC-5 Insufficient PBAC (Pipeline-Based Access Controls) in the Top 10 CI/CD Security Risks. PBAC decides how much damage follows a poisoned build, a malicious dependency or a compromised third-party action. This guide covers what PBAC means in practice, how to scope secrets, runners, host permissions and network egress per pipeline and per step, how to assess and measure it, and how it maps to NIST, CIS, ISO and MITRE frameworks. It is for platform engineers, DevSecOps teams and owners of CI/CD service identities, and goes deeper than the Top 10 CI/CD Security Risks overview.
Key takeaways
- PBAC is about blast radius. OWASP states that the damage from malicious code in a pipeline "is determined by the granularity of the PBAC in the environment".
- Three areas of control. Access within the execution environment (code, secrets, variables, other pipelines), permissions on the underlying host and other nodes, and ingress and egress filtering to the internet.
- Scope to the step, not just the pipeline. A dependency install script, a test run and a deployment step need very different access, and OWASP calls for a separate context for package installation scripts.
- Nodes must not carry state or mix trust levels. Revert execution nodes to a pristine state after every run and never share nodes between pipelines of different sensitivity.
- Every pipeline is a non-human identity. Tokens, federated cloud roles and runner host credentials should each be scoped to one pipeline, one environment and one purpose.
What it is and who it applies to
OWASP's definition starts from the execution node: pipeline execution nodes have access to numerous resources inside and outside the execution environment, and when malicious code runs in a pipeline, adversaries use insufficient PBAC to abuse the pipeline's permissions and move laterally within or outside the CI/CD system. OWASP describes PBAC as the context in which each pipeline, and each step within it, runs, and says each should be limited to the exact data and resources it needs.
CICD-SEC-5 is the consequence half of an attack. Code gets into the pipeline through OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution Guide scenarios, through OWASP CI/CD Top 10 CICD-SEC-3 Dependency Chain Abuse Guide scenarios, or through a compromised third-party action or tool; PBAC decides what that code can then reach. It applies to:
- Anyone running self-hosted runners or agents: persistent hosts, shared Docker sockets and instance roles are the most common PBAC gaps.
- Organisations with shared CI platforms: one Jenkins controller or runner fleet serving many teams lets one team's compromise reach another team's secrets.
- Teams that deploy from CI: deployment credentials turn a build compromise into a production compromise.
- Users of hosted CI services: the provider isolates jobs from each other, but the secrets, tokens and network paths you grant a job remain your responsibility.
Structure and key requirements
The OWASP entry lists what nodes typically do (access and build source code; obtain secrets from environment variables, vaults and cloud identity services such as the AWS metadata service; create, modify and deploy artefacts), the impact, and eight recommendations. The table groups the recommendations by control area.
| Control area | OWASP recommendation | Practical implementation |
|---|---|---|
| Node sharing | Do not use a shared node for pipelines with different sensitivity or resource needs | Runner groups or labelled agent pools per trust tier (public, internal build, release, production deploy) |
| Secret scoping | Each pipeline and step gets only the secrets it requires | Environment-scoped secrets, protected variables, per-job vault roles, short-lived federated tokens |
| Node hygiene | Revert the execution node to a pristine state after each run | Ephemeral or just-in-time runners, rebuilt containers or VMs per job |
| Host permissions | Least-privilege OS user for the job | Non-root runner user, no sudo, no mounted Docker socket, rootless or VM-isolated container builds |
| Controller separation | Limited job permissions on the controller; run jobs on dedicated nodes | Jenkins built-in node executors set to 0; Agent to Controller Access Control kept on |
| Patching | Keep execution nodes patched | Rebuild runner images on a schedule; track runner agent versions |
| Network | Segment so the node reaches only what it needs; avoid unlimited internet access | Egress allowlists, package proxy, blocked metadata endpoints, separate deploy network path |
| Install scripts | Separate context for package installation scripts with no access to secrets | Install dependencies in a job without secrets, pass the result as an artefact; disable lifecycle scripts where possible |
The OWASP impact statement is broad: malicious code in a pipeline node has the full permissions of the stage it runs in, can access secrets and the host, connect to any system the pipeline can reach, expose confidential data, move laterally beyond the CI environment, and deploy malicious artefacts down the pipeline, including to production.
Identity and NHI requirements
PBAC is least privilege applied to non-human identities. Each credential a pipeline holds should answer four questions: which pipeline, which step, which environment, how long.
| Identity | Over-privileged pattern | Scoped pattern |
|---|---|---|
Job token (GitHub GITHUB_TOKEN, GitLab CI_JOB_TOKEN) | Write access to contents, packages and pull requests for every job | Workflow default permissions read-only; per-job grants such as contents: write only in the release job |
| Cloud access | Static access keys in a CI variable with an admin role shared by all pipelines | OIDC federation with trust pinned to repo:ORG/REPO:environment:Production or a specific job_workflow_ref; separate roles for plan and apply |
| Runner host role | Instance profile on a self-hosted runner that every job can read through the metadata service | No instance role, or a minimal one, with job containers blocked from the metadata endpoint |
| Registry and publishing credentials | One long-lived token reused across projects and available to test jobs | Trusted publishing or per-project tokens available only to the release environment |
| Vault access | One CI role that can read every path | Per-pipeline roles bound to job identity claims, limited to that pipeline's paths |
| Cross-pipeline access | Jobs can trigger or read artefacts and caches of other pipelines | Job token allowlists, restricted cache scope, artefacts treated as untrusted input |
GitHub documents that OIDC subject claims can include the environment name, and that cloud trust configuration accepts the exact subject string, for example in an AWS condition on token.actions.githubusercontent.com:sub. Trust policies written against a whole organisation or any branch defeat PBAC because any workflow can then assume the role. See the Workload Identity Federation Guide, the Secrets Management Guide and the CI/CD Pipeline Identity Security Guide. Credential storage and rotation are covered by the OWASP CI/CD Top 10 CICD-SEC-6 Insufficient Credential Hygiene Guide.
Implementation steps
- Map reachability (weeks 1 to 3). For each pipeline record its runners, the secrets and variables each job reads, its cloud roles and their trust policies, vault roles, host permissions, and the network destinations it actually contacts. Egress logs from a few weeks of normal runs give you the allowlist baseline.
- Define trust tiers. A workable model is four tiers: untrusted (fork and pull request builds), internal build and test, release and signing, and production deployment. Each tier gets its own runner group or agent pool, and higher tiers never run lower-tier jobs.
- Move secrets to the narrowest scope. Convert repository-wide secrets to environment secrets with required reviewers (GitHub) or protected variables tied to protected branches (GitLab). Bind vault and cloud roles to pipeline identity claims. Remove secrets from any job that does not use them.
- Split risky steps into separate jobs. Run dependency installation and tests in a job that has no secrets, publish the build output as an artefact, and let a separate job with credentials consume it. GitHub's guidance notes that jobs in the same workflow can interfere with each other through shared directories, environment variables or the Docker socket, so separation at job level is not a formality.
- Make nodes ephemeral. Replace persistent self-hosted runners with just-in-time or autoscaled ephemeral runners that run one job and are destroyed. GitHub notes that self-hosted runners have no guarantee of a clean environment and can be persistently compromised by untrusted workflow code.
- Harden the host. Run the job as a non-root user, remove sudo, do not mount the Docker socket into job containers, keep runner images patched, and on Jenkins set built-in node executors to 0 so no build runs with controller file system access.
- Restrict the network. Default-deny egress from runners, allow the package proxy, SCM, artefact registry and named deployment endpoints, and block cloud metadata endpoints from job workloads. Put production deployment paths on a separate network segment reachable only from the deploy tier. See the Network Segmentation Guide.
- Enforce and review. Encode tiers in organisation policy (runner group access lists, rulesets, reusable workflows) and review role trust policies quarterly like firewall rules.
Testing: start network controls in log-only mode and compare blocked destinations with the baseline before enforcing; pilot ephemeral runners on one team's pipelines; run a canary job in each tier that tries to read secrets, the metadata endpoint and an unapproved host, and confirm each attempt fails. Rollback: keep the previous runner pool registered but disabled during migration, and manage egress rules and trust policies as code so a change can be reverted by commit.
Decision guidance
| Choice | Option A | Option B | When to choose which |
|---|---|---|---|
| Runner model | Platform-hosted ephemeral runners | Self-hosted ephemeral runners | Hosted for public repositories and small teams; self-hosted only when private network access or special hardware is needed, and then ephemeral |
| Secret delivery | CI-stored secrets scoped to environments | Federated identity and vault roles per job | Federation wherever the target supports OIDC; stored secrets only for targets that do not, with environment scoping |
| Egress control | Network allowlist at the runner subnet | Per-job proxy with policy | Subnet allowlist is enough for most; per-job policy for release and deploy tiers in large organisations |
| Organisation size | Small: hosted runners, read-only token default, environment secrets | Large: tiered runner groups, central reusable workflows, egress policy, trust policy reviews | Effort should follow the value of what the pipeline can deploy or sign |
Evidence and assessment
An assessor should be able to pick any pipeline and see exactly what one of its steps could reach. Ask for:
- Reachability map: per pipeline, the secrets, roles, runners and network destinations, with the owner of each.
- Runner configuration: runner group or agent pool access lists, ephemeral settings, image build dates, OS user and Docker socket configuration.
- Trust policies: cloud role trust documents for CI federation showing subject conditions.
- Network policy: egress rules for runner subnets and metadata endpoint blocking.
- Canary results: recent output of tier tests proving a low-tier job cannot reach high-tier secrets.
Detection and metrics
Monitor runner egress to destinations outside the allowlist, metadata endpoint requests from job workloads, cloud API calls by CI roles outside their expected actions or from unexpected repositories, secret reads from vault paths a pipeline does not normally use, and new registrations of self-hosted runners. Correlate CI audit logs with cloud and vault logs so a credential used outside a pipeline run stands out.
| Metric | Target |
|---|---|
| Jobs with access to secrets they never use | Zero, measured from secret references versus usage |
| Self-hosted runners that are persistent or shared across trust tiers | Zero |
| CI federation trust policies without repository and environment or branch conditions | Zero |
| Runner subnets with unrestricted internet egress | Zero for release and deploy tiers |
| Workflows with read-only default token permissions | 100 percent |
| Runner image age | Within patch SLA, for example under 30 days |
Mapping to other frameworks
| Framework | Item | Relevance to PBAC |
|---|---|---|
| NIST SP 800-53 Rev. 5 | AC-6 Least Privilege; AC-6(9) Log Use of Privileged Functions | Minimal permissions per pipeline and step; logging privileged use |
| NIST SP 800-53 Rev. 5 | AC-4 Information Flow Enforcement; SC-7 Boundary Protection | Egress filtering and segmentation of runners |
| NIST SP 800-53 Rev. 5 | SC-39 Process Isolation; CM-7 Least Functionality | Ephemeral, separated nodes with minimal software and privileges |
| NIST CSF 2.0 | PR.AA-05; PR.IR-01; PR.PS-01 | Least-privilege permissions; protecting environments from unauthorised access; configuration management |
| CIS Controls v8.1 | Control 6 Access Control Management; Control 12 Network Infrastructure Management; Control 16 Application Software Security | Scoped access, network segmentation, secure build infrastructure |
| ISO/IEC 27001:2022 Annex A | A.8.2 Privileged access rights; A.8.22 Segregation of networks; A.8.31 Separation of development, test and production environments | Restricted pipeline privileges, segmented runner networks, separated deploy tier |
| MITRE ATT&CK v20 | T1552.001 Credentials In Files; T1552.005 Cloud Instance Metadata API; T1528 Steal Application Access Token; T1078.004 Cloud Accounts | Credential theft from nodes and use of pipeline cloud identities |
| MITRE D3FEND | D3-EI Execution Isolation; D3-NI Network Isolation; D3-OTF Outbound Traffic Filtering | Isolated jobs, segmented networks, restricted egress |
Common pitfalls
- Every job sees every secret. In the Codecov breach, a modified uploader script sent the output of
envfrom customers' CI jobs to an attacker server, exposing whatever tokens and keys those jobs carried. A coverage upload job that held no unrelated secrets would have exposed far less. - Relying on the platform to separate trust. Travis CI CVE-2021-41077 exposed public projects' secrets to forked pull request builds; secrets scoped to release environments would not have been present in those builds.
- Shared caches and state across privilege levels. In the Ultralytics PyPI compromise, code run by a pull request workflow is believed to have poisoned the Actions cache later restored by the release build, and a leftover PyPI token was then used to publish further malicious versions.
- Organisation-wide cloud trust. An OIDC trust policy accepting any repository in the organisation lets an unrelated, low-trust workflow assume a production role.
- Docker socket and root on the runner. OWASP cites a Teleport CI finding where a pull request could run a privileged container and escalate to root on the node, leading to secret exfiltration and release of malicious artefacts.
- Open internet egress. Unrestricted outbound access makes exfiltration a single request and lets build steps fetch unvetted code. The CI/CD Pipeline Compromise Playbook covers response when a node is compromised.
Practitioner checklist
- Map, per pipeline and step, the secrets, roles, hosts and network destinations it can reach.
- Define trust tiers and give each its own runner group or agent pool.
- Scope secrets to environments or protected branches and remove unused secret references.
- Run dependency installation and tests in jobs without secrets.
- Replace persistent runners with ephemeral or just-in-time runners.
- Run jobs as non-root without the Docker socket; set Jenkins built-in node executors to 0.
- Default-deny runner egress and block metadata endpoints from job workloads.
- Pin every CI federation trust policy to repository plus environment or branch claims.
- Set default job token permissions to read-only.
- Run tier canary tests after every runner or policy change.
Standards and references
- OWASP: CICD-SEC-5 Insufficient PBAC (Pipeline-Based Access Controls)
- OWASP: Top 10 CI/CD Security Risks
- GitHub Docs: Secure use reference for GitHub Actions
- GitHub Docs: OpenID Connect reference
- GitLab Docs: Merge request pipelines
- Jenkins: Controller Isolation
- CISA: CISA and NSA Release Joint Guidance on Defending CI/CD Environments
Related NHI Mgmt Group resources: Top 10 CI/CD Security Risks · OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution Guide · CI/CD Pipeline Identity Security Guide · GitHub and GitHub Actions Security Guide · Workload Identity Federation Guide