GitHub Actions script block injection

Pass external values to actions/github-script as JavaScript data instead of embedding them in code.

Description

The script input to actions/github-script runs as a JavaScript function body. Directly inserting an external value such as an issue title through ${{ }} can break out of a string as the code is constructed and execute other code. Values used in file paths also need separate path restrictions.

Potential impact

  • Injected code may read runner files or alter artifacts.
  • Depending on the job’s token and permissions, it may misuse repository features such as issues and comments or expose secrets.

Remediation

  • Read values as data from context.payload, or pass them through env and read process.env. Do not interpolate them into code strings or evaluate them with eval.
  • Validate the allowed format and scope of file paths and API arguments. Passing a value as data does not make every subsequent use safe.
  • Limit token and secret access, and keep untrusted code out of privileged jobs.

Examples

These examples explicitly grant the permissions needed to create a comment. The first example’s /tmp file read illustrates the risk and fails if that file has not been prepared. Pin actions to a reviewed version or commit.

Before

yaml
name: test-script-run

on:
  issues:
    types: [opened]

permissions:
  contents: read
  issues: write

jobs:
  script-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Run script
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const body = fs.readFileSync('/tmp/${{ github.event.issue.title }}.txt', {encoding: 'utf8'});

            await github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Thanks for reporting!'
            })

            return true;

The issue title is inserted directly into a JavaScript string and a file path. Both code injection and path manipulation need attention.

After

yaml
name: test-script-run

on:
  issues:
    types: [opened]

permissions:
  contents: read
  issues: write

jobs:
  script-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Run script
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Thanks for reporting!'
            })

            return true;

The unnecessary file read is removed and only a fixed comment is posted. If the title is needed, read and validate it as data rather than inserting it into code.

References