This is a landmine hidden in the print room, not a grenade rolling across every desktop
CVE-2026-10928 is a script injection flaw in Chrome Headless that affects versions before 149.0.7827.53 on Linux and before 149.0.7827.53/.54 on Windows and macOS. The attacker supplies crafted HTML, and the vulnerable code path is only reached when that content is rendered by Headless Chrome—the unattended browser mode used by automation stacks, PDF/screenshot services, CI jobs, crawlers, preview generators, and tools such as Puppeteer.
Google's HIGH 8.8 rating is technically defensible for the bug in isolation, but it overstates the population actually reachable in most enterprises. The real-world friction is the big one: the target must be running headless rendering of attacker-controlled content. That sharply narrows exposure versus normal Chrome desktop browsing, and the very low EPSS plus no KEV listing and no reviewed exploitation evidence push this down into a controlled, environment-dependent risk rather than an all-hands emergency.
4 steps from start to impact.
Get malicious HTML in front of a renderer
chrome --headless usage. The weaponized content is just a crafted HTML page; there is no need for the attacker to authenticate to Chrome itself.- A service or host must run Chrome Headless
- That workflow must render attacker-controlled HTML or visit attacker-controlled URLs
- The vulnerable version must still be installed
- Most enterprise endpoints run normal interactive Chrome, not headless automation
- Many headless jobs render only trusted internal templates, not arbitrary internet content
- URL allowlists, job isolation, and queue mediation often block direct attacker reach
Trigger the Headless-only vulnerable code path
--print-to-pdf, screenshots, and automation, which is exactly the kind of unattended rendering path an attacker wants. The practical toolchain here is Headless Chrome / Puppeteer / ChromeDriver.- The render job must actually invoke Headless mode
- The malicious HTML must hit the specific vulnerable parser/runtime path
- The environment must not reject or sanitize the submitted content before render
- Some wrappers pre-render or sanitize content before browser execution
- Job timeouts, CSP, or network egress controls can reduce exploit reliability
- Many services run fresh Chrome-for-Testing bundles rather than stale system Chrome
--headless, but it usually will not distinguish safe rendering from exploit triggering without strong telemetry.Gain code execution in the browser context
- The exploit must be reliable against the exact Chrome build and platform
- The browser process must have access to useful secrets or network paths
- The service account or container must not be heavily constrained
- Headless workers are often ephemeral and sandboxed
- Many render nodes have low-privilege service accounts and minimal data at rest
- There is no reviewed public PoC or active exploitation evidence reducing confidence in weaponization today
Pivot from renderer to something that matters
- Renderer must have meaningful network or secret access
- Segmentation or egress controls must be weak enough to allow pivoting
- The workload must persist long enough for follow-on actions
- Good container isolation and short-lived jobs shrink dwell time
- Cloud IAM scoping and secret brokers can sharply limit follow-on value
- Many headless renderers sit behind queues and are not directly internet-addressable
The supporting signals.
| In-the-wild status | No reviewed evidence of active exploitation as of 2026-06-05; the CVE is not in CISA KEV. |
|---|---|
| Public PoC | No reviewed public PoC tied specifically to CVE-2026-10928. That matters because Headless-only bugs often need environment-specific grooming before they become reliably weaponized. |
| EPSS | 0.00084 (user-supplied intel), which is extremely low and consistent with a bug that is technically nasty but operationally niche. |
| KEV status | Not listed in the CISA Known Exploited Vulnerabilities Catalog. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H — remote, low complexity, no auth, but user interaction/render-trigger required. In this case the practical 'user' is often an automation pipeline or preview worker. |
| Affected versions | Google advisories place desktop fixes at 149.0.7827.53/.54 for Windows and Mac and 149.0.7827.53 for Linux; affected builds are earlier than those versions. |
| Fixed versions | Patch to 149.0.7827.53+ on Linux and 149.0.7827.53/.54+ on Windows/macOS. I found no distro-specific backport details in the reviewed sources. |
| Exposure reality | This is not directly internet-scannable like a server daemon. Exposure usually rides inside apps that call chrome --headless, use Puppeteer, or perform PDF/screenshot rendering. |
| Operational amplifiers | Risk goes up fast if your environment has public preview endpoints, URL-to-PDF services, screenshot APIs, crawler farms, or CI jobs that open attacker-supplied pages. |
| Disclosure | Disclosed 2026-06-04 (user-supplied intel); Chrome stable update appeared 2026-05-29 for Windows/Mac early stable, and the Canadian advisory references Google's 2026-06-02 publication. |
noisgate verdict.
The decisive downward pressure is exposure narrowing: the bug matters only where enterprises run Headless Chrome to render attacker-controlled content. That is a real attack surface, but it is a much smaller and more controllable population than the vendor CVSS suggests for a generic Chrome RCE.
Why this verdict
- Starts at vendor HIGH 8.8 because unauthenticated remote content can trigger code execution with severe CIA impact.
- Downward adjustment: requires Headless mode. That implies a narrower target set than normal user browsing; many enterprises have thousands of Chrome installs but only dozens of headless render nodes.
- Downward adjustment: requires attacker-controlled render input. If your jobs only render trusted templates or internal pages, the attack chain never starts.
- Downward adjustment: commonly post-app-friction, not raw internet reach. The attacker usually needs a preview/export/crawl feature, CI workflow, or embedded automation service that accepts untrusted content.
- Further downward adjustment: no KEV, no reviewed exploitation evidence, very low EPSS. That weakens the case for emergency prioritization absent local exposure amplifiers.
- Still not LOW because exposed render farms can be quietly valuable**: service tokens, internal reachability, CI artifacts, screenshots/PDFs, and cloud metadata can all turn a 'niche' browser bug into an infrastructure incident.
Why not higher?
This is not a broad, no-click, fleet-wide desktop event. The chain narrows hard on two prerequisites: Headless Chrome must be in use, and it must be fed attacker-controlled HTML or URLs. Without evidence of active exploitation or a public PoC, calling this HIGH for most enterprises would overweight theoretical impact and underweight deployment reality.
Why not lower?
A lot of modern infrastructure quietly depends on Headless Chrome for rendering and automation, and those workers often sit near secrets, CI tokens, internal apps, or cloud APIs. If you do run internet-facing preview/PDF/screenshot features, the attacker position is effectively unauthenticated remote, which is too strong to dismiss as backlog-only hygiene.
What to do — in priority order.
- Inventory headless usage — Identify every workload that launches Chrome with
--headlessor through Puppeteer/ChromeDriver and map which ones accept untrusted URLs or HTML. For a MEDIUM verdict there is no noisgate mitigation SLA — go straight to the 365-day remediation window, but do this discovery work now so the patch does not get lost behind desktop-only assumptions. - Fence untrusted render paths — Restrict preview, screenshot, and PDF generation features to allowlisted domains or sanitized templates where possible. This is the best risk reducer because it removes the attacker-controlled content prerequisite entirely; deploy as part of the same remediation cycle even though there is no formal mitigation SLA for MEDIUM.
- Constrain renderer identities — Run headless workers with low-privilege service accounts, minimal filesystem access, no developer tokens, and tightly scoped cloud IAM. That keeps successful browser-process execution from turning into infrastructure compromise; complete during the 365-day remediation window if not already in place.
- Segment and egress-filter render nodes — Limit outbound network access from screenshot/PDF/CI renderer subnets to only the domains and APIs they genuinely need. This cuts off follow-on recon and exfiltration if exploitation occurs, and it is especially important for internet-facing render services.
- Update bundled browser runtimes — Patch not just visible desktop Chrome but also container images, CI images, and app-bundled Chrome-for-Testing or Chromium copies. Headless stacks frequently pin old browser versions; for MEDIUM, clear those through the noisgate remediation SLA of ≤365 days.
- A WAF on your front-end does not reliably stop this if the app legitimately forwards attacker-supplied URLs/HTML to a backend renderer.
- Patching only end-user desktop Chrome misses the actual risk if your vulnerable copy lives inside containers, build agents, or app bundles.
- Browser hardening meant for interactive users—homepage locks, extension policy, Safe Browsing prompts—does little for unattended Headless jobs.
Crowdsourced verification payload.
Run this on the target host or container image that may execute Chrome Headless. Invoke it with python3 check_cve_2026_10928.py or python3 check_cve_2026_10928.py --version "Google Chrome 149.0.7827.52"; no admin rights are required, but local read/execute access to Chrome binaries helps automatic discovery.
#!/usr/bin/env python3
# check_cve_2026_10928.py
# Determine likely exposure to CVE-2026-10928 by checking installed Chrome/Chromium version.
# Outputs exactly one of: VULNERABLE / PATCHED / UNKNOWN
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import os
import platform
import re
import shutil
import subprocess
import sys
PATCH_WIN_MAC = (149, 0, 7827, 54) # conservative threshold for Windows/macOS
PATCH_LINUX = (149, 0, 7827, 53)
COMMON_COMMANDS = [
"google-chrome",
"google-chrome-stable",
"chromium",
"chromium-browser",
"chrome",
"msedge",
]
COMMON_PATHS = [
"/usr/bin/google-chrome",
"/usr/bin/google-chrome-stable",
"/usr/bin/chromium",
"/usr/bin/chromium-browser",
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
]
def parse_version(text):
m = re.search(r"(\d+)\.(\d+)\.(\d+)\.(\d+)", text)
if not m:
return None
return tuple(int(x) for x in m.groups())
def version_to_str(v):
return ".".join(str(x) for x in v) if v else "unknown"
def cmp_version(a, b):
return (a > b) - (a < b)
def run_cmd(cmd):
try:
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=8)
out = (p.stdout or "") + "\n" + (p.stderr or "")
return out.strip()
except Exception:
return ""
def find_version_from_commands():
checked = []
for c in COMMON_COMMANDS:
path = shutil.which(c)
if not path:
continue
checked.append(path)
out = run_cmd([path, "--version"])
v = parse_version(out)
if v:
return v, path, checked
return None, None, checked
def find_version_from_paths():
checked = []
for p in COMMON_PATHS:
if os.path.exists(p):
checked.append(p)
out = run_cmd([p, "--version"])
v = parse_version(out)
if v:
return v, p, checked
return None, None, checked
def threshold_for_platform():
system = platform.system().lower()
if system == "linux":
return PATCH_LINUX, "Linux"
if system == "darwin":
return PATCH_WIN_MAC, "macOS"
if system == "windows":
return PATCH_WIN_MAC, "Windows"
return PATCH_WIN_MAC, platform.system()
def main():
explicit = None
args = sys.argv[1:]
if "--version" in args:
try:
idx = args.index("--version")
explicit = args[idx + 1]
except Exception:
print("UNKNOWN")
sys.exit(2)
threshold, os_name = threshold_for_platform()
version = None
source = None
checked = []
if explicit:
version = parse_version(explicit)
source = "--version argument"
else:
version, source, checked = find_version_from_commands()
if not version:
version, source, checked2 = find_version_from_paths()
checked.extend(checked2)
if not version:
print("UNKNOWN")
sys.exit(2)
# CVE-2026-10928 affects Chrome prior to 149.0.7827.53 on Linux
# and prior to 149.0.7827.53/.54 on Windows/macOS.
# We use 149.0.7827.54 as the conservative patched threshold on Windows/macOS.
if cmp_version(version, threshold) < 0:
print("VULNERABLE")
sys.exit(1)
else:
print("PATCHED")
sys.exit(0)
if __name__ == "__main__":
main()
If you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.