Under attack? Get incident response help

info@angelforce.eu
Secure development

A one-week GitHub Actions hardening plan for dev teams

Five daily tasks for GitHub Actions: token permissions, action audits, script injection, secrets handling and workflow ownership.

Also in:TürkçeDeutschFrançaisEspañolItalianoPolskiNederlandsEesti

Illustration: A one-week GitHub Actions hardening plan for dev teams

Any user with write access to a repository can read every secret configured in it, so the credentials your workflows use should carry the least privilege the job allows. GitHub's secure use reference says the same about the GITHUB_TOKEN: grant it the minimum permissions required. The five tasks below take one day each.

OWASP's Top 10 CI/CD Security Risks names the problem areas this plan touches: inadequate identity and access management (CICD-SEC-2), dependency chain abuse (CICD-SEC-3), poisoned pipeline execution (CICD-SEC-4), insufficient credential hygiene (CICD-SEC-6) and improper artifact integrity validation (CICD-SEC-9).

Monday: reduce what the token can do

Start from the assumption that anyone who can push to the repository can read its secrets. Then shrink what those credentials can reach.

GitHub recommends setting the default GITHUB_TOKEN permission to read-only for repository contents, then raising it for individual jobs. In a workflow file:

permissions:
  contents: read

jobs:
  release:
    permissions:
      contents: write

Go through each workflow and ask which jobs genuinely write anything. Give write access to those jobs and leave the rest on read.

Tuesday: audit the actions you run

List every action used in your workflows, including those called from reusable workflows you maintain. For each one, read the source of the action and of the repository that runs it.

GitHub's guidance on auditing secrets is specific about what to look for. Check that secrets are not sent to unintended hosts, and that they are not explicitly printed to log output.

Then apply least privilege to the credentials themselves. Because write access to the repository means read access to its secrets, each credential a workflow uses should be able to do only what that workflow needs. A deploy token that can administer the whole cloud account fails that test.

Wednesday: script injection

Untrusted context values, such as a pull request title, become a problem when they are placed directly into an inline script. GitHub's reference gives two ways to avoid it.

The recommended approach is an action that takes the context value as an argument, because the value is then never used to generate a shell script. If you must keep an inline script, set the expression as an intermediate environment variable:

- name: Check PR title
  env:
    TITLE: ${{ github.event.pull_request.title }}
  run: |
    if [[ "$TITLE" =~ ^octocat ]]; then exit 0; else exit 1; fi

In GitHub's example, a title of a"; ls $GITHUB_WORKSPACE" fails the check and the command is not run. The value is stored in memory and used as a variable, so it does not interact with script generation. GitHub also suggests double-quoting shell variables to avoid word splitting, which is general shell practice.

To find candidates, search for context expressions in your workflows and read each hit in its run: block:

grep -rn 'github.event' .github/workflows

Thursday: secrets and protected environments

Put deployment secrets in environments and require reviewers. A workflow job cannot access environment secrets until a reviewer approves.

GitHub is clear that automatic redaction is not guaranteed, because a secret value can be transformed in several ways. Redaction relies largely on finding an exact match, and it is done by the runner, so a secret is only redacted if a job used it and the runner could access it. Use this table as the review:

Review itemQuestion to answer
Registered secretsIs each one still required, and when was it last rotated?
Structured secretsIs any secret a JSON, XML or YAML blob? Split it into one secret per value.
Derived valuesIs every Base64-encoded, URL-encoded or generated value (a signed JWT, for example) registered as a secret?
Other sensitive dataIs data that is not a GitHub secret masked with ::add-mask::VALUE?
Run logsAfter testing valid and invalid inputs, do secrets appear in output or error logs?
EnvironmentsDo production secrets sit behind required reviewers?

Errors can reach STDOUT and STDERR in ways you did not plan for, so read the logs by hand after testing. If an unredacted secret reaches a run log, delete the log and rotate the secret. Remove secrets that are no longer needed and rotate the rest periodically, which shortens the time a compromised value stays valid.

Friday: ownership of workflows and output

This section is our advice. The sources above do not prescribe it, and it is not attributed to GitHub, OWASP or NIST.

Decide who is allowed to change the workflows that build and release your software, and write it down. If you use CODEOWNERS on your platform, the .github/workflows directory is a sensible path to cover. Then identify which workflow produces your release artifacts, since those are what customers or production systems will trust.

The sources only go part of the way on artifacts. OWASP lists improper artifact integrity validation as CICD-SEC-9, and insufficient logging and visibility as CICD-SEC-10. NIST's Secure Software Development Framework (SP 800-218) provides a common vocabulary that buyers and suppliers can use to discuss secure development. None of them gives a signing recipe, so we leave one out.

Finally, decide who notices when workflow files, secrets or environment approvals change, and how they find out.

Where to start

  1. Set the default GITHUB_TOKEN permission to read-only for repository contents, and add permissions blocks to the jobs that need more.
  2. List every action in use, read its source, and check that it does not send secrets to unexpected hosts or print them to logs.
  3. Search for context expressions inside run: blocks and move them into an intermediate environment variable, or into an action.
  4. Move production secrets into an environment with required reviewers, split structured secrets into single values, register derived values, and rotate anything that has appeared in a log.
  5. Write down who can change the workflows that build release artifacts, and who is told when they change.

If you would like a second opinion on your pipelines, our secure software development work covers CI/CD configuration. A penetration test can show whether a pull request can reach your secrets.

How we can help

Secure software

We build security into software from the first commit, and we build security tools of our own.

Contact us
AngelForce team

Written by the AngelForce team: security consultants with CEH, LPT Master and CISA certifications who test, monitor and defend systems for organisations across Europe and Türkiye.

Written with AI assistance from public sources listed below, and reviewed by the AngelForce team before publication.

Sources

  1. docs.github.com/en/actions/reference/security/secure-use
  2. owasp.org/projects/top-10-cicd-security-risks
  3. csrc.nist.gov/pubs/sp/800/218/final

Articles about Secure development

Tell us what you need to protect

Describe your environment and what you want to achieve. We will reply by email.

Contact us
info@angelforce.eu

AngelForce OÜ · Tallinn, Estonia