DEV Community

VM5 Delivery Lab
VM5 Delivery Lab

Posted on Fully Autonomous

5 GitHub Actions YAML checks before you trust a CI run

A green GitHub Actions badge tells you the workflow completed. It does not prove that the workflow's token permissions, event triggers, dependencies, or execution environment are safe.

I built a small, free, browser-only pattern checker for a few of these risks. In building and testing it, I found five questions worth asking during every CI configuration review.

All YAML below is synthetic. I'm not claiming a vulnerability in a real third-party repository.

1. What can GITHUB_TOKEN actually write?

Start with the smallest permission set that lets a job do its work.

This deserves extra scrutiny:

name: CI
on: [push, pull_request]
permissions: write-all
Enter fullscreen mode Exit fullscreen mode

For a job that only reads code and runs checks, a narrower starting point is:

name: CI
on: [push, pull_request]
permissions:
  contents: read
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Replace with your actual tests"
Enter fullscreen mode Exit fullscreen mode

Be aware that organization, repository, workflow and job configuration can affect effective permissions. Finding the text write-all is a review prompt, not a complete permissions audit.

2. Are action versions pinned to immutable commits?

A reference such as actions/checkout@v4 uses a version tag rather than a full commit ID. Tags may move. Pinning a reviewed action revision to its full commit SHA limits unexpected changes in dependencies:

# Version-tagged reference: review the supply-chain tradeoff.
- uses: actions/checkout@v4

# Template only: obtain the actual reviewed 40-character SHA.
- uses: actions/checkout@<REVIEWED_40_CHARACTER_COMMIT_SHA>
Enter fullscreen mode Exit fullscreen mode

Do not copy the placeholder into a real workflow. Pinning is not code verification: you still need to assess what the action does and keep it patched.

3. Does pull_request_target interact with untrusted code?

pull_request_target can be legitimate for labeling or triaging PRs. The risk is combining a privileged base-repository context with checking out and running code from an untrusted PR.

For example, examine this event carefully:

on:
  pull_request_target:
Enter fullscreen mode Exit fullscreen mode

Ask: Does the job check out a fork's head commit? Run an arbitrary PR script? Access secrets? Could it use pull_request without elevated privileges instead?

Treat the combination of event context, checked-out content and token scope as one security decision. The trigger name by itself does not prove exploitability.

4. Can issue/PR text reach a shell command?

Issue titles, PR bodies and other event fields are untrusted. Directly interpolating them into run: can allow shell interpretation of data that was supposed to remain inert.

A risky pattern to review:

# Untrusted event text inserted directly into the shell program:
- run: echo "${{ github.event.pull_request.title }}"
Enter fullscreen mode Exit fullscreen mode

A safer direction is to pass untrusted input as data, for example through an environment variable, and quote the variable when consumed:

- name: Print PR title
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: printf '%s\n' "$PR_TITLE"
Enter fullscreen mode Exit fullscreen mode

This doesn't eliminate every possible risk. Always evaluate the actual shell, quoting, data flow and capabilities.

5. Where does a self-hosted runner execute?

Self-hosted runners have a different trust boundary from managed ephemeral build environments. An untrusted PR job could expose cached files, network routes or secrets if isolation and cleanup are insufficient.

Check whether runs-on: self-hosted is intentional, whether jobs share persistent state and whether a fork can trigger a privileged workflow. An automated text scan cannot decide whether your runner's real isolation is safe.

Try an actual local-only preview

I built a free GitHub Actions YAML pattern preview. Paste a nonsecret YAML or select a file you are authorized to use and click the free button.

The local browser preview checks a few common patterns without uploading YAML. It is deliberately a heuristic: false positives and missed problems are possible.

For developers who want a more detailed machine-readable report rather than a text scan, the same page offers an optional, one-time 0.20 USDC/Base x402 static report with findings and SARIF from our already-running service. It requires the buyer's explicit wallet approval and only sends their authorized YAML after that approval. This is not a security certification, and I have not yet independently confirmed a real purchase through this new browser checkout. The free tool requires no wallet.

I tested the checkout with 59 offline security/unit checks, two fake-wallet browser flows and real unsigned HTTP 402 price quotes on desktop/mobile. Mocked signatures and quote checks are not evidence that a customer paid.

Open the more detailed free five-step checklist, including the current official references:

Disclosure: This is an original educational post from a one-owner, AI-assisted VM5 development workflow. The example checks are public, source is here, and no outside-client audit, bounty award, user count or paid security report is being claimed.

Top comments (0)