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.4.tar.gz (7.9 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.4-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: gh_prompt_scan-0.1.4.tar.gz
  • Upload date:
  • Size: 7.9 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.4.tar.gz
Algorithm Hash digest
SHA256 0378e60c0cdb13ed627fd75f85d2241497a14f5f9d5565cc2aef26dc10c38cd3
MD5 587a3a285443afb4631f8ea68c6dfa73
BLAKE2b-256 1dd5ea760a7b4008d9b409d88ab67118e91cd347e911ca0b8a6b2ce8197b380c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: gh_prompt_scan-0.1.4-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.4-py3-none-any.whl
Algorithm Hash digest
SHA256 7d417f238193ab5754df1e73de1833e757c110f7ffd492f93b68ced0d7cf2705
MD5 dbc7950f53515ec82f4146b1c2897311
BLAKE2b-256 9ad585a5e6897df65543990d6e9d31b67ce56f32f0e0ede2f5731f38897d7fae

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