Skip to content

Application & API Security • 3 min read

Running Security Scanners as a GitHub Action: Setup, Permissions and Pitfalls

A practical guide to running security scanning in GitHub Actions safely: least-privilege tokens, pinning actions to commit SHAs, fork pull request behavior, SARIF reporting and secret handling.

By CryptoAgile Labs Editorial Team • Published August 2026 • Last updated October 10, 2026

GitHub Actions is a convenient place to run scanners for secrets, vulnerable dependencies and insecure code on every pull request. But a security tool running in your pipeline is also code with access to your repository, so it needs the same care as production code. This guide covers a safe setup and the mistakes that cause real incidents.

A baseline workflow

name: security-scan
on:
  pull_request:
  push:
    branches: [main]
permissions:
  contents: read
  security-events: write   # only if uploading SARIF
concurrency:
  group: scan-${{ github.ref }}
  cancel-in-progress: true
jobs:
  scan:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          persist-credentials: false
      - name: Run scanner
        run: ./scripts/run-scanner.sh --format sarif --output results.sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif

Key choices: an explicit permissions block, a timeout, no persisted credentials, and a concurrency group that cancels superseded runs.

1. Use least-privilege tokens

Every run receives a GITHUB_TOKEN. Declare the permissions the job needs at the top of the workflow and nothing more. A scanner that only reads code needs contents: read. Add security-events: write only when uploading results, and pull-requests: write only if posting comments. Setting the repository default to read-only provides a safety net.

2. Pin third-party actions to a commit SHA

Tags such as @v4 can be moved by the action's maintainer, or by an attacker who compromises the maintainer's account. In March 2025 the widely used tj-actions/changed-files action was compromised and tags were repointed to malicious code that printed secrets into workflow logs. Workflows pinned to a full commit SHA were not affected.

- uses: actions/checkout@<full-40-character-commit-sha>   # v4.x.x

Keep a version comment next to the SHA and let a dependency bot propose updates. First-party actions from GitHub are lower risk but pinning is still good hygiene.

3. Understand pull request triggers

Behavior differs sharply by trigger:

TriggerSecrets availableToken permissionsSafe for untrusted forks?
pull_requestNot for forksRead-only for forksYes
pull_request_targetYes (base repo)Can be writeNo, if it checks out PR code

pull_request_target runs in the context of the base repository with access to secrets. If it checks out and executes code from a fork's pull request, the contributor's code runs with your secrets. Use pull_request for scanning untrusted code. If you need to comment on fork PRs, use a two-stage design: a no-secret workflow produces artifacts, and a separate trusted workflow reads them as data only.

4. Handle secrets carefully

  • Store credentials in repository or environment secrets, never in the workflow file.
  • Do not print secrets or pass them on command lines that appear in logs. Masking is a best-effort feature, not a guarantee.
  • Prefer short-lived credentials via OpenID Connect to your cloud provider over long-lived access keys stored as secrets.
  • Scope environment secrets to protected branches or require reviewers for deployments.

5. Avoid script injection

Expressions like ${{ github.event.pull_request.title }} are substituted into shell scripts before they run. A malicious title can inject commands. Pass untrusted values through environment variables instead:

- name: Safe usage
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: echo "Title: $PR_TITLE"

6. Report findings where developers look

Upload results as SARIF so findings appear as annotations on the changed lines and in the repository's code scanning view. Developers act on feedback in the pull request far more than on a separate dashboard. See API security linting for how to tune which findings block a merge.

7. Manage cost and speed

  • Cache scanner databases between runs.
  • Scan changed files on pull requests and the full repository on a schedule.
  • Set timeout-minutes so a hung scanner cannot consume your quota.

Checklist

  1. Explicit minimal permissions on every workflow.
  2. Third-party actions pinned to commit SHAs.
  3. pull_request rather than pull_request_target for untrusted code.
  4. No secrets in logs; OIDC instead of static cloud keys where possible.
  5. Untrusted event data passed via environment variables.
  6. SARIF upload and a documented process for handling findings.

Sources and further reading

This article is for educational purposes and is not professional security, legal or compliance advice. Spotted an error or an out-of-date detail? Email us a correction. See our editorial policy.