Cosmium: Why I Patch Chromium Instead of Hiding It With JavaScript
A Chromium in a container does not look like anyone's computer, and stealth plugins can only cover that from JavaScript. What Cosmium patches in C++, how it keeps a fingerprint coherent, and how it compares to stealth plugins, patched drivers and other anti-detect browsers.
I run browsers for scraping inside Docker and Kubernetes. The automation part has been easy for years: Puppeteer, Playwright or any Chrome DevTools Protocol client can open a page, wait for it and read the DOM. The hard part is that a Chromium running in a container does not look like anyone's computer.
It reports a SwiftShader GPU, because there is no real GPU. It says Linux x86_64 while its user agent claims Windows. It runs in UTC, has an odd CPU count from the cgroup limit, has no battery, no speech voices and a headless screen size. None of these come from your automation code. They come from the browser binary itself, so a stealth plugin cannot fully fix them.
Cosmium is my answer to that: a patched Chromium plus a Rust engine that tells it what machine to be. This post covers what it does and how it compares to the other ways people disguise a scraping browser.
What Cosmium is
Cosmium has two halves that meet at the command line.
The first is a series of 31 patches against Chromium 154.0.8037.57. Each patch closes one detection vector in the C++ source and adds a --cosmium-* switch to control it: the WebGL vendor and renderer, navigator.platform, Client Hints, CPU count, device memory, screen size, timezone, battery status, audio sample rate, pointer type, speech voices, and per-profile noise for canvas and audio fingerprints.
The second is a Rust workspace that loads a fingerprint profile, checks that it is coherent, turns it into those switches and launches the patched binary. The same engine runs a scraper, a fingerprint test suite and an HTTP server, and it ships as a CLI for Linux, macOS and Windows. The latest release, v0.5.0, also includes a prebuilt patched browser for Linux x86_64, which is where containers run.
Why patch the browser at all
Most stealth tools inject JavaScript before the page's own scripts run. They redefine navigator.webdriver, wrap getParameter on the WebGL context and override Intl.DateTimeFormat. That works against simple checks, but it has two structural weaknesses.
The overrides can be detected. A property redefined from JavaScript is not the same as a native one. Its descriptor, its toString() and its place on the prototype chain are all different, and detection scripts check exactly those things.
The overrides do not reach every realm. A page can ask the same question inside a Web Worker, a fresh iframe or an about:blank document, where the injected script may never run. Cosmium's test suite has a webdriver_worker probe for this reason: it starts a worker from a blob and asks it for navigator.webdriver. JavaScript-based stealth usually fails it.
When the value is changed in C++, the spoofed value is the native value. There is no descriptor to compare and no realm that missed the patch, because every realm is built from the same Blink code.
Coherence is the hard part
Spoofing each value on its own is not enough. A profile that claims MacIntel with an NVIDIA RTX renderer is a stronger signal than the SwiftShader string it replaced. Real machines are consistent, so a fingerprint has to be too.
Cosmium models a fingerprint as one JSON document. This is part of a real profile from the repository:
{
"identity": {
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36",
"client_hints": { "platform": "Windows", "platform_version": "15.0.0", "architecture": "x86", "bitness": "64" },
"navigator_platform": "Win32"
},
"locale": {
"languages": ["de-DE", "de", "en"],
"accept_language": "de-DE,de;q=0.9,en;q=0.8",
"timezone": "Europe/Berlin"
},
"gpu": {
"vendor": "Google Inc. (AMD)",
"renderer": "ANGLE (AMD, AMD Radeon RX 6700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)"
}
}
Before any browser starts, the engine checks rules that JSON Schema cannot express:
- the Chrome major version in the user agent must match the Client Hints brand version
navigator.platformmust match the Client Hints platform (Win32withWindows,MacIntelwithmacOS)- the first language must be a prefix of
Accept-Language - the timezone must be a real IANA zone
- the GPU renderer must not mention SwiftShader
- the CPU count must be even, and device memory must be one of Chrome's buckets: 0.25, 0.5, 1, 2, 4 or 8 GB
The repository ships eight profiles across Windows, macOS and Linux and several locales. You can also generate new ones with an LLM from a description like "Windows 11 gaming PC, RTX 4070, en-US". The generated profile goes through the same validator, and any profile that fails can be sent back to the model to repair. The LLM is optional; everything else works without an API key.
Proving it works
A stealth browser you cannot test is a stealth browser you have to trust. Cosmium has two test commands, and they answer different questions.
cosmium test fingerprint runs about thirty probes inside a real page and compares each answer with the loaded profile:
cosmium test fingerprint --profile win11_rtx3060_en-us
Because the expected values come from the profile, a failure tells you exactly which surface is wrong, and the command exits non-zero so it works as a CI gate after every Chromium rebuild.
cosmium test stealth asks public fingerprinting pages for their verdict instead: CreepJS, Pixelscan and BrowserLeaks by default, plus any URL you add. Reading the two together is useful. If the fingerprint passes but a site still flags you, the browser is not the problem. Look at your IP, your request pattern or your behavior instead.
How it compares
There are four other common ways to run a browser for scraping. Each one fixes some problems and leaves others.
Stock Chrome or Chromium in a container. This is what most scrapers use, and it is the baseline Cosmium exists to fix. Every container tell above is visible, and headless mode adds its own.
JavaScript stealth plugins such as puppeteer-extra-plugin-stealth. They are easy to add and fix the automation flags, but they work by overriding values from JavaScript, so they carry the descriptor and realm problems described above, and the hardware they describe has to agree with a binary that is still reporting the container underneath.
Patched drivers such as undetected-chromedriver and Patchright. They remove leaks in the automation layer itself, such as ChromeDriver's variables or the CDP calls Playwright makes. That is real progress, but they still drive a stock browser, so the GPU, platform, timezone and hardware values are still those of the container.
Other patched browsers. Camoufox takes the same C++-level approach on Firefox, and it is the project closest to Cosmium in spirit. The difference is the engine. Cosmium is Chromium, the engine behind most real browser traffic, and Chromium-based browsers are the only ones that send Client Hints, so a Chromium fingerprint has more real company to blend into. Commercial anti-detect browsers also patch the engine, but they are closed source and mostly built around a desktop app for managing many accounts by hand. Cosmium is open source, MIT licensed and built for servers: a CLI, a Docker image and an HTTP API.
| Stock Chromium | JS stealth plugin | Patched driver | Cosmium | |
|---|---|---|---|---|
| Automation flags hidden | No | Yes | Yes | Yes |
| Values native in every realm | Yes | No | Yes | Yes |
| Container hardware hidden | No | Partly | No | Yes |
| Profiles checked for coherence | No | No | No | Yes |
| Built-in fingerprint tests | No | No | No | Yes |
Built to run as a service
The CLI covers single runs: cosmium run opens a browser with a profile, and cosmium scrape runs a workflow over CDP and returns the HTML, cookies, extracted values and screenshots. For anything long-lived, cosmium serve starts an HTTP server, built with Axum (I wrote about why Axum is my default), so any language can drive it:
curl -X POST http://localhost:3000/api/v1/scrape \
-H "x-api-key: $COSMIUM_API_KEY" \
-H 'content-type: application/json' \
-d '{
"url": "https://example.com",
"profile": "win11_rtx3060_en-us",
"extract": ["h1", ".price"],
"screenshot": true,
"proxies": ["http://user:pass@host:8080"],
"proxy_rotation": "round_robin",
"retries": 2
}'
Requests can rotate through a proxy pool, retry when a page looks blocked, and use geo-sync, which looks up the proxy's exit location and sets the browser's timezone to match. A browser in Berlin time behind a Jakarta IP is exactly the kind of mismatch the profile validator exists to prevent, and geo-sync applies the same idea to the network. There is also a React dashboard on top of the API, including a visual builder for multi-step scrapes.
What Cosmium does not solve
Being clear about the limits matters more than the feature list.
- IP reputation. Anti-bot systems score your IP before they look at your browser. No patch changes a datacenter IP into a residential one, so serious targets still need residential or mobile proxies.
- Behavior. Mouse movement, scrolling, timing and click patterns belong to your automation layer, not to the browser binary.
- Captchas. Out of scope.
- Building it is heavy. A Chromium build needs 100 GB of disk, 16 GB of RAM and around six hours the first time. The prebuilt Linux browser saves you that, but changing a patch means rebuilding.
- Every Chromium release is work. Patches are written against one Chromium tag, and moving to a new one means re-applying 31 patches. Keeping one detection vector per patch makes conflicts easy to find, but it is still maintenance.
Use it responsibly
A browser that does not look automated can be misused, so Cosmium has an acceptable use policy: follow the law and the terms of service and robots.txt of the sites you visit, and do not use it for unauthorized access, fraud, account abuse, credential testing or spam. Its purpose is to make legitimate scraping and testing in containers behave like a normal browser, not to break into anything.
Try it
The code, patches and profiles are at github.com/maulanasdqn/cosmium, and the documentation is at cosmium.zod.rs. Install the CLI from the latest release, validate a profile with cosmium profile validate, and run cosmium test fingerprint against the prebuilt browser to see every probe pass for yourself.