Skip to content
GitHubGHSA-jxrq-hq57-gwwm

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

Critical9.1Published Aug 8, 2026

### 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 (`:...

GitHub advisory

Affected versions

PackageAffectedFixed in
github/gh-aw
Product
< v0.86.1v0.86.1
Details and references

### 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 }`.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:L
Severity from
GitHub (reviewed advisory)
Weakness
CWE-863, CWE-915

More GitHub advisories

All GitHub

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.