Git, GitHub & DevOps/06advanced12 min

Secrets and pipeline security (gh secret, OIDC)

The pipeline is an attack surface too

CI/CD has access to the code, the secrets, and often to production environments. A leaked workflow token can be more dangerous than a bug in the application. This lesson clarifies how to keep secrets and how to get rid of them entirely with OIDC.

Secrets in GitHub Actions

Secrets are encrypted and injected into the workflow as variables. They never end up in logs (GitHub masks their values). You define them at the level of the repo, the environment, or the organization.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        env:
          API_TOKEN: ${{ secrets.API_TOKEN }}
        run: ./scripts/deploy.sh

Management via gh CLI

gh secret lets you set secrets from the terminal — faster than clicking in the panel and easy to automate.

# Set a repository secret (from a file or interactively)
gh secret set API_TOKEN

# From a value in a variable / file
gh secret set API_TOKEN --body "$TOKEN"
gh secret set SERVICE_JSON < service-account.json

# A secret for a specific environment
gh secret set DB_PASSWORD --env production

# List and delete
gh secret list
gh secret delete API_TOKEN

Tip: gh secret list shows names and dates, never values — a secret cannot be read back after being saved, it can only be overwritten. If you lose the value, generate a new one and rotate.

Best practice: OIDC instead of long-lived keys

Classically, deploying to the cloud (AWS, Azure, GCP) meant keeping long-lived keys in secrets. That is a risk — such a key, if leaked, works for months. OIDC (OpenID Connect) eliminates the problem: GitHub issues a short-lived identity token that the cloud exchanges for temporary permissions. No fixed keys in the repo.

permissions:
  id-token: write   # lets the workflow request an OIDC token
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Login do chmury przez OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-deploy
          aws-region: eu-central-1
      - run: ./deploy.sh

On the cloud side you configure a trust policy that trusts tokens from a specific repo and branch. The token lives for minutes, so even if intercepted it expires quickly.

Pipeline security rules

RuleWhy
Minimal permissionsBy default restrict GITHUB_TOKEN to read
Pin actions to a version/SHA@v4 or a specific SHA, not @main
Per-environment secretsProduction separated from preview
OIDC instead of keysNo long-lived credentials
Watch out for pull_request_targetIt can expose secrets to forks

Limiting token permissions

The default GITHUB_TOKEN can be too powerful. Set the minimum at the top of the workflow:

permissions:
  contents: read

Expand only where a specific job truly needs more (e.g. id-token: write for OIDC, packages: write for publishing).

Summary

Keep secrets in GitHub Actions (encrypted, masked), manage them via gh secret, and rotate them after incidents. Wherever possible, replace long-lived keys with OIDC — short-lived tokens without fixed credentials. Restrict permissions and pin actions to versions. This is the minimum hygiene of secure CI/CD.