1. Set explicit, minimal token permissions
A workflow can use GITHUB_TOKEN even when it is not written explicitly in the YAML. Declare a restrictive top-level permissions block, then add a narrower write permission only to the job that requires it. A pull-request reviewer may need pull-requests: write; a build job often needs only contents: read.
Review permissions together with the trigger. The same write scope has a different risk when a workflow runs only on a protected branch versus when an outside contributor can trigger it.
GitHub reference: Secure use reference
2. Pin third-party Actions to full commit SHAs
Tags and branches can move. A full-length commit SHA is GitHub's immutable reference for an Action release. Verify that the SHA belongs to the expected repository, keep the readable version in a comment, and use Dependabot or another deliberate process to propose updates.
- uses: owner/action@0123456789abcdef0123456789abcdef01234567 # v3.2.1Pinning reduces dependency-substitution risk; it does not prove that the pinned code is safe. Review what an Action reads, which credentials it can access, and where it sends data.
GitHub reference: Using third-party Actions securely
3. Keep untrusted code out of privileged triggers
Use pull_request for checks that build or inspect pull-request code. Be especially cautious with pull_request_target and workflow_run: they can run with privileged context. Do not check out or execute an untrusted pull-request head in a job that can access secrets, write tokens, shared privileged caches, or internal networks.
If you need a privileged follow-up, separate it from the untrusted build. Pass only the minimum reviewed data across that boundary and treat uploaded artifacts as untrusted input.
GitHub reference: Securely using pull_request_target
4. Do not interpolate untrusted context into shell scripts
Pull-request titles, bodies, branch names, issue content, commit metadata, and other event fields can contain attacker-controlled strings. Putting an expression such as ${{ github.event.pull_request.title }} directly inside run can turn data into shell syntax.
Prefer a purpose-built Action that accepts the value as an input. When an inline script is necessary, assign the expression to an environment variable and quote the shell variable when it is used.
- name: Check pull request title
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
printf '%s\n' "$PR_TITLE" | ./scripts/check-titleGitHub reference: Script injections
5. Prefer short-lived cloud credentials
For supported cloud providers, GitHub Actions can use OpenID Connect to exchange a signed workload identity for short-lived credentials. Scope the provider trust policy to the repository, branch, tag, or protected environment that should be allowed to assume the role. Avoid a trust rule that accepts every workflow in an organization.
OIDC removes a long-lived cloud secret from GitHub, but it does not make the resulting session harmless. Keep the assumed cloud role least-privileged and set a short session duration.
GitHub reference: OpenID Connect
6. Put production behind environments and review
Use GitHub environments to separate development, staging, and production credentials. Add deployment-branch restrictions and required reviewers where your plan and process support them. Keep the approval step independent from the contributor whose change triggered the deployment.
A manual approval is a control only when the reviewer can see what will deploy. Link the approval to the commit, artifact digest, change summary, and relevant security results.
7. Require review for workflow and ownership changes
Changes under .github/workflows/, custom Action directories, deployment scripts, and CODEOWNERS can alter the security boundary. Assign knowledgeable owners to those paths and require review through branch protection or rulesets.
Review both the YAML and the code it delegates to. A harmless-looking one-line workflow change can invoke a modified script, reusable workflow, container image, or build target elsewhere in the repository.
8. Generate—and verify—artifact provenance
Artifact attestations link a build output to its repository, workflow, commit, and build identity. Generate attestations for artifacts consumers will download or deploy, then add verification to the consumption or deployment path. An attestation establishes provenance; it does not establish that the code is vulnerability-free.
GitHub reference: Artifact attestations
A safer pull-request review baseline
This example keeps the trigger unprivileged, declares only the permissions it needs, avoids persisting checkout credentials, and shows where immutable Action SHAs belong. Replace each placeholder with a verified full commit SHA from the Action's repository.
name: Review infrastructure changes
on:
pull_request:
paths:
- '**/*.tf'
- '**/*-policy.json'
permissions:
contents: read
pull-requests: write
jobs:
security-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<full-commit-sha>
with:
persist-credentials: false
- uses: tryflare-ai/pr-security-check@<full-commit-sha>
with:
token: ${{ secrets.FLARE_API_KEY }}
fail-on: none
The Flare PR check sends relevant infrastructure diffs and repository or pull-request metadata to Flare's hosted service. It can post review findings back to the pull request. Review the data handling and execution boundaries before adding it to a repository.
The five-minute review checklist
- Does every job have only the token permissions it needs?
- Are third-party Actions pinned to verified full commit SHAs?
- Can an untrusted fork, event field, artifact, or cache reach a privileged job?
- Are cloud credentials short-lived and narrowly scoped?
- Do production environments require the right branch and reviewer?
- Do workflow, custom Action, and ownership changes require review?
- Can consumers verify the provenance of released artifacts?
- Are security findings treated as review input rather than proof of safety?
Review the infrastructure diff
Add contextual review without connecting a cloud account.
Flare's open-source PR security check reviews Terraform, CloudFormation, IAM policy, Kubernetes, and Helm changes. It posts file-referenced findings and suggested fixes in the pull request, while your team keeps the final decision.