CVE-2026-2441 is an actively exploited use-after-free in Chromium's CSS implementation. Google shipped the fix on February 13, 2026 in Chrome 145.0.7632.75 for Windows and macOS and 144.0.7559.75 for Linux. Playwright and Puppeteer pin their own Chromium build at install time, so any HTML-to-PDF pipeline still running the bundled binary stays exposed until a new Chromium roll lands in the npm package. This guide walks through what the bug is, how to detect it, the three patch paths, and the hardening that makes the next CVE cheap.
What CVE-2026-2441 actually is
CVE-2026-2441 is a heap use-after-free in Chromium's CSS handling, reachable from any page that loads attacker-controlled HTML and CSS. NVD scores it CVSS 8.8 (high) on vector AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H and classifies it as CWE-416. A crafted style sheet can free a CSS object while another code path still holds a reference to it; the next access reads or writes memory that has been reassigned, which an exploit can groom into arbitrary code execution inside the renderer process.
Shaheen Fazim reported the bug to Google on February 11, 2026. Two days later, on February 13, 2026, Google shipped the patch in the desktop stable channel as Chrome 145.0.7632.75 and 145.0.7632.76 for Windows and macOS, and 144.0.7559.75 for Linux. Google's Threat Analysis Group confirmed in-the-wild exploitation, which is the trigger for the "actively exploited zero-day" label and the reason the patch went out as an out-of-band stable update rather than waiting for the next scheduled release. Coverage from The Hacker News and Help Net Security tracked the disclosure timeline.
Active in-the-wild exploitation has been confirmed by Google TAG. Any HTML-to-PDF pipeline that renders user-submitted templates, user-uploaded HTML, or third-party CSS is in the attack path. The renderer sandbox limits the blast radius, but a successful exploit still gets code execution inside the sandbox, which is enough to read every PDF the worker has produced in its current process lifetime.
Why your HTML-to-PDF pipeline is in scope
Server-side PDF generation is "render attacker-controlled HTML" by definition. A Playwright or Puppeteer pipeline that compiles a Handlebars template with user input, runs page.setContent(rendered), and calls page.pdf() is doing exactly what a browser does when it visits a malicious page, except the input came through your API and the output goes back to the requester. The attack surface is identical to a desktop browser navigating to a page the attacker controls.
Three properties make the server-side case worse than the desktop case. First, the renderer runs continuously, so a single vulnerable replica can serve thousands of crafted payloads per minute. Second, many implementations reuse a singleton browser process across requests via a page pool, so memory corruption persists past a single render. Third, the output (a PDF) is typically delivered back to the requester, which gives the attacker a side channel for whatever the exploit reads from process memory: API keys, other tenants' templates, cached cookies.
The set of pipelines that match this pattern is almost everyone running headless Chromium in production. Public examples include invoice rendering, certificate generation, report exports, PDF previews of user-submitted markdown, and document signing flows that render the final PDF on the backend. All of them parse attacker-influenced HTML and CSS on every render, which is exactly what CVE-2026-2441 needs to fire.
Detect: is my Playwright Chromium vulnerable?
Run the matching command for the binary your pipeline launches and compare the output against 145.0.7632.75 on Windows or macOS, or 144.0.7559.75 on Linux. Anything below those numbers is exposed to CVE-2026-2441 and should be patched in the same change window.
For Playwright:
# Print the Playwright version and the bundled Chromium build.
npx playwright --version
# Locate the Chromium binary the package will launch.
node -e "console.log(require('playwright').chromium.executablePath())"
# Run the binary directly to see its real version string.
$(node -e "console.log(require('playwright').chromium.executablePath())") --versionThe last command prints a line like Chromium 145.0.7632.6 for Playwright 1.58 or HeadlessChrome 144.0.7559.55 for older lines. That is the version exposed to the renderer, not the npm package version.
For Puppeteer:
# Show all Chromium builds Puppeteer has on disk.
npx puppeteer browsers list
# Or, from inside a running container:
node -e "console.log(require('puppeteer').executablePath())" \
| xargs -I{} {} --versionFor chrome-headless-shell:
chrome-headless-shell --versionThe metadata file at node_modules/playwright-core/browsers.json lists the pinned Chromium build by revision. Convert it through the Chromium Dash UI if you need to map a revision to a version string. As a shortcut, the Playwright release notes always state which Chrome for Testing build a given Playwright version bundles.
Patch: three options ranked by trade-off
There are three ways to remove the vulnerable Chromium from the request path. Pick based on how fast you need the fix and how much reproducibility you can give up. Most production teams ship option 2 within hours and revert to option 1 once the Chromium roll lands.
| Option | Speed | Reproducibility | Best for |
|---|---|---|---|
| 1. Upgrade Playwright once the Chromium roll lands | Slow: days to weeks of vendor lag | Highest: matches the pinned Chromium the team tests against | Default once the roll is shipped |
2. Override executablePath to system Chrome | Fastest: minutes after apt upgrade | Lower: production runs a different Chromium than CI | Active incident response |
3. Pin a manual chrome-headless-shell build | Medium: one image rebuild | Medium: explicit pin, but diverges from Playwright bundle | Long-running stable environments |
Option 1: upgrade Playwright once the Chromium roll lands
Track microsoft/playwright issue #39574 and the Playwright release feed on GitHub. As soon as a release bundles a Chromium at or above the patched build, bump the dependency and reinstall.
npm install -D @playwright/test@latest
npx playwright install chromium
# Confirm the floor was crossed.
npx playwright --versionCI caches at ~/.cache/ms-playwright on Linux must be invalidated, otherwise the runner reuses the old binary. The same cache lives under %USERPROFILE%\AppData\Local\ms-playwright on Windows and ~/Library/Caches/ms-playwright on macOS.
Option 2: override executablePath to system Chrome
Install or update Chrome through the system package manager, then point Playwright at the system binary instead of the bundled one. The renderer process inherits the system Chrome's patch level, so as soon as apt, yum, or brew has the new version, the pipeline is patched.
# Debian/Ubuntu, assuming the Google repo is enabled.
sudo apt update && sudo apt install --only-upgrade google-chrome-stable
google-chrome --versionimport { chromium } from "playwright";
const browser = await chromium.launch({
executablePath: "/usr/bin/google-chrome-stable",
});This option breaks the "Playwright manages its own Chromium" guarantee. Behavior differences between the bundled Chromium and the system Chrome are usually small for page.pdf() workloads, but exist (font fallback, GPU defaults, print emulation). Run a smoke test on representative templates before flipping production. Microsoft documents the pattern in the Playwright browser configuration guide.
Option 3: pin a manual chrome-headless-shell build
The middle path is to install a specific chrome-headless-shell build at a known-patched version and point Playwright at it. The build comes from Chrome for Testing, which Google maintains specifically for automation pipelines.
# Download the patched chrome-headless-shell build.
curl -L -o cfst.zip \
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7632.75/linux64/chrome-headless-shell-linux64.zip"
unzip cfst.zip -d /opt/chrome-145const browser = await chromium.launch({
executablePath: "/opt/chrome-145/chrome-headless-shell-linux64/chrome-headless-shell",
});This gives you explicit version control and decouples patching from the Playwright release cycle entirely. The cost is one more thing to monitor: the Chrome for Testing channel publishes new builds frequently, and the pinned version becomes stale without a deliberate bump.
Harden your pipeline for the next zero-day
A patch is a fire drill. A pipeline that survives the next CVE without one is the goal. Five defenses make Chromium memory bugs cheap to absorb, regardless of which subsystem ships the next use-after-free.
Keep the renderer sandbox on. Do not pass --no-sandbox in production. The renderer sandbox is what limits a CVE like this one to "code execution inside a sandbox" rather than full host RCE. Disabling the flag, common in older Docker setups, removes the one barrier between a malicious CSS payload and your file system.
Drop privileges on the host. Run the headless browser as a non-root user inside the container, drop Linux capabilities except those Chromium actually needs (CAP_SYS_ADMIN is required only when using the namespace sandbox, not the seccomp-bpf sandbox), and mount the work directory as read-only outside the cache path. None of this stops a use-after-free; all of it shrinks the blast radius once one fires.
Restart the browser process per render or per N renders. A long-lived singleton browser holds whatever memory state the last few renders accumulated, including the side effects of any successful exploit. Recycling the process every N requests is a cheap kill switch: even if a renderer is compromised, the contamination window closes within seconds. The trade-off is cold-start cost, which is small for headless Chromium and offset by the security benefit.
Lock down network egress. The renderer should never need outbound network access during a page.pdf() call. Put the worker on a network policy that blocks egress except to your asset CDN and your fonts host. If the exploit tries to exfiltrate process memory over HTTP, the request fails.
Automate the dependency PRs. Renovate or Dependabot configured to track @playwright/test, playwright, playwright-core, puppeteer, puppeteer-core, chrome-headless-shell, and @sparticuz/chromium opens a PR within hours of every upstream release. A simple Renovate rule keeps the four packages grouped:
{
"extends": ["config:recommended"],
"packageRules": [
{
"matchPackageNames": [
"@playwright/test",
"playwright",
"playwright-core",
"puppeteer",
"puppeteer-core",
"@sparticuz/chromium"
],
"groupName": "headless chromium",
"schedule": ["before 9am every weekday"],
"prPriority": 5,
"labels": ["security", "chromium"]
}
]
}Pair the Renovate rule with a Snyk or GitHub Dependabot alert subscription on the same packages and a Chrome Releases blog feed in your incident channel. The fastest signal that a patched Chromium is in your tooling is usually the Playwright or Puppeteer GitHub release feed, ahead of NVD.
Why PDF4.dev users are not affected
PDF4.dev rolls Chromium server-side and patches it on the same day Google ships a stable update. The pin policy is a hardcoded minimum version checked at worker boot: a worker that runs a Chromium below the floor refuses to take traffic. When CVE-2026-2441 landed on February 13, 2026, the production fleet picked up the new build the same day and the warm pool was force-recycled to flush any in-memory state.
PDF4.dev customers do not need to track Chromium CVEs to stay patched. The platform's Chromium pin and worker-recycle policy means the same upgrade described in this article runs automatically across every render. If you want to move the Chromium upgrade window off your team's plate, that is what the service handles.
This is not a sales pitch, it is the architectural answer to the bundled-Chromium problem. Any provider running headless rendering as a service has to solve the same problem: Chromium ships a high-severity CVE roughly every month, the bundled-browser model in Playwright and Puppeteer always lags, and the fix tempo will not slow down. The choice is to own the upgrade window or to outsource it.
Takeaway
CVE-2026-2441 is the second high-severity Chromium CVE in 2026 that hits HTML-to-PDF pipelines directly, after CVE-2026-5287 earlier in the year. Both follow the same pattern: a fix lands upstream within days, but the bundled-Chromium dependency in Playwright and Puppeteer delays the fix in customer pipelines by weeks. The detection-and-patch playbook above closes the gap for this one. The hardening section closes it for the next one.
The single most useful change for any team running headless Chromium in production is to add a boot-time version check that refuses to take traffic if the Chromium binary is below a hardcoded floor. The next CVE is already being researched somewhere; the goal is to make the response routine rather than urgent.
Start generating PDFs
Build PDF templates with a visual editor. Render them via API from any language in ~300ms.



