CVE-2026-66902: Verified Reproduction
CVE-2026-66902: Google::Auth for Perl command injection: external account credentials JSON executable run via ungated system → RCE
CVE-2026-66902 is verified against the affected target. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00323.
What Is CVE-2026-66902?
CVE-2026-66902 is a critical-severity RCE vulnerability. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00323).
CVE-2026-66902 Severity
CVE-2026-66902 is rated critical severity.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
How to Reproduce CVE-2026-66902
pruva-verify REPRO-2026-00323 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00323/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-66902
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
external_account credentials JSON named by GOOGLE_APPLICATION_CREDENTIALS: credential_source.executable.command and credential_source.executable.environment_variables
- Google::Auth
- default()
- DefaultCredentials
- from_env()
- ExternalAccountCredentials::make_creds
- Pluggable
- new
- fetch_access_token
- retrieve_subject_token
- single-argument system($command) via /bin/sh -c
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-66902
Google::Auth for Perl (CPAN distribution Google-Auth, maintained at
GoogleCloudPlatform/google-auth-library-perl) executes a command taken verbatim
from an external_account credentials JSON file through a single-argument
system($command) call. In versions before 0.06 there is no opt-in gate: any
application that builds Application Default Credentials from a configuration it
does not fully control runs the embedded shell command (with full /bin/sh -c
interpretation and attacker-chosen environment variables) with the privileges of
the application process. This is CWE-78 OS command injection leading to arbitrary
OS command execution.
- Package/component: CPAN
Google-Auth(Google::Auth),lib/Google/Auth/ExternalAccountCredentials/Pluggable.pm - Affected versions: < 0.06 (verified at commit
913fb1780202c1ee9dd640c28c01549903f8e23a= fix commit parent; packaged as 0.05) - Risk level: Critical. Any service/workload that consumes a credentials JSON from an untrusted or partially trusted source (mounted config, user-supplied file, CI artifact) executes attacker-chosen shell commands as the application user.
Impact Parity
- Disclosed/claimed maximum impact: arbitrary OS command execution (RCE) in the application process.
- Reproduced impact from this run: arbitrary shell command execution in the
Perl application process. The embedded command used shell output redirection to
write a unique attacker-chosen marker file, and consumed attacker-controlled
environment variables copied from the same JSON (
environment_variablesmap) — proving both full shell interpretation and environment injection. - Parity:
full. - Not demonstrated: nothing material; the claimed impact (code/command execution) was reproduced directly, twice, against the real library entrypoint.
Root Cause
Google::Auth::ExternalAccountCredentials::Pluggable::retrieve_subject_token()
(in versions < 0.06) does the following with zero validation and no opt-in:
- Copies every entry of
credential_source.executable.environment_variablesfrom the credentials JSON into%ENV. - Reads
credential_source.executable.commandand runscapture { system($command) }. Becausesystem()receives a single string, Perl invokes/bin/sh -c, giving the attacker pipes, redirection, command substitution, and all shell metacharacters.
Dispatch reaches this subclass automatically: Google::Auth->default() →
Google::Auth::DefaultCredentials->from_env() reads the JSON named by
GOOGLE_APPLICATION_CREDENTIALS → make_creds() in
ExternalAccountCredentials.pm selects the Pluggable subclass whenever
credential_source.executable exists → construction succeeds with only
audience, subject_token_type, token_url, and credential_source → the
first fetch_access_token() (which every consuming application performs to use
the credential) calls retrieve_subject_token() and executes the command before
any network access.
Fix commit c95c77e70bec94f17e239d88050f843ea1cade95 (released as 0.06) adds an
opt-in gate at the top of retrieve_subject_token() that throws unless
GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1, plus a bounded execution timeout,
schema validation of the command output, and URL domain validation in the base
class. Version 0.10 additionally parses with Text::ParseWords and uses indirect
system exec.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.sh(self-contained; re-runnable). - The script:
- Clones
GoogleCloudPlatform/google-auth-library-perl(into the prepared project cache when available) and resolves the vulnerable checkout asc95c77e70bec94f17e239d88050f843ea1cade95^(=913fb17) and the fixed checkout as the fix commit itself; verifies the gate string is absent in the vulnerable tree and present in the fixed tree. - Installs the pure-Perl runtime dependencies (Moo, Capture::Tiny,
LWP::UserAgent, Log::Any, Throwable, URI) via apt when permitted, otherwise
into a bundle-local
INSTALL_BASEwithcpan, then builds the real module including its XS component (perl Makefile.PL && make) for both versions. - Generates an attacker-controlled
external_accountcredentials JSON whosecredential_source.executable.commandwrites a unique marker file using shell redirection and attacker-injected environment variables, and pointstoken_urlat a closed localhost port so the STS exchange fails fast after the command has already executed. - Invokes the real ADC flow as a CLI command:
GOOGLE_APPLICATION_CREDENTIALS=<json> perl -I... trigger.pl, wheretrigger.plcallsGoogle::Auth->default()and thenfetch_access_token()— exactly the sequence a real application performs. - Runs the matrix: 2 vulnerable attempts, 2 fixed attempts (default gated), and 1 fixed attempt with the opt-in gate set (positive control).
- Clones
- Expected evidence: both vulnerable attempts create their unique marker files;
both fixed attempts throw
Pluggable credentials are not enabled. Set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1and create no marker; the opt-in control creates its marker again.
Evidence
- Main log:
bundle/logs/reproduction_steps.log - Per-attempt process logs:
bundle/logs/attempt_vuln_1.log,bundle/logs/attempt_vuln_2.log,bundle/logs/attempt_fixed_1.log,bundle/logs/attempt_fixed_2.log,bundle/logs/attempt_fixed_allow1.log - Marker files (written by the injected shell command through
/bin/sh -c):bundle/repro/markers/vuln_1.marker,bundle/repro/markers/vuln_2.marker,bundle/repro/markers/fixed_allow1.marker - Per-attempt observation JSONs:
bundle/repro/observations/*.json - Attacker configs used:
bundle/repro/adc/*.json - Runtime manifest:
bundle/repro/runtime_manifest.json
Key excerpts (identical across two consecutive runs):
[run] vuln attempt 1 ...
RESULT: credentials class = Google::Auth::ExternalAccountCredentials::Pluggable
RESULT: fetch_access_token error: Token exchange failed with status 500: Can't connect to 127.0.0.1:9
[run] vuln attempt 1: MARKER CREATED -> pwned-via-CVE-2026-66902 vuln attempt 1 shell+env injection
[matrix] vulnerable attempts with marker: 2/2
RESULT: fetch_access_token error: Pluggable credentials are not enabled. Set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1 to enable.
[matrix] fixed attempts blocked (no marker): 2/2
[run] fixed attempt allow1: MARKER CREATED -> pwned-via-CVE-2026-66902 fixed attempt allow1 shell+env injection
[matrix] fixed+opt-in attempts with marker: 1/1
=== RESULT: CONFIRMED - command injection via Pluggable external_account credentials ===
Environment: Ubuntu 24.04, perl 5.38.2 (x86_64-linux-gnu-thread-multi),
OpenSSL 3.0.13 headers for the XS build, pure-Perl deps installed into
bundle/repro/deps (cpan INSTALL_BASE). No sanitizers, no mocks, no network
interaction with Google endpoints (token_url is http://127.0.0.1:9/v1/token,
which fails after the injected command has executed).
Recommendations / Next Steps
- Upgrade to Google-Auth 0.06 or later; 0.06 throws unless
GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES=1is set, and 0.10 additionally shell-parses the command withText::ParseWordsand uses indirect (list-form)systemexec, removing/bin/sh -cinterpretation. - Treat every credentials JSON reachable by the ADC flow
(
GOOGLE_APPLICATION_CREDENTIALS, well-known paths) as executable code: restrict write access, prefer trusted provisioning, and avoid setting the opt-in gate. - Regression testing: the upstream fix commit already adds tests
(
t/16-pluggable-credentials.t); downstream should additionally test that acredential_source.executableconfig without the gate never spawns a process.
Additional Notes
- Idempotency: the script was executed twice consecutively (plus two more times after adding unique per-attempt markers/observations) — every run produced the full matrix result (2/2 vulnerable markers, 2/2 fixed blocked, 1/1 opt-in control) and exit code 0.
- The command executes before the STS token exchange; the proof intentionally
uses a closed-loopback
token_urlso no external network call is needed and the marker is created regardless of the later (expected) STS failure. - Both the claimed entrypoint variants are supported: the primary proof uses
Google::Auth->default()(requires the XS build, which the script performs); if the XS toolchain were unavailable the script falls back toGoogle::Auth::DefaultCredentials->from_env(), which is the same dispatch path named in the claim. - Edge case: the vulnerable code only executes the command when a token is
fetched, not at config parse time — matching real application behavior, since
any consumer of the credential calls
fetch_access_token()to use it.
CVE-2026-66902 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
ls -la /data/pruva/project-cache/.run-views/e0d7614274d099ca25147d868040f8a99d1f2796b570b6bba6a5f6ab22c5eed5/ /data/pruva/project-cache/.run-views/e0d7614274d099ca25147d868040f8a99d1f2796b570b6bba6a5f6ab22c5eed5/repo-mirrors/ 2>&1; cat /data/pruva/project-cache/.run-views/e0d7614274d099ca25147d868040f8a99d1f2796b570b6bba6a5f6ab22c5eed5/.pruva/cache_manifest.json 2>&1/data/pruva/project-cache/.run-views/e0d7614274d099ca25147d868040f8a99d1f2796b570b6bba6a5f6ab22c5eed5/: total 16 drwx------ 4 pruva pruva 4096 Aug 9 14:00 . drwx------ 4 pruva pruva 4096 Aug 9 14:00 .. drwx------ 2 pruva pruva 4096 Aug 9 14:00 .pruva drwx------ 2 pruva pruva 4096 Aug 9 14:00 repo-mirrors /data/pruva/project-cache/.run-views/e0d7614274d099ca25147d868040f8a99d1f2796b570b6bba6a5f6ab22c5eed5/repo-mirrors/:
Artifacts and Evidence for CVE-2026-66902
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-66902
FAQ: CVE-2026-66902
Is CVE-2026-66902 exploitable?
How severe is CVE-2026-66902?
What type of vulnerability is CVE-2026-66902?
How can I reproduce CVE-2026-66902?
Is the CVE-2026-66902 reproduction verified?
References for CVE-2026-66902
Authoritative sources for CVE-2026-66902 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.