Diff it

CI and workflow review

How to review GitHub Actions changes safely

Review workflow triggers, token permissions, script injection, action dependencies, and skipped checks before merging a GitHub Actions change.

What should I check in a GitHub Actions pull request?

Review a GitHub Actions change by tracing five things: what triggers the workflow, whose code it executes, which credentials that code can use, how external input reaches commands, and which checks actually run. A small YAML edit can change any of these without touching the application.

Start with on, each job’s permissions, checkout references, and if conditions. Then follow the scripts and actions those jobs invoke. Read enough surrounding code to explain the effect of the changed lines. Use the questions below to turn that reading into a review decision.

Is pull_request_target safe to use?

It can be appropriate for privileged PR automation, such as labeling, when untrusted code is never executed. It is dangerous to use that privileged context to build or test code supplied by an untrusted pull request. GitHub’s pull_request_target security guidance explains this distinction and the protections available.

For testing PR code without privileged access, start with pull_request. If a change introduces pull_request_target, ask why it needs the base repository’s trusted context. Follow every checkout, fetch, or downloaded artifact to the command that consumes it. A build script, dependency installation, or test command can execute PR-controlled code too.

Record both the event and the source revision being executed. “The workflow comes from the base repository” is insufficient if a later step loads and runs the PR’s files. Consult the repository’s event policies as well; the event name alone does not establish the protections in effect.

Which GITHUB_TOKEN permissions does CI need?

Grant each job only the scopes its operations require. A job that reads source does not need write access merely because a separate job publishes a release. Identify the operation behind every new write permission and keep that permission close to the job that needs it.

Read workflow-level and job-level permissions together. GitHub calculates access from repository or organization defaults, then workflow and job settings, with further restrictions for fork PR events. When a permissions map specifies a scope, unspecified scopes become none. Check the permissions reference instead of assuming that all repositories have the same defaults.

Also inventory explicit secrets and other credentials passed to commands or actions. A narrow GITHUB_TOKEN does not describe the access granted by a separate deployment token.

Can a pull request title cause script injection?

Yes. A PR title is user-controlled text. When a workflow inserts it directly into an inline run expression, GitHub substitutes its value before the shell executes the generated script. Quotation marks in the template do not make that substitution safe. GitHub documents the mechanism in its script injection guide.

These Bash step examples are illustrative, not an executed test or a report of a real incident. The first inserts the title into shell source:

# Unsafe: title becomes part of the shell script
- name: Print PR title
  shell: bash
  run: printf '%s\n' "${{ github.event.pull_request.title }}"

Pass the title through an environment variable instead, then expand that variable as a quoted argument:

- name: Print PR title
  shell: bash
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: printf '%s\n' "$PR_TITLE"

This follows GitHub’s intermediate environment variable guidance. Here, Bash treats the expanded title as data, rather than parsing it as new shell code. Do not feed it back into execution through eval or another generated command. This fixes this input path; review the rest of the workflow separately.

Should GitHub Actions be pinned to a commit SHA?

Pin remote actions to a full commit SHA when you need an immutable revision. A tag can move. Confirm that the SHA belongs to the intended upstream repository and inspect the action change before updating it. GitHub’s third-party action guidance recommends both pinning and source review.

Pinning identifies the code; it does not establish that the code is safe. Review what the action executes, what inputs it accepts, and which credentials its job exposes. Apply the same scrutiny to a newly introduced action as to a newly introduced script.

Can a required check pass without running its tests?

Yes. A job skipped by its if condition reports success and does not block merging even when it is required. GitHub documents this in job execution conditions. That differs from skipping an entire workflow through branch or path filters, which can leave required checks pending, as the workflow filter reference explains.

For each changed condition, list the PR cases that should run the check and those that should skip it. Inspect the job details and test output at the reviewed commit. A check name and a green badge do not tell you which assertions executed. Our guide to verifying behavior when CI is green covers the next step: connecting that output to the behavior being changed.

Finish the review with four concrete statements: this workflow runs on these events, executes code from this revision, receives these credentials, and exercises these checks. Link the relevant YAML and run output. If one statement is still unknown, make it an open review question before approving.