Skip to content

Q-workflows (qlaude/qopilot/qodex): disable-xpia-prompt + Bash + npm registry creates unguarded supply-chain attack path — two-h [Content truncated due to length] #28056

@szabta89

Description

@szabta89

Summary

The qlaude, qopilot, and qodex workflows explicitly disable XPIA (Cross-Prompt Injection Attack) protection via disable-xpia-prompt: true while simultaneously granting Bash tool access, including registry.npmjs.org in the firewall allowlist, and injecting user-controlled text verbatim into the agent's system prompt as "Triggering Content." The q-assistant.md shared component additionally instructs the agent to proactively fetch and process the content of an arbitrary referenced issue via issue_read MCP call. Threat detection runs post-agent and is non-blocking (GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"). Together, these create a complete, unguarded path from a prompt-injection payload to npm install of an attacker-controlled package with lifecycle script execution at runner level. A two-hop variant requires zero attacker write access.

Affected Area

Agent tool-restriction and XPIA defense layer; affected files: qlaude.md, qopilot.md, qodex.md, shared/q-assistant.md, shared/disable-xpia-prompt.md, and their compiled lock files.

Reproduction Outline

Two-hop variant (zero attacker write access required):

  1. Attacker (any GitHub user) creates an issue in the repository with a prompt-injection payload in the body, e.g., an instruction to run npm install <malicious-package>.
  2. A write-access user (legitimate) comments /qlaude please review issue #<attacker-issue-number> on any issue.
  3. gh-aw activates the qlaude workflow; compute_text.cjs sanitizes shell/template injection but does not neutralize prompt-injection instructions within the text.
  4. The agent job starts with XPIA protection disabled, Bash listed in --allowed-tools, and registry.npmjs.org in the firewall domain allowlist.
  5. Following q-assistant.md's built-in instruction, the agent fetches the attacker's issue via issue_read MCP call and processes the attacker-controlled content with no XPIA framing to distinguish adversarial instructions from legitimate ones.
  6. If the model follows the injected instruction, it calls Bash to execute npm install <malicious-package>; lifecycle scripts run with runner-level permissions. The agent then creates a PR via the create-pull-request safe-output potentially modifying .github/workflows/ files.

Observed Behavior

No technical control blocks npm install once the model calls Bash. The soft instruction "Never execute untrusted code" in q-assistant.md is a model-level prompt guideline with no enforcement mechanism. Removing the XPIA protection prompt eliminates the primary framing that would otherwise alert the model it may be under injection. Threat detection runs after the agent job completes and cannot undo an already-executed npm install.

Expected Behavior

The combination of disable-xpia-prompt: true, Bash tool access, and package registry connectivity should be rejected at the configuration level, or threat detection should be a hard gate for any Bash-capable workflow. At minimum, documentation for disable-xpia-prompt should explicitly warn that combining it with shell/package-registry access removes the last technical control between a prompt-injection payload and arbitrary code execution.

Suggested mitigations (from static analysis):

  1. Remove Bash from Q-workflow tool allowlists — workflow optimization does not require shell execution.
  2. Re-enable XPIA protection, or add explicit data-context framing (e.g., XML-tagged block) for content fetched via issue_read.
  3. Remove registry.npmjs.org from Q-workflow network allowlists if Bash is retained.
  4. Change GH_AW_DETECTION_CONTINUE_ON_ERROR to "false" for Bash-capable workflows so a detection finding blocks safe-output dispatch.
  5. Restrict q-assistant.md's issue-reading instruction to the triggering issue only, closing the two-hop attack surface.

Security Relevance

The two-hop variant reduces the attacker prerequisite from write access to zero — any GitHub user who can create an issue can plant the payload. Successful exploitation enables npm package lifecycle script execution with runner-level permissions, potential secret exposure from the runner environment, and PR-based workflow injection for persistence across future runs.

Additional Context

The disable-xpia-prompt: true feature is documented as deliberately disabling the default XPIA prompt, but no documentation warns that combining it with Bash tool access and package registry connectivity creates an unguarded supply-chain attack surface. If this combination is an intentional design decision (e.g., Q-workflows are meant for fully trusted operators only), that assumption should be explicitly documented and enforced — for example, by restricting the disable-xpia-prompt feature to configurations that do not include shell tools or unrestricted network access.

gh-aw version: v0.68.3

Original finding: https://github.com/githubnext/gh-aw-security/issues/1919

Generated by File Issue · ● 465.1K ·

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions