← All insights

Security · 8 min read

A GitHub hardening checklist: ten settings that close the common holes

By BizDevHub ·

Most repository compromises don't need a clever exploit. They use a leaked token, an over-permissioned workflow, or a third-party action that changed under you. This checklist covers the settings that close those gaps. Items follow GitHub's own security hardening guide for Actions.

1. Protect the default branch

Require pull requests, at least one approval and passing status checks; block force-pushes and deletion; apply the rules to admins too. See GitHub's docs on protected branches.

gh api -X PUT repos/OWNER/REPO/branches/main/protection --input - <<'JSON'
{
  "required_status_checks": { "strict": true, "contexts": ["ci"] },
  "enforce_admins": true,
  "required_pull_request_reviews": { "required_approving_review_count": 1 },
  "restrictions": null,
  "allow_force_pushes": false,
  "allow_deletions": false
}
JSON

2. Make the workflow token read-only by default

Set the repository or organisation default for GITHUB_TOKEN to read-only, then grant write per job only where needed.

# top of every workflow
permissions:
  contents: read

3. Pin third-party actions to a full commit SHA

A tag like @v4 can be moved. A full-length commit SHA cannot. GitHub's guidance is that pinning to a full-length commit SHA is the only way to use an action as an immutable release.

- uses: actions/checkout@<full-40-char-sha>   # v4.x.x

Let Dependabot keep the SHAs current (see item 6).

4. Use OIDC instead of long-lived cloud keys

Store no AWS_SECRET_ACCESS_KEY in GitHub. Let the workflow exchange a short-lived OIDC token for a role limited to exactly what the job needs. See GitHub's OIDC guide.

permissions: { id-token: write, contents: read }
steps:
  - uses: aws-actions/configure-aws-credentials@<full-commit-sha>
    with:
      role-to-assume: arn:aws:iam::<account>:role/ci-least-privilege
      aws-region: us-east-1

5. Turn on secret scanning and push protection

Push protection blocks a commit containing a recognised credential before it lands in history. Availability for private repositories depends on your GitHub plan.

gh api -X PATCH repos/OWNER/REPO \
  -f 'security_and_analysis[secret_scanning][status]=enabled' \
  -f 'security_and_analysis[secret_scanning_push_protection][status]=enabled'

If a secret has ever been committed, assume it is compromised: rotate it. Deleting the commit does not un-leak it.

6. Enable Dependabot, including for Actions

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: github-actions
    directory: /
    schedule: { interval: weekly }
  - package-ecosystem: npm
    directory: /
    schedule: { interval: weekly }

7. Add CODEOWNERS for workflows and sensitive paths

Changes to CI definitions are changes to what runs with your credentials. Require a review from a named owner.

# .github/CODEOWNERS
/.github/workflows/  @your-org/security-owners
/infra/              @your-org/platform

8. Require two-factor authentication for the organisation

Org settings, Authentication security. Members without 2FA are removed or blocked, which closes the commonest account-takeover path.

9. Restrict which actions can run

In Settings, Actions, General, allow only actions created by GitHub plus an explicit allow-list, rather than any action from anywhere.

10. Treat pull_request_target and deploy secrets with care

How to use this

Work top to bottom; items 1 to 5 deliver most of the risk reduction. Our fixed-price GitHub Security Hardening offer applies all ten across your repositories and gives you a written report of every change.