CI/CD Lesson 26 – Access control and Permissions | Dataplexa
Section III · Lesson 26

Access Control & Permissions

In this lesson

Least Privilege Repository Roles Token Permissions Environment Protection Deployment Approvals

Access control in CI/CD is the discipline of defining who can trigger pipelines, who can approve deployments, who can modify workflow definitions, and what permissions the pipeline itself holds when it runs. It is the human and machine layer of security that sits above the pipeline's technical controls — governing not just what the pipeline can do, but who can instruct it to do anything at all. A pipeline secured at the technical level but accessible to anyone who can open a pull request is not a secure pipeline; it is a well-engineered attack surface with an unlocked front door.

Least Privilege as an Organisational Principle

The principle of least privilege states that every actor — human or machine — should have exactly the permissions required to perform its defined function, and no more. Applied to CI/CD, this means developers who write application code should not automatically have production deployment access. A pipeline job that runs tests should not hold credentials that allow it to push to the container registry. A new contributor should not be able to trigger a workflow that has access to production secrets on their first PR.

Least privilege is not a single setting — it is a continuous practice of auditing what access exists, why it exists, and whether it is still needed. Access accumulates over time: a developer who needed temporary production access for an incident three months ago may still hold that access today. A service account created for a deprecated pipeline may still have live credentials. Regular access reviews — at least quarterly for production systems — are the mechanism that prevents privilege accumulation from becoming a standing vulnerability.

The Hospital Badge Analogy

A hospital gives every staff member a badge, but the badge does not open every door. A nurse's badge opens patient wards and medication rooms. An administrator's badge opens offices and record storage. A surgeon's badge opens operating theatres. No single badge opens everything — and every badge access is logged. CI/CD access control works identically: every person and every pipeline job holds a badge scoped to exactly the doors they need, and every use of elevated access is recorded in an audit log that can be reviewed when something goes wrong.

Repository Roles and Team Structure

GitHub organises repository access through five roles, each granting a different set of permissions. The correct assignment of roles — and the discipline of not granting higher roles than necessary — is the first line of access control for any CI/CD system that lives in GitHub.

GitHub Repository Roles — CI/CD Relevant Permissions

Role
Can Do
Cannot Do
Read
View code, view workflow runs and logs
Push code, trigger workflows, manage secrets
Triage
Manage issues and PRs, re-run failed workflows
Push code, manage secrets, approve deployments
Write
Push to non-protected branches, trigger workflows
Manage secrets, change branch protections, approve environments
Maintain
Manage branch protections, approve environment deployments
Delete repository, manage repository settings
Admin
Full control — manage secrets, settings, branch rules, team access
Nothing — this role has no restrictions within the repository

The most common access control mistake in growing teams is granting Admin or Write roles broadly to avoid having to think about permissions per person. Every developer with Admin access to a repository can modify branch protection rules, change environment secrets, and bypass every pipeline gate. Admin access should be restricted to a small number of named individuals with a documented reason for each grant.

Token Permissions — Scoping the Pipeline's Own Access

Every GitHub Actions workflow run receives a GITHUB_TOKEN — an automatically generated credential scoped to the repository that runs the workflow. By default, this token has broad permissions: it can read and write repository contents, create issues, manage pull requests, write packages, and trigger other workflows. For most pipeline jobs, the vast majority of these permissions are unnecessary.

Declaring minimal permissions at the workflow level and granting additional permissions only to the specific jobs that need them is one of the most impactful access control changes a team can make. A test job that needs to read the repository and nothing else should declare exactly that. A job that publishes a Docker image needs packages: write. A job that creates a release needs contents: write. Everything else gets read or is omitted entirely.

Minimal Token Permissions — GitHub Actions

name: CI Pipeline

permissions:                             # Workflow-level default — applies to all jobs
  contents: read                         # Read repository code only
  # All other permissions default to none

jobs:
  test:
    runs-on: ubuntu-latest
    # Inherits workflow-level permissions — contents: read only
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

  publish-image:
    needs: test
    runs-on: ubuntu-latest
    permissions:                         # Job-level override — only this job gets write access
      contents: read
      packages: write                    # Needed to push to GitHub Container Registry
      id-token: write                    # Needed for OIDC token request
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t ghcr.io/myorg/app:${{ github.sha }} .
      - run: docker push ghcr.io/myorg/app:${{ github.sha }}

  create-release:
    needs: publish-image
    runs-on: ubuntu-latest
    permissions:
      contents: write                    # Needed to create a GitHub Release
    steps:
      - uses: actions/github-script@v7
        with:
          script: |
            await github.rest.repos.createRelease({
              owner: context.repo.owner,
              repo: context.repo.repo,
              tag_name: `v${process.env.VERSION}`,
              name: `Release v${process.env.VERSION}`
            })

What just happened?

The workflow declares contents: read as the default for all jobs. The test job inherits this and nothing more — it cannot push code, create releases, or write packages even if a compromised step tried to. The publish and release jobs override permissions at the job level, granting only what each specific job requires. A compromise of the test job has a blast radius of zero beyond reading the repository.

Environment Protection Rules and Deployment Approvals

GitHub environment protection rules are the access control layer for deployments. A protected environment — typically production — can require that specific named individuals or teams review and approve a deployment before it runs. The pipeline pauses at the deployment job, sends a notification to the required reviewers, and only proceeds when the approval is granted. No code change is required to pause a deployment — the control sits at the infrastructure level.

Beyond manual approval, environment protection rules support additional controls: a wait timer that forces a minimum soak period between approval and deployment, deployment branch filters that restrict which branches can deploy to which environments, and required status checks that must pass before any deployment to that environment is permitted. Together these controls form a deployment governance layer that operates independently of the pipeline code — meaning even a fully automated pipeline can be governed by humans at the environment boundary without changing a single line of YAML.

Environment Protection Controls — Reference

Required reviewers
Named individuals or teams who must approve before the deployment job runs. Up to 6 reviewers can be required. The deployment pauses until the required number approve.
Wait timer
A mandatory delay (1–43,200 minutes) between approval and execution. Useful for regulated environments requiring a minimum review period, or change windows that restrict when deployments can occur.
Deployment branches
Restricts which branches or tags can deploy to this environment. A production environment configured to only accept deployments from main cannot be accidentally deployed to from a feature branch.
Prevent self-review
Prevents the person who triggered the deployment from also approving it. Enforces four-eyes separation between the developer who wrote the code and the person who authorises the production deployment.

Warning: Admin Access Granted for Convenience Is a Permanent Security Liability

The most common access control failure in engineering teams is not a sophisticated attack — it is granting Admin role to a developer who needed to change one setting, and never removing it. An Admin can disable branch protection rules, remove required reviewers from production environments, delete secrets, modify workflow files without review, and bypass every pipeline gate the team has carefully built. Every Admin grant should have a documented reason, an expiry date, and a review cadence. If your team's access audit reveals that more than two or three people hold Admin on a production-connected repository without a specific, current reason, that is a standing vulnerability.

Key Takeaways from This Lesson

Least privilege applies to both humans and pipeline jobs — every person holds the minimum role needed for their work, and every pipeline job holds only the token permissions required for its specific task. Excess access in either dimension expands the blast radius of any compromise.
Admin role should be rare and documented — Admin access bypasses branch protections, environment approvals, and secret management controls. Every Admin grant must have a specific reason, an expiry, and a review date.
Workflow-level token permissions set the default; job-level permissions override for specific needs — declaring contents: read at the workflow level means every job starts with minimal access, and only jobs that explicitly need more get it.
Environment protection rules govern deployment access independently of pipeline code — required reviewers, wait timers, branch filters, and self-review prevention operate at the infrastructure level and cannot be bypassed by modifying workflow YAML.
Access accumulates silently without regular audits — permissions granted for temporary needs rarely get revoked. Quarterly access reviews for production-connected repositories are the mechanism that prevents privilege accumulation from becoming a standing vulnerability.

Teacher's Note

Add permissions: contents: read to every existing workflow file this week — it takes 30 seconds per file and immediately reduces the blast radius of any future pipeline compromise without changing any pipeline behaviour.

Practice Questions

Answer in your own words — then check against the expected answer.

1. What GitHub environment protection rule prevents the person who triggered a deployment from also being the one who approves it — enforcing a separation between the developer who wrote the code and the person who authorises the production release?



2. What is the name of the automatically generated credential that every GitHub Actions workflow run receives — scoped to the repository — whose default permissions are broader than most pipeline jobs require, making explicit permission declarations at the workflow level a critical security control?



3. What security principle states that every actor — human or machine — should hold exactly the permissions required to perform its defined function and no more — the principle that underlies all access control decisions in CI/CD systems?



Lesson Quiz

1. A pipeline has three jobs: test, publish-image, and create-release. The workflow declares packages: write and contents: write at the workflow level, so all three jobs inherit both permissions. A security review flags this. What is the correct fix?


2. A developer accidentally triggers a production deployment from a feature branch. What environment protection control would have prevented this without requiring any change to the workflow file?


3. A developer was granted Admin role six months ago to help resolve an incident. The incident is resolved but the role was never removed. A security audit discovers this. What access control failure does this illustrate?


Up Next · Lesson 27

CI/CD with Containers

Containers changed how software is packaged and deployed — and they changed how CI/CD pipelines are structured. Lesson 27 covers how containers fit into the pipeline from build to production.