Skip to content

gh-aw: unauthenticated prompt-injection to code execution in the shipped ai-moderator workflow

Moderate
pelikhan published GHSA-2cwf-x2h8-mqj5 Aug 7, 2026

Package

actions github/gh-aw (GitHub Actions)

Affected versions

<= 0.82.4 (all released versions; verified on main at commit 966ccb8, 2026-07-12)

Patched versions

v0.86.1

Description

Summary

The shipped ai-moderator.md workflow, which runs on github/gh-aw itself,
combines four settings that together hand an unauthenticated GitHub user code
execution in the agent job. The agent job holds GITHUB_TOKEN and the engine API
keys. The compiler emits no warning for this combination. This is live on
github/gh-aw right now and reachable by any GitHub account with no special
access. It is the most immediately exploitable of the issues I found in this
review of gh-aw.

I verified this against current main at commit 966ccb8 (2026-07-12). It was
first verified at bdfdb8e (2026-06-24, ~v0.80.9); the intervening commits did
not change any of the four ingredients.

Details

gh-aw treats issue/PR/comment content from arbitrary GitHub users as
attacker-controlled, and its compiler normally env-indirects those expressions
out of shell contexts. This workflow disables the controls that enforce that
model. The four ingredients:

  1. roles: all removes the activation gate. needsRoleCheck in
    pkg/workflow/role_checks.go:348-353 returns false when roles is ["all"],
    so the compiled lock has no check_membership step. In the compiled
    ai-moderator.lock.yml the activation condition is just
    rate_limit_ok && skip_roles_ok && skip_bots_ok. The workflow triggers on
    issues: [opened], issue_comment: [created], and pull_request: [opened]
    with forks: "*", all reachable by an external account.

  2. tools.github.min-integrity: none defeats the author-association filter that
    public repos otherwise default to approved
    (actions/setup/js/determine_automatic_lockdown.cjs:57); a configured value
    is never overridden (:61-67). threat-detection: false removes the
    injection pre-screen. The prompt then instructs the agent to fetch raw
    content: "Use the GitHub MCP server tools to fetch the original context ...
    unsanitized content directly from GitHub API" and "Do NOT use the
    pre-sanitized text from the activation job".

  3. engine: codex compiles to codex exec --dangerously-bypass-approvals-and-sandbox
    (pkg/workflow/codex_engine.go:238), so the agent auto-executes shell with
    no approval. The other auto-executing engines are equivalent: gemini emits
    --yolo (pkg/workflow/gemini_engine.go:181), copilot emits
    --allow-all-tools (pkg/workflow/copilot_engine_tools.go:78). The AWF
    firewall around the engine filters network egress only, not local execution.

  4. An issue/PR/comment trigger reachable by arbitrary users (above).

There is no compile-time diagnostic for open activation + min-integrity: none

  • an auto-executing engine.

PoC

I reproduced the full chain on my own repo on a real runner: an external issue
activates the agent with no membership check, the raw issue body is built into
the prompt, and the auto-approved agent runs shell via the engine and posts back
to the issue. I used the gemini engine for the live run because I had a free key
rather than codex credits; the mechanism is engine-independent, and the shipped
ai-moderator uses codex. Markers were benign (PWNED_*).

Impact

Any GitHub account can drive shell execution inside the agent job on a repo
running this pattern. Containment on most adopters: --exclude-env strips
engine/MCP keys, the agent token is read-only, egress is allowlisted. The last
step is XPIA (the model must follow the payload). Every control that would
normally make that unreliable is disabled by this configuration, the payload
surface is the whole body/diff, and the prompt instructs the model to trust raw
content.

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:L (7.5). Up to 9.1 (Critical) where
the agent has an arbitrary-shell tool.

Suggested fix

Fail the build, or emit a hard warning, when open activation is combined with
min-integrity: none/unapproved and an auto-executing engine. Stop shipping
samples that instruct the agent to fetch unsanitized content. Reconsider
bypassing the engine sandbox for agents that read untrusted input.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Changed
Confidentiality
Low
Integrity
Low
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:L

CVE ID

No known CVE

Weaknesses

Improper Control of Generation of Code ('Code Injection')

The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment. Learn more on MITRE.

Initialization of a Resource with an Insecure Default

The product initializes or sets a resource with a default that is intended to be changed by the administrator, but the default is not secure. Learn more on MITRE.

Credits