Skip to main content

Detect prompt injection risks in AI-integrated GitHub Actions

Project description

gh-prompt-scan

A static analyzer that finds prompt-injection and related vulnerabilities in AI-integrated GitHub Actions workflows.

gh-prompt-scan scans the workflow YAML in a repository's .github/workflows/ directory and flags patterns where untrusted, attacker-controlled input can reach a shell command, an AI agent, or the runner — the class of bugs behind "pwn request" and prompt-injection attacks in CI pipelines.

It is designed to run as a CI gate: it exits non-zero when it finds issues, so a build can fail on a vulnerable workflow.

gh-prompt-scan is a linter for common dangerous patterns, not a proof of safety. A clean report does not guarantee a workflow is secure. See Limitations.


Why this exists

Workflows triggered by events like pull_request_target, issue_comment, or issues run with the base repository's permissions and secrets, but their input (issue titles, PR bodies, comments, branch names) is controlled by anyone on the internet. When that untrusted input is interpolated into a shell command or fed to an AI agent that can run tools, an attacker can inject commands or instructions. gh-prompt-scan looks for these data-flow patterns automatically.

The threat-vector taxonomy (TV4–TV7) is based on the Heimdallr research paper (arXiv:2605.05969).


What it detects

Vector Severity What it flags
TV4 CRITICAL An attacker-controlled context expression (e.g. github.event.issue.title) used directly inside a run: block, where GitHub interpolates it before the shell runs — classic command injection.
TV5 CRITICAL An AI step receives attacker-controlled input and a step output later flows into a shell command, so model output can drive shell execution.
TV6 CRITICAL A pull_request_target workflow that checks out the PR head (github.head_ref / pull_request.head.*), giving attacker-controlled code access to the privileged runner context.
TV7 MEDIUM An AI step reachable by any external user with no actor guard, allowing repeated triggering to exhaust your AI API quota.

Severity reflects impact: CRITICAL findings can lead to code execution or secret exposure; MEDIUM findings are abuse/cost risks.


Installation

Requires Python 3.10+.

git clone https://github.com/panther-0707/gh-prompt-scan.git
cd gh-prompt-scan
python -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate
pip install -e .

This installs the gh-prompt-scan command (via [project.scripts] in pyproject.toml). Dependencies: pyyaml, click.


Usage

Scan the current repository:

gh-prompt-scan scan --path .

Scan a different repository:

gh-prompt-scan scan --path /path/to/repo

Show the version:

gh-prompt-scan version

Exit codes

Code Meaning
0 No findings (scan clean, or no workflow files found)
1 One or more findings reported

The non-zero exit on findings is what lets gh-prompt-scan fail a CI build.

Example output

Scanning 2 workflow file(s) ...
[CRITICAL] TV4 - .github/workflows/triage.yml step 1
Message: Attacker controlled 'github.event.issue.title' which was found directly in run: command
Fix: Pass untrusted values via env: variables instead of ${{ }} in run: blocks

Running gh-prompt-scan in CI

Add a workflow that runs gh-prompt-scan on every push and pull request. Because the scan only reads your workflow files, run it on the safe pull_request trigger:

name: gh-prompt-scan
on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -e .
      - run: gh-prompt-scan scan --path .

The job fails automatically if gh-prompt-scan reports any findings.


How it works

gh-prompt-scan runs a small pipeline over each workflow file:

  1. Loader finds *.yml / *.yaml files under .github/workflows/.
  2. Parser loads the YAML into typed objects, normalizing the on: field (which YAML parses as the boolean True, and which may be a string, list, or mapping).
  3. Trigger classifier decides whether the workflow is reachable by untrusted users. Workflows with only "safe" triggers (push, schedule, workflow_dispatch) are skipped.
  4. AI detector identifies steps that invoke a known AI action or set an AI provider API key.
  5. Taint analysis walks the steps of each externally-triggerable job and emits findings for TV4–TV7.

Limitations

gh-prompt-scan catches common patterns; it is not exhaustive and can produce both false positives and false negatives. Known gaps in the current version:

  • AI-step identification has a narrow gap. A step is recognized as an AI step if it uses a known AI action or sets an AI API key directly on the step. A generic step (a plain run: or an unknown action) that qualifies as an AI call only because it inherits a key from job- or workflow-level env may not be recognized as one. Attacker input flowing through inherited job/workflow env into a known AI step is tracked.
  • TV5 is a heuristic. It flags when an AI step received tainted input and any later step references a steps.*.outputs.* value in a shell command, without confirming the referenced output belongs to the AI step. It may over- or under-report.
  • Renamed AI-key env vars are matched by name only. is_ai_step compares env key names against a known list; a custom-named key whose value references an AI secret (e.g. MY_KEY: ${{ secrets.OPENAI_API_KEY }}) won't be detected.
  • step numbers are positions, not file line numbers. A finding's step index is the step's position within its job.
  • Detection uses substring matching on expression strings, so unusual syntax (such as index form ['title']) may not match.
  • ATTACKER_SOURCES is a curated list, not a complete enumeration of every untrusted GitHub context.

Treat gh-prompt-scan's output as a prompt to review a workflow by hand, not as a security guarantee.


Development

Run the test suite:

pytest -v

Contributions that add test cases for new threat vectors or edge cases are especially welcome.


License

Released under the MIT License. See LICENSE for details.


Acknowledgements

The threat-vector taxonomy is based on the Heimdallr research paper (arXiv:2605.05969).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gh_prompt_scan-0.1.3.tar.gz (7.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gh_prompt_scan-0.1.3-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

Details for the file gh_prompt_scan-0.1.3.tar.gz.

File metadata

  • Download URL: gh_prompt_scan-0.1.3.tar.gz
  • Upload date:
  • Size: 7.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for gh_prompt_scan-0.1.3.tar.gz
Algorithm Hash digest
SHA256 4ace58724ff4e41f087ec1288291015bdddd2863814efa8463ea67772807b8fa
MD5 f77a8c1da45a40a96a49d365946fab03
BLAKE2b-256 e37eb39682285ad5f52bf86396d2bd8b08131e6a37e50d1deb38cd1ba1a668e4

See more details on using hashes here.

File details

Details for the file gh_prompt_scan-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: gh_prompt_scan-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 9.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for gh_prompt_scan-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 a439c9f53242c0eee4358fcd581f5eb36fba3f0d84535b647a311dd9afe22821
MD5 2df47647628d2f87216d73c54373c532
BLAKE2b-256 74f819fd92c2347c96d18f405520f1115cf040675e0860e4e5708e590699411a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page