Refuse the OVH fleet by address and charge browsers that cannot prove it - #174
Merged
Conversation
Two callers the crawler lists never see. The OVH VPS fleet measured 2026-08-28 (vps-*.vps.ovh.net, spoofing Chrome/148) is now answered 403 from its eight /16s before anything else runs. The residential-proxy rotation seen on 2026-09-05 -- 499 distinct addresses in 500 requests, never more than two hits from one, cycling three generic Chrome strings over /topics/*, /api/topics/* and /authors/* at 25 to 250 requests a second, 78% of a burst shed by the in-flight cap -- cannot be named by address, so it is asked a question only a browser can answer: a request that claims Chrome and sends no Sec-Fetch-Mode is charged like GPTBot, 402 and the offer, a hash instead of a render. Readers with a session, programs with an API key, and crawlers that name themselves on the retrieval or search-engine lists are exempt, because Googlebot's evergreen string also claims Chrome and sends no browser headers. The training half of a pair (Applebot-Extended) stays charged. @profullstack/x402-gateway 0.1.0 -> 0.2.1, which adds denyCidrs, chargeSpoofedBrowsers and exempt, and reads the client address from X-Real-IP or the last X-Forwarded-For hop. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two callers the crawler lists in #173 never see, both from the edge logs:
vps-*.vps.ovh.net, every request wearing a spoofedChrome/148. Zero hits today, but the owner wants the ranges shut./topics/*,/api/topics/*and/authors/*at 25 to 250 req/s, with 78% of a burst shed byserver.mjs's in-flight cap. No address list can touch it.The two behaviours
denyCidrs— the eight OVH /16s (51.38,54.38,141.94,145.239,149.202,151.80,57.129,213.32) are answered a tiny403 Not available from this network.before any other check, including/crawl. Not 402: there is nothing on sale to a hosting range that will not say who it is. The address isX-Real-IPor the lastX-Forwarded-Forhop (the one the edge appended); a client-seeded first entry is ignored.chargeSpoofedBrowsers: true— a request that claimsChrome/…and sends noSec-Fetch-Mode(which every Chromium since 76 sends on every request, and which is a forbidden header no script or extension can remove) is charged like GPTBot:402plus the x402 offer, a hash instead of a render. Firefox and Safari are not judged. A real browser, headless Chrome included, passes untouched.Exempt, never charged (
exemptoption,apps/web/src/lib/crawl-gateway.js):signed_inhint cookie the masthead reads, or thersa_sessioncookie it describes;rsa_<8 hex>_<secret>) asAuthorization: BearerorX-API-Key. Shape only: verifying it is a database lookup thatlib/tiers.jsdoes a moment later, and this module has to stay edge-clean (@rssamplifier/authimportsnode:crypto). A made-up key gets past the gate and lands in the anonymous tier, metered like a curl user agent already is;Files
apps/web/src/lib/crawl-gateway.js—DENY_CIDRS,exempt,chargeSpoofedBrowsers: true, with the measurements in the comments.apps/web/test/crawl-gateway.test.js— 7 new tests: each OVH range refused even with a full browser header set (last hop, single hop,x-real-ip; a seeded first entry54.38.1.2, 203.0.113.9is not refused;203.0.113.9, 54.38.1.2is); the three rotation strings get 402 on/topics/x,/api/topics/x,/authors/*; the same strings withsec-fetch-mode: navigate(andcors,no-cors) pass; hint cookie / session cookie pass, emptied session and look-alike cookie name do not; bearer andx-api-keykeys pass,Bearer nopedoes not; evergreen Googlebot, Bingbot, OAI-SearchBot pass while Applebot-Extended and ClaudeBot are charged;/crawlstill answers and the deny list still wins there.apps/web/package.json,pnpm-lock.yaml,pnpm-workspace.yaml—@profullstack/x402-gateway0.1.0 → 0.2.1 (pinned),minimumReleaseAgeExcludeentry updated.No new environment variables.
Verified
pnpm -r test: 1,469 tests, 0 failures, 12 workspaces (apps/web 382, 7 new).pnpm --filter @rssamplifier/web build: compiled in 15 s,ƒ Proxy (Middleware)present.One thing worth knowing:
lib/crawlThrottle.js'scallerAddressstill keys on the firstX-Forwarded-Forentry (its test says so explicitly), while the gateway now reads the last. On Railway the header carries one entry so both agree; behind a second hop they would not, and the throttle is the one that would be reading a client-seeded value.🤖 Generated with Claude Code
https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC