← Back to blog

2026-06-11 · 7 min read

Hardening GitHub Actions: OIDC, Pinned Actions, and Least Privilege

A practical guide to securing your GitHub Actions pipelines, replace long-lived cloud keys with OIDC, pin actions to SHAs, scope permissions, and stop supply-chain attacks before they start.

#github-actions#devsecops#ci-cd#security#cloud
Hardening GitHub Actions: OIDC, Pinned Actions, and Least Privilege

Hardening GitHub Actions: OIDC, Pinned Actions, and Least Privilege

CI/CD pipelines are a juicy target: they hold cloud credentials, run third-party code, and deploy to production. Most GitHub Actions setups I review have the same handful of avoidable risks. Here's how to close them.

1. Replace long-lived cloud keys with OIDC

The biggest risk is a stored AWS_SECRET_ACCESS_KEY or GCP service-account key sitting in repo secrets. If it leaks, an attacker has standing access to your cloud.

Fix: OpenID Connect (OIDC). GitHub issues a short-lived token per run; your cloud trusts it and hands back temporary credentials. No long-lived keys anywhere.

permissions:
  id-token: write   # required for OIDC
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-deploy
          aws-region: us-east-1

GCP and Azure have the same pattern (Workload Identity Federation). Once you switch, delete the stored keys.

2. Pin actions to a full commit SHA

uses: some/action@v3 follows a moving tag. If that action is compromised, v3 can be repointed at malicious code and it runs with your secrets. Pin to an immutable SHA:

# Instead of: uses: actions/checkout@v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

Use Dependabot to bump the SHAs so you stay current without losing the protection.

3. Set least-privilege permissions

By default the GITHUB_TOKEN can be broad. Set permissions explicitly, start from nothing and add what each job needs:

permissions:
  contents: read   # most jobs only need this

Add packages: write or id-token: write only on the specific jobs that require them.

4. Be careful with pull_request_target and untrusted input

pull_request_target runs with repo secrets in the context of a PR, a classic exfiltration vector. Avoid checking out and running untrusted PR code in that context. Treat any PR-supplied value as hostile; never interpolate it directly into a run: shell line (script injection).

5. Scan in the pipeline

Add the basics so problems fail the build, not production:

  • gitleaks: catch committed secrets.
  • Trivy / Grype: scan images and dependencies.
  • tfsec / checkov: scan IaC.

(My devsecops-starter-kit wires these in.)

6. Protect the branches that deploy

  • Require PR review and passing checks on main.
  • Use environments with required reviewers for production deploys.
  • Restrict who can edit workflows.

The short checklist

  • OIDC instead of long-lived cloud keys
  • Actions pinned to SHAs (+ Dependabot)
  • Explicit least-privilege permissions:
  • No untrusted input in run:; careful with pull_request_target
  • Secret/image/IaC scanning in CI
  • Branch protection + environment approvals

None of this is exotic, it's the difference between a pipeline that's a liability and one that's an asset. Do it once and it protects every deploy after.


Want your CI/CD pipelines hardened? That's part of my DevOps consulting, reach out.

Share:LinkedInXWhatsApp

Related articles

Reactions & comments