GitHub Actions Inspector
Detect security issues in GitHub Actions workflows: unpinned actions, exposed secrets, and dangerous triggers
Enter your GitHub Actions workflow YAML to scan for security vulnerabilities and misconfigurations
Detecting Security Issues in GitHub Actions Workflows
GitHub Actions workflows execute arbitrary code in response to repository events — pull requests,
pushes, issue comments, and scheduled triggers. This execution model creates a significant attack
surface: unpinned third-party actions can be hijacked via tag mutation, secrets can leak through
script injection, and dangerous triggers like pull_request_target can grant write
permissions to untrusted code from forks. The GitHub Actions Inspector scans your workflow YAML
files to detect these security vulnerabilities before they enable supply chain attacks or credential
exposure.
Each finding includes the specific workflow file location, severity classification, and concrete remediation steps — such as pinning actions to SHA commits, restricting permissions to minimum required scopes, and avoiding dangerous expression interpolation in run scripts. All analysis happens entirely in your browser with zero data transmission.
Unpinned Actions and Supply Chain Risks
Third-party GitHub Actions referenced by tag (e.g., uses: actions/checkout@v4) are
vulnerable to tag mutation attacks. If a maintainer's account is compromised, the attacker can
point the v4 tag to malicious code that executes in every workflow using that action:
- Tag references:
@v4,@main,@latest— mutable and exploitable - SHA pinning:
@a1b2c3d4...— immutable, guarantees exact code version - First-party actions: Actions from
actions/org have lower but non-zero risk
The inspector flags all unpinned action references and recommends SHA-pinned equivalents. For commonly used actions, it provides the specific commit SHA for the referenced version tag to make the fix trivial.
Script Injection via Expression Interpolation
GitHub Actions expressions like ${{ github.event.issue.title }} are interpolated
directly into the workflow YAML before execution. If these expressions appear inside
run: blocks, an attacker who controls the interpolated value can inject arbitrary
shell commands:
- Issue titles and body content controlled by any GitHub user
- Pull request titles, branch names, and commit messages from fork contributors
- Discussion comments and review body text
The inspector detects expression interpolation in run: scripts and recommends
safer patterns — passing values through environment variables (env:) where shell
escaping applies, or using actions/github-script for safe string handling.
Dangerous Triggers and Permission Escalation
Certain workflow triggers grant elevated permissions that, when combined with external input, create critical vulnerabilities:
-
pull_request_target: Runs in the context of the base branch with write permissions and access to secrets — even for PRs from forks. If the workflow checks out the PR's head ref and executes its code, an attacker gains full repository access. -
workflow_run: Inherits the triggering workflow's context and can access secrets, creating indirect privilege escalation paths. -
Overly broad permissions: Workflows with
permissions: write-allor no permissions block (defaults to write) grant unnecessary access that amplifies any vulnerability in the workflow.
The inspector flags dangerous trigger configurations and recommends minimum-privilege permission blocks that restrict each workflow to only the scopes it actually needs.
Code Examples
Vulnerable vs Hardened GitHub Actions Workflow
# VULNERABLE: Multiple security issues
name: CI
on: [pull_request_target] # Dangerous trigger
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # Unpinned
with:
ref: ${{ github.event.pull_request.head.sha }} # Checks out fork code!
- run: echo "PR title: ${{ github.event.pull_request.title }}" # Injection
---
# HARDENED: Security best practices
name: CI
on: [pull_request]
permissions:
contents: read
pull-requests: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Log PR info safely
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "PR title: $PR_TITLE" # Via env var, not interpolation Frequently Asked Questions
What security issues does the GitHub Actions Inspector detect?
The inspector detects excessive permissions (write-all, dangerous write scopes), hardcoded secrets in run scripts (passwords, API keys, AWS keys, GitHub tokens), unpinned third-party actions (not using SHA pinning), dangerous triggers (pull_request_target, workflow_run), and script injection vulnerabilities from user-controlled GitHub context expressions.
How is the security score calculated?
The score starts at 100 and deducts points based on finding severity: Critical issues (exposed secrets in scripts) deduct 25 points, High issues (unpinned actions, excessive permissions, dangerous triggers, injection risks) deduct 15, Medium issues (dangerous write scopes) deduct 8, and Low issues deduct 3. Grades map to: A (90+), B (75+), C (60+), D (40+), F (below 40).
Why is SHA pinning important for GitHub Actions?
When you reference an action by tag (e.g., actions/checkout@v4), a malicious maintainer or compromised account can move that tag to point to different code. Pinning to a full SHA (40-character commit hash) ensures you always run the exact code you reviewed. Use Dependabot or Renovate to automate SHA updates when new versions are released.
Why is pull_request_target flagged as dangerous?
Unlike pull_request, the pull_request_target trigger runs in the context of the base branch with write permissions and access to secrets. If the workflow checks out the PR's head ref and executes its code, an attacker can submit a malicious PR that exfiltrates secrets or modifies your repository. Use pull_request trigger instead when possible.
What is script injection in GitHub Actions?
Script injection occurs when user-controlled GitHub context values (like issue titles, PR bodies, or comment text) are interpolated directly into run scripts using ${{ }}. An attacker can craft a PR title or issue body containing shell commands that execute in your workflow. Use environment variables to pass these values safely.
Why should I avoid write-all permissions?
The write-all permission grants the workflow token write access to all repository scopes (contents, packages, deployments, etc.). If any step is compromised — through a supply chain attack on a third-party action or injection vulnerability — the attacker gains full write access to your repository, packages, and deployments. Always use granular permissions per job.
Are official GitHub actions (actions/*) exempt from SHA pinning?
The inspector treats actions from the official 'actions/' and 'github/' organizations as lower risk when pinned to a tag, since GitHub maintains these. However, for maximum security in production workflows, SHA pinning is still recommended even for official actions to prevent any theoretical compromise of the GitHub Actions organization.
How should I handle secrets in GitHub Actions workflows?
Never hardcode secrets directly in workflow YAML files. Use GitHub Secrets (Settings → Secrets and variables → Actions) and reference them as ${{ secrets.MY_SECRET }}. Pass secrets to steps via environment variables rather than directly in run scripts. For OIDC-compatible services, use the id-token permission with federated credentials instead of long-lived secrets.
Does the inspector support multi-job workflows?
Yes. The inspector analyzes the entire workflow file including all jobs and their steps. Permissions are checked at both workflow level and individual job level. Each job's steps are scanned for unpinned actions, exposed secrets, and injection vulnerabilities independently.
Is my workflow file sent to any server?
No. All analysis happens entirely in your browser using JavaScript. Your GitHub Actions workflow files — which may contain sensitive information like secret names, internal infrastructure details, and deployment configurations — never leave your device. No data is stored, logged, or transmitted.