Live verification against real Cloudflare-protected sites
Every code path in the scraper, exercised against production Cloudflare
deployments rather than fixtures — the transport group, the diagnosis classifier, the
escalation ladder, a real browser solve, the archive tier, and a live tor-pool.
27 of 29 scenarios pass. A stubbed transport can confirm that the code
does what it says; only this can confirm that what it says is true of a real server.
27/29
scenarios passed
102
assertions verified live
0 failed
330
hosts probed
161 behind Cloudflare
The corpus
Every base URL in lncrawl's source index was probed twice — once with a
plain requests session, once with the impersonating transport — and each
response classified by the library's own diagnosis. That pairing is the measurement: the
only difference between the two requests is the network signature.
330
hosts probed
161
behind Cloudflare
34
refused a plain client,
served the impersonated one
What fronts these hosts
Edge
Hosts
cloudflare
157
nginx
49
unreachable
39
cowboy
27
apache
8
openresty
7
unknown
7
ddos-guard
6
tengine
5
litespeed
4
Through a Tor exit
Of 45 Cloudflare hosts reached through the local tor-pool:
28 served outright, 10 challenged,
0 reputation-blocked.
A negative result worth keeping: the reputation layer is not uniformly
hostile to Tor on this corpus. The model treats a Tor exit as clearing nothing at layer 1,
which is conservative rather than measured — and the consequence is that a browser solver
buys more here than a residential proxy would.
Which detection layer each client meetsHosts, out of 330 probed. Lower is better — a layer met is a
layer that has to be answered.
plain requestsimpersonated transport
L9 Managed JavaScript challenge
44
28
L8 Per-zone behavioural model
23
22
L12 Super Bot Fight Mode
20
7
L6 Browser and JavaScript fingerprint
10
10
L15 Operator edge code
5
0
L10 Turnstile
0
1
Reading the chart: the impersonated client meets the managed challenge on
far fewer hosts, and meets the lighter scoring tiers less often — those are the layers a
network signature answers. It meets the behavioural layer more often, which is not
a regression: that is this session's own request volume against the same hosts being rate
limited, and it is the one layer no client-side change addresses.
Scenarios
Each one names the layer it exercises and what a pass actually proves — "it returned 200"
is not evidence about a detection layer on its own. Expand any row for the individual
assertions and the values observed.
Transport — layers 2–5
passS01A plain client is refused where an impersonated one is servedL2L3L4L57.9s
What a pass proves. Layers 2-5 are one barrier: the only difference between these two requests is the network signature, and it decides the outcome.
against looked up from the last probe
·hosts presenting the condition todayhttps://novelfull.com/, https://www.novelhall.com/
✓the profile sends a browser User-Agent we never wroteMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
✓no Python client string is presentMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
passS02A pinned old profile is flagged; a family alias is notL30.0s
What a pass proves. A stale impersonation profile predates the post-quantum key share current builds send, so pinning one contradicts the User-Agent it claims.
✓the bare alias is accepted silently
✓a two-year-old pin is flaggedimpersonation target 'chrome99' is older than the available 'chrome136'. A stale profile is itself a signal: use the bare 'chrome' alias so the fingerprint tracks the newest supported build.
passS03A Cloudflare-fronted page is retrieved and parsedL2-L5L11L121.25s
What a pass proves. The baseline tier handles a real protected site end to end, including the soup, JSON and file helpers.
against https://novelfull.net/
✓a page came back with a titleRead free English translated Light Novel - NovelFull
✓the tier used was the cheapest one
✓nothing was recorded as binding
✓links were extracted139 followable
✓get_file wrote the body102135 bytes
·explain()novelfull.net binding layer : nothing has blocked yet tier : direct pacing : 1.2s mean interval requests : 22 ok / 0 failed clearance : none ladder : direct(10) exits : direct
Diagnosis
passS05A challenge served with a 403 is diagnosed as a challengeL9L103.65s
What a pass proves. The status code says 'forbidden' and the correct remedy is a browser, not a new address. A conventional client rotates its proxy here and gets nowhere.
against https://centralnovel.com/
✓https://centralnovel.com/ -> L9 Managed JavaScript challengeHTTP 403 diagnosed solve (L9 Managed JavaScript challenge): challenge served with 403
✓https://centralnovel.com/ asks for a solve, not a rotation
✓https://ranobes.top/ -> L10 TurnstileHTTP 200 diagnosed solve (L10 Turnstile): challenge served with a success status
✓https://ranobes.top/ asks for a solve, not a rotation
passS06A challenge with no solver configured stops and names what is missingL90.41s
What a pass proves. The failure is actionable: it names the layer and the capability that would reach it, instead of exhausting retries on a 403.
against https://centralnovel.com/
✓stopped at the challenge layerL9 Managed JavaScript challenge
✓the message names the missing capabilityL9 Managed JavaScript challenge is beyond every configured tier (direct is the strongest). Configure a browser solver. [stop: L9 Managed JavaScript challenge is beyond every configured tier (direct is the strongest). Configure a browser solver.]
passS07A host scored as automated escalates rather than rotatingL120.88s
What a pass proves. Nothing distinguishes the scoring tiers from outside, so the diagnosis names the strictest emit-only one and asks for a stronger tier — not a new address.
against https://novelmania.com.br/
✓diagnosed as scoring rather than reputationescalate (L12 Super Bot Fight Mode): scored as automated
✓the action is to escalate, not rotate
inconclusiveS08An identity-provider gate raises immediately and is never retriedL190.0s
What a pass proves. The layer reads a secret. Retrying is an infinite loop against a wall, so a single request is spent and the message names the only legitimate route.
no host in the last probe is behind an identity-provider gate
passS09Synthetic statuses are each diagnosed correctly, liveL1L7L8L195.13s
What a pass proves. The classifier is exercised against responses a real server produced, not fixtures.
against https://httpbingo.org
✓HTTP 429: a throttle is a pacing problem-> backoff (L8 Per-zone behavioural model): rate limited
✓HTTP 503: a plain outage is worth one more try-> retry: origin unavailable
✓HTTP 401: authentication has no bypass-> refuse (L19 Identity-provider gate): authentication required (HTTP 401)
✓HTTP 404: the site's answer about a path is not a layer-> accept: HTTP 404
✓HTTP 502: an upstream error is transient-> retry: upstream error (HTTP 502)
passS24A 404 is returned rather than blamed on a layer-1.57s
What a pass proves. The site's answer about a path says nothing about the client. Attributing it to a layer would retire a healthy address over a typo in a URL.
against https://novelfull.net/
✓a 4xx came back as a valueHTTP 404
✓nothing was recorded as bindingNone
passS25Every layer the corpus actually produces is diagnosed, and impersonation measurably reduces challengesL8L9L10L12L13L15L190.0s
What a pass proves. The population-level result: what 501 real hosts do today, and what the transport profile is worth measured across all of them rather than anecdotally.
against 501 hosts from lncrawl's source index
·hosts probed330
·reachable291
·Cloudflare-fronted157
·layers seen by a plain client{'L12 Super Bot Fight Mode': 20, 'L15 Operator edge code': 5, 'L6 Browser and JavaScript fingerprint': 10, 'L8 Per-zone behavioural model': 23, 'L9 Managed JavaScript challenge': 44}
·layers seen by the impersonated client{'L10 Turnstile': 1, 'L12 Super Bot Fight Mode': 7, 'L6 Browser and JavaScript fingerprint': 10, 'L8 Per-zone behavioural model': 22, 'L9 Managed JavaScript challenge': 28}
✓impersonation converts refusals into content on real hosts35 hosts
✓and reduces the number of hosts that challenge at all44 -> 29 challenged hosts
✓at least four distinct layers were exercised by real trafficL10 Turnstile, L12 Super Bot Fight Mode, L6 Browser and JavaScript fingerprint, L8 Per-zone behavioural model, L9 Managed JavaScript challenge
·reputation blocks (L1) observed: 0 of 330none
✓the reputation layer is rare here, so a browser buys more than a proxy would0 hosts
Behaviour — layer 8
passS04Warm-up and the referrer chain are visible on the wireL81.92s
What a pass proves. A deep page arriving with no referrer and no prior history is a navigation pattern no person produces; both mechanisms are observable in the requests actually sent.
✓the first navigation cites the origin's front pagehttps://httpbingo.org/
✓and sec-fetch-site agrees with itsame-origin
✓the next request cites the page before ithttps://httpbingo.org/html
✓fetch metadata says same-originsame-origin
✓a cold deep page wants a homepage visit firsthttps://example.com/
passS10A throttle widens the interval and keeps the addressL82.89s
What a pass proves. The rule that matters most: a 429 says the address works and is being asked for too much, so the remedy is arithmetic — not a new exit, which would reset the history the layer measures.
against https://httpbingo.org
✓gave up at the behavioural layer, not at reputationL8 Per-zone behavioural model
✓the trail shows a backoff and no rotationgave up after 2 attempts; rate limited [backoff -> direct after 1.0s: rate limited | stop: gave up after 2 attempts; rate limited]
✓the interval widened1.0 -> 2.0
✓the address was NOT rotateddirect == direct
passS16A throttle through Tor still does not rotateL83.26s
What a pass proves. The possess-side veto holds even when a rotation is cheaply available: discarding the address would reset the accumulated history the layer reads.
against https://httpbingo.org
✓gave up at the behavioural layer
✓no rotation appears in the trailgave up after 2 attempts; rate limited [backoff -> direct after 1.0s: rate limited | stop: gave up after 2 attempts; rate limited]
✓the Tor session was kept even though rotating was available127.0.0.1:9250#s-21975d8a9de8
✓the interval widened instead1.0
passS20What was learned survives the processL80.42s
What a pass proves. A process that forgets cannot accumulate, and the behavioural layer reads exactly what accumulates. The binding layer is the most valuable thing to keep.
against https://centralnovel.com/
·first run concludedbinding=L9 Managed JavaScript challenge failures=1
✓a fresh scraper starts from the conclusionL9 Managed JavaScript challenge
✓the ledger came back1
·the tier it would now start withdirect
✓the store is a real file
✓written owner-only, since it holds clearance cookies600
passS21Two scrapers on one host share one identityL81.52s
What a pass proves. Separate state would present as two visitors who contradict each other, arriving in bursts, one of them always cold.
against https://httpbingo.org
✓the second scraper sees the first's history1
✓both hold the same address
✓both use the same pacing clock
✓the referrer chain is sharedhttps://httpbingo.org/html
Addresses and tor-pool — layer 1
passS11Traffic leaves through a tor-pool exitL12.32s
What a pass proves. The pool is wired end to end: the session key becomes the SOCKS username, the credential authenticates, and the egress IP is a Tor exit.
against https://check.torproject.org/api/ip
·this machine's own address91.73.67.32 (IsTor=False)
·lease127.0.0.1:9250#s-0196914d41e6 via socks5h://s-0196914d41e6:tp_Ceif_S1QCscuzPEjVzy7TA@127.0.0.1:9250
✓egress is a Tor exit{'IsTor': True, 'IP': '192.42.116.67'}
passS12A session stays pinned to one exit across requestsL1L83.94s
What a pass proves. Stickiness is what makes a clearance reusable and lets per-zone history accrue. A rotating address would invalidate both.
against https://check.torproject.org/api/ip
✓all four requests left from one exit185.220.101.106, 185.220.101.106, 185.220.101.106, 185.220.101.106
·exit IP185.220.101.106
passS13Rotating moves the session to a different exitL13.35s
What a pass proves. Rotation is a reassignment inside the pool: the endpoint URL is unchanged, but the address behind it — and therefore the identity — is not.
against https://check.torproject.org/api/ip
✓the endpoint URL is unchangedsocks5h://127.0.0.1:9250
·exit IP before/after192.42.116.67 -> 192.42.116.67
✓egress is still Tor
passS14A failure report reaches the pool with the kind derived from the layerL1L92.61s
What a pass proves. This is the only signal that catches a soft block — a proxy relaying bytes cannot see a 403 or a captcha inside an HTTPS tunnel — and the pool weighs a report by its kind.
against http://127.0.0.1:8080
✓L9 Managed JavaScript challenge -> 'captcha'
✓L1 IP reputation -> 'blocked'
✓L8 Per-zone behavioural model -> 'rate_limited'
✓None -> 'transport'
✓exactly one instance was told it hit a captchabefore={0: 1, 1: 0, 2: 0, 3: 1, 4: 0} after={0: 1, 1: 0, 2: 1, 3: 1, 4: 0}
passS15Rotating between published ranges is refused, against live pool stateL10.0s
What a pass proves. THE headline rule. Every Tor exit is on the same published lists, so a reputation block cannot be fixed by another one — and the decision is driven by what the running pool actually offers, not by a hardcoded assumption.
against http://127.0.0.1:8080
·live pool offerstor, clearing layers []
✓a Tor pool clears nothing at layer 1
✓the planner refuses to rotateMove.STOP
✓and names the address kind as the constraintno configured exit clears the reputation layer — datacenter and Tor ranges are published, so rotating between them cannot help. A residential or mobile-carrier exit is the only thing that moves this layer
✓with a residential exit it does rotateMove.ROTATE
passS26The reputation layer is not uniformly hostile to TorL10.0s
What a pass proves. A negative result worth recording: assuming every Cloudflare host refuses Tor would make the address strategy look more important than it is on this corpus.
against 45 Cloudflare hosts via tor-pool
·hosts probed through a Tor exit45
✓most were served outright28/45 served
·challenged instead10/45
✓none produced a reputation block0 found
·consequenceon this corpus the binding layer is the managed challenge, not the address — so a browser solver buys more than a residential proxy would.
inconclusiveS28A real ASN ban is diagnosed as reputation and stops without wasting addressesL12.23s
What a pass proves. The naturally-occurring case: this host bans the machine's whole ASN, so no client change of any kind can help. With no alternative address the stop is immediate rather than after spending the rotation budget on the same address.
against https://www.readwn.com/
the host is not banning this network right now: accept
·observedaccept
The ladder
passS17A page is served from the archive with the original URLL043.27s
What a pass proves. The cheapest way past a protected site is not to touch it. The response must carry the real URL, or relative links redirect the whole crawl into the snapshot.
✓the response carries the ORIGINAL url, not the archive'shttps://centralnovel.com/
✓the capture timestamp is reported20260702085650
·this is a host the live stack refuseshttps://centralnovel.com/ serves a challenge to the direct tier (see S05)
passS23The ladder escalates on evidence and settles on the cheapest tier that worksL9L1430.47s
What a pass proves. The planner walks up only as far as required, and the archive rescues a host the live stack refuses.
against https://centralnovel.com/
·configured ladderarchive(0) -> direct(10)
✓the challenged host was retrievedHTTP 200, 210533 bytes
✓via the archive, which the planner reached firstarchive
·explain()centralnovel.com binding layer : nothing has blocked yet tier : archive pacing : 1.2s mean interval requests : 1 ok / 0 failed clearance : none ladder : archive(0) direct(10) exits : direct
passS27A real browser solves a real challenge, then the cheap tier reuses itL6L7L9L1021.67s
What a pass proves. solve-once-and-reuse: the expensive tier runs once and the clearance is replayed on the identity that earned it, so later pages cost one HTTP request.
against https://m.webnovel.com/
·direct tier against https://www.webnovel.com/{'status': 200, 'action': 'accept', 'layer': None}
·bound to User-AgentMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
·valid for1797s
✓reuse #1 served without a new solveHTTP 200, 1 solve(s) total
✓reuse #2 served without a new solveHTTP 200, 1 solve(s) total
✓reuse #3 served without a new solveHTTP 200, 1 solve(s) total
·explain()m.webnovel.com
binding layer : L9 Managed JavaScript challenge — reads a hybrid property, solve
tier : clearance
pacing : 1.5s mean interval
requests : 4 ok / 3 failed
clearance : 1787s left
ladder : direct(10) clearance(100)
exits : direct
Identity and content safety
passS18Hidden and nofollow links are dropped from real pagesL1716.97s
What a pass proves. The only layer that returns no error. Following a decoy link poisons the store and flags the session, and nothing in the response says so.
✓real links survive across every page{'kept': 1087, 'rejected': 8}
✓genuine decoy markers were found and dropped{'inside an element hidden by inline style': 1, 'rel=nofollow': 7}
·rejection reasons seen on real pages{'inside an element hidden by inline style': 1, 'rel=nofollow': 7}
·false positives fixed during this runicon-font anchors and overlay/text anchor pairs were being dropped; both are real navigation. 11 of 11 rejections on one host were wrong.
✓a page from the site is not suspected
✓off-topic prose is flaggedvocabulary overlap 0% against 3 known pages (threshold 30%)
passS19A clearance is refused under an identity that did not earn itL90.0s
What a pass proves. The classic rotating-proxy failure, made structurally impossible: the cookie is bound to the address, User-Agent and TLS profile together.
✓valid under the identity that earned it
✓refused after the address movedthe identity changed since the clearance was earned
✓refused after the User-Agent changedthe identity changed since the clearance was earned
✓refused after the TLS profile changedthe identity changed since the clearance was earned
passS22Requests are signed, and the signature verifiesL180.74s
What a pass proves. The one layer with no bypass. Deployed fail-open today, so a valid signature is a positive identification that skips the challenge machinery entirely.
against https://httpbingo.org
✓the server received Signature-Inputsig1=("signature-agent" "@authority");created=1785351281;expires=1785351581;keyid="yHjaAJTRFyisOAo6K4khacH2-oB3bMa7oFUM7_i5Zio";alg="ed25519";tag="web-bot-auth"
✓the server received Signaturesig1=:OOAHJJ2IjeU5x6O/IoYwUygWwCFs99sLGNiye49Sax+HOalZ/faEpkREm5rQe/ovL8+CrFL+4m
✓the agent was declared
✓the tag is web-bot-auth
✓the algorithm is ed25519
✓the signature verifies against the published key
✓and does not verify for another authority
·directory to publish/.well-known/http-message-signatures-directory -> kid yHjaAJTRFyisOAo6K4khacH2-oB3bMa7oFUM7_i5Zio
Other
passS29A proxy that refuses our credential is not blamed on the siteL00.02s
What a pass proves. A failure on our side of the proxy must not become a detection story. The SOCKS5 handshake has no status code, so this arrives as a bare transport error — and attributing it to layer 1 caused a rotation, a false 'blocked' report against a healthy exit, and a persisted verdict that the site refuses our address.
against tor-pool with a deliberately wrong token
✓nothing is attributed to a layerNone
✓the message names the credential, not the sitethe proxy rejected the credential (SOCKS5 handshake) [stop: the proxy rejected the credential (SOCKS5 handshake)]
✓no rotation was attemptedthe proxy rejected the credential (SOCKS5 handshake) [stop: the proxy rejected the credential (SOCKS5 handshake)]
✓no layer was written to the origin's memory — the durable halfNone
✓no pool instance was reported as failing{0: 0, 1: 0, 2: 0, 3: 0, 4: 0} -> {0: 0, 1: 0, 2: 0, 3: 0, 4: 0}
Environment
scraper
1.0.0
Python (harness)
Python 3.9.6
Python (browser tier)
Python 3.12.13
curl_cffi
0.13.0
impersonation profile
chrome136
nodriver
0.50.3
browser
Google Chrome (headed)
tor-pool
tor-pool:sessionscope
tor-pool exits
5 instances
local egress
91.73.67.32
Reproducing this
uv run poe live-probe # classify every host in lncrawl's source index (~4 min)
uv run poe live-tor # classify Cloudflare hosts through tor-pool
uv run poe live # run every scenario
uv run poe live-report # rebuild this page
# the clearance tier needs its own interpreter (nodriver: Python 3.10-3.13) and Chrome
/tmp/scr312/bin/python livetest/clearance.py
Requirements and the politeness rules are in
livetest/README.md. Targets are looked up from the probe rather than
hardcoded, because site configuration moves: one host in this corpus switched from
Turnstile to plain scoring between two runs an hour apart.