5713e3d04a
chromedp/headless-shell cannot clear kagane.to's managed challenge. It is
a stripped Chrome build, and the tells are structural rather than a
header: navigator.webdriver is true, the plugin list is empty, and the
client hints are Chromium- rather than Chrome-branded. Overriding
webdriver through CDP was tried on its own and changed nothing.
Everything below was measured on 2026-08-08 from a single IP, against the
same kagane cover, so the comparisons are like for like:
chromedp/headless-shell:stable never cleared (90s)
zenika/alpine-chrome never cleared - ships Chrome 124, old
enough that Cloudflare refuses it and
old enough to break chromedp's CDP structs
google-chrome, default UA never cleared (60s) - --headless=new
advertises "HeadlessChrome"
google-chrome, stock UA, UTC never cleared (90s)
google-chrome, stock UA, TZ set cleared in ~4s
So both remaining tells are load-bearing, and each was tested in
isolation. chrome/ is a Debian image with google-chrome-stable, a UA
whose version is read back out of the binary at startup (a hardcoded one
would drift out of step with the Sec-CH-UA hints on the next Chrome
update and become a fresh tell), and no --enable-automation.
The timezone matters because Cloudflare scores a browser whose clock zone
disagrees with its egress IP's country as a proxy. Note that the usual
`-v /etc/localtime:/etc/localtime:ro` does not work here: Chrome resolves
the zone through ICU, which takes the name from that path's symlink
target and ignores the file's contents, so glibc reports the host zone
while Chrome still reports UTC. /etc/timezone carries the name and is
mounted instead; BROWSER_TZ overrides it for a host whose clock is UTC in
a country that is not.
Chrome also binds its DevTools port to loopback and silently ignores
--remote-debugging-address, which is why headless-shell fronted it with
socat. This image does the same, so it stays a drop-in: the compose
service keeps the headless-shell name and its pinned address, and
BROWSER_WS_URL is unchanged.
Deploying needs `docker compose build headless-shell`.
40 lines
1.9 KiB
Docker
40 lines
1.9 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# Real Google Chrome for the latest-chapter poller and the kagane cover proxy.
|
|
#
|
|
# Not chromedp/headless-shell, which this replaces. headless-shell is a stripped
|
|
# Chrome build and Cloudflare's managed challenge on kagane.to never clears for
|
|
# it: measured 2026-08-08, 60s of a held-open tab still served the interstitial,
|
|
# while stock Chrome from the same IP cleared in ~4s. The tells are structural
|
|
# rather than a header — navigator.webdriver true, an empty plugin list, and
|
|
# Chromium- rather than Chrome-branded client hints. Overriding webdriver alone
|
|
# was tried and did not move it, so the browser build itself is the fix.
|
|
#
|
|
# zenika/alpine-chrome was also tried: its Chrome is 124 (2024), old enough that
|
|
# Cloudflare refuses it outright and old enough to break chromedp's CDP structs.
|
|
FROM debian:trixie-slim
|
|
|
|
# Chrome is deliberately unpinned, against the usual rule. A pinned build goes
|
|
# stale, and a stale browser is exactly what Cloudflare turns away — the 124 in
|
|
# alpine-chrome is the worked example. Rebuild is the upgrade path.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends ca-certificates wget gnupg \
|
|
&& wget -qO- https://dl.google.com/linux/linux_signing_key.pub \
|
|
| gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg \
|
|
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] https://dl.google.com/linux/chrome/deb/ stable main" \
|
|
> /etc/apt/sources.list.d/google-chrome.list \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends google-chrome-stable socat \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Unprivileged: Chrome refuses to run as root, and the CDP endpoint is a shell
|
|
# on whatever user owns it.
|
|
RUN useradd --create-home --shell /usr/sbin/nologin chrome
|
|
USER chrome
|
|
WORKDIR /home/chrome
|
|
|
|
COPY entrypoint.sh /entrypoint.sh
|
|
|
|
EXPOSE 9222
|
|
ENTRYPOINT ["/entrypoint.sh"]
|