Skip to content

gh-aw: safe-output validator forwards undeclared agent fields to the appliers (scope escape / mass assignment)

Critical
pelikhan published GHSA-jxrq-hq57-gwwm Aug 8, 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

Agent safe-outputs are NDJSON validated by a trusted job before they are
applied. The validator spreads the raw agent item and validates only the
schema-declared fields, so any field the agent adds that the validator does not
declare is forwarded to the applier verbatim. Wherever an applier reads such a
field, the agent controls it. This is a mass-assignment class that yields several
scope escapes, including overwriting another user's comment and retargeting or
closing arbitrary pull requests.

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 safe-outputs refactors in
between changed only Go config-parsing modules and did not alter the field
handling.

Details

In validateItem, the normalized item is built as
const normalizedItem = { ...item }
(actions/setup/js/safe_output_type_validator.cjs:680), which spreads the raw
agent item. The validation loop then iterates only the schema-declared fields
(:692-707) and returns the spread item unchanged for everything else (:713).
Undeclared fields are not stripped; actions/setup/js/collect_ndjson_output.cjs
pushes the item (:330) and writes it to the agent output file (:344) with the
extra fields intact.

Confirmed instances (applier reads a field absent from the validator schema in
pkg/workflow/safe_outputs_validation_config.go):

  • add_comment -> comment_id. Declared on the MCP tool but absent from the
    validator config; reaches issues.updateComment in
    actions/setup/js/add_comment.cjs with a positive-integer check and no
    ownership check, so a steered agent overwrites any comment by id.
    Tool-reachable on any engine because the tool schema exposes comment_id.
  • update_pull_request -> base, state. Reaches pulls.update in
    actions/setup/js/update_pull_request.cjs with no allowed_base_branches gate
    on the update path, so the agent retargets a PR's base branch or closes
    arbitrary PRs.
  • upload_asset -> targetFileName. Used as the destination of
    fs.copyFileSync with no containment
    (actions/setup/js/upload_assets.cjs:131,163), giving a runner filesystem
    write. Writing into .git/hooks or .git/config escalates to code execution
    in the write-token posting job.
  • create_issue -> assignees; create_discussion -> labels;
    close_issue -> state_reason; push_to_pull_request_branch -> diff_size
    (forging 0 skips the max_patch_size guard). All via the raw-NDJSON path.

PoC

For the add_comment case I set comment_id on the NDJSON item and overwrote
another user's comment by id on my own repo, then reverted. For
update_pull_request I retargeted a PR base branch on my own repo, then
reverted. The remaining instances were confirmed by tracing each applier against
the validator config.

Impact

A confused-deputy family: the agent influences state that the safe-output
contract was meant to gate (comment integrity, PR base branch, PR state, runner
filesystem). One fix closes the class.

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:L (High). Suggested vector; adjust per
instance.

Suggested fix

In validateItem, build normalizedItem from the type's declared schema fields
only, instead of spreading { ...item }.

Severity

Critical

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
Low
Privileges required
Low
User interaction
None
Scope
Changed
Confidentiality
Low
Integrity
High
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:L/PR:L/UI:N/S:C/C:L/I:H/A:L

CVE ID

No known CVE

Weaknesses

Incorrect Authorization

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. Learn more on MITRE.

Improperly Controlled Modification of Dynamically-Determined Object Attributes

The product receives input from an upstream component that specifies multiple attributes, properties, or fields that are to be initialized or updated in an object, but it does not properly control which attributes can be modified. Learn more on MITRE.

Credits