Back to Blog
Lesson 42 of the CI/CD: Continuous Integration from Scratch course
DevOpsAugust 17, 20263 min read

Security Best Practices: Hardening Your DevOps Pipelines

Learn to secure your CI/CD pipelines by auditing workflow permissions and implementing least privilege access to protect your infrastructure from compromise.

securityhardeningDevOpsGitHub ActionsCI/CD
Close-up of a rustic wooden gate with a padlock, offering a secure outdoor setting.

Previously in this course, we discussed monitoring pipeline health to keep our builds performant. In this lesson, we shift our focus to security, specifically how to harden your CI/CD workflows by controlling exactly what your automation can do.

In professional DevOps, "security" isn't just about firewalls; it's about restricting the blast radius of your automation. If a malicious dependency or a compromised runner executes code in your pipeline, you want to ensure it doesn't have the keys to the kingdom.

The Principle of Least Privilege in CI/CD

The principle of least privilege dictates that a module—in this case, your GitHub Actions job—should possess only the permissions necessary to perform its intended task, and nothing more.

By default, GitHub Actions workflows often run with broader permissions than they actually need. If your pipeline only needs to read your code to run tests, it shouldn't be able to delete your repository or modify your branch protections.

Understanding Workflow Permissions

GitHub Actions uses permissions blocks to define the scope of the GITHUB_TOKEN. This token is automatically generated for every job. If you don't define a permissions block, GitHub defaults to a broad set of read/write access.

To secure your pipeline, you should explicitly set these permissions to read-all by default, and then grant specific write access only where absolutely required.

Worked Example: Hardening a Workflow

Let's look at a standard test workflow and apply the principle of least privilege.

Before (Default/Insecure):

YAML
name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

Note: In the configuration above, the GITHUB_TOKEN has broad read/write access to your repository by default.

After (Hardened/Secure):

YAML
name: CI
on: [push]
permissions:
  contents: read # Only allow reading the source code

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

If your pipeline needs to post comments on Pull Requests (often done by linters or test reporters), you would explicitly add that specific permission:

YAML
permissions:
  contents: read
  pull-requests: write # Only grant write access where strictly needed

Hands-on Exercise: Audit Your Workflow

  1. Open your main .github/workflows/ YAML file.
  2. Add a permissions: block at the top level of the file (before the jobs: section).
  3. Set it to contents: read.
  4. Run your pipeline. If it fails, check if any step was trying to write to the repository (e.g., a cache update or a status reporter).
  5. Add back only the specific write permissions required (e.g., actions: write if you are using actions/cache).

Common Pitfalls

  • Over-permissioning: It is tempting to set permissions: write-all to "just make it work." Avoid this at all costs; it exposes your repository to unnecessary risk.
  • Assuming Implicit Security: Never assume that because a runner is "temporary," it is secure. A compromised runner can steal environment variables or secrets if it has the permissions to access them.
  • Ignoring Dependency Security: While we are focusing on workflow permissions, remember that your code is only as secure as its dependencies. Always ensure you are using dependency scanning to catch vulnerabilities early.

Frequently Asked Questions

What happens if I set permissions too restrictively? Your pipeline steps will fail with a "403 Forbidden" error when trying to access the GitHub API. Check the runner logs to identify which specific permission was denied.

Does this replace other security measures? No. This is one layer of a "defense-in-depth" strategy. You should still use secret management best practices and avoid command injection by sanitizing inputs.

Recap

Hardening your DevOps pipelines is a continuous process. By auditing your workflow permissions and enforcing the principle of least privilege, you significantly reduce the potential impact of a security incident. Start by defaulting to read access and only escalating to write when the job explicitly requires it.

Up next: We will tackle the challenge of identifying and managing flaky tests to keep your pipeline reliable.

Similar Posts