Skip to content

Enable the x402 gateway's edge controls (0.2.2): OVH denylist, spoofed-browser toll, signed-in exemption - #207

Merged
ralyodio merged 2 commits into
masterfrom
edge-controls
Sep 5, 2026
Merged

Enable the x402 gateway's edge controls (0.2.2): OVH denylist, spoofed-browser toll, signed-in exemption#207
ralyodio merged 2 commits into
masterfrom
edge-controls

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

What changes, for whom

Bumps @profullstack/x402-gateway 0.1.0 -> 0.2.2 and turns on its edge controls in src/lib/crawl-gateway.ts. The declared-crawler behaviour from #205 is unchanged; this adds handling for crawlers that do not declare themselves.

Visitor Before After
A person in Chrome, Firefox, Safari site site, unchanged: real Chromium always sends Sec-Fetch-Mode; Firefox and Safari are never judged
Anyone signed in (Supabase session cookie) or a v1 API client (Authorization: Bearer btr_...) site site, never charged whatever else the request looks like (exempt)
Googlebot / Bingbot evergreen strings (... compatible; Googlebot/2.1 ... Chrome/148 ..., no Sec-Fetch) site site; 0.2.2 never applies the spoof check to anything that declares itself
An HTTP client claiming Chrome/... with no Sec-Fetch-Mode (VPS fleets, residential-proxy rotations) site 402 with the x402 offer, same as GPTBot; /robots.txt and /crawl still readable
Last-hop address in an OVH VPS fleet range (51.38/16, 54.38/16, 141.94/16, 145.239/16, 149.202/16, 151.80/16, 57.129/16, 213.32/16) site tiny 403 Not available from this network. before anything else, session or not

The OVH ranges were measured 2026-08-28 on rssamplifier: vps-*.vps.ovh.net hosts spoofing Chrome/148. The list is a named constant with that provenance in a comment.

Notes for review

  • exempt runs before the agent lists in 0.2.2, so a request presenting a valid-looking session is let through even under a declared training UA. That is the intended trade for a site that is mostly people, and it is pinned by a test. "Valid-looking" means the sb-auth-token JSON has a JWT-shaped access_token and a refresh_token; a junk cookie merely named sb-auth-token does not qualify (tested). No verification happens in the gate; the ordinary session refresh runs afterwards as before.
  • The bearer exemption matches the shape of src/lib/api-tokens tokens (btr_ + 64 hex) only; the route still verifies the hash.
  • The gateway reads the client IP from x-real-ip, else the last x-forwarded-for hop (the hop our own edge appends). The existing rate limiter in src/proxy.ts reads the first hop; that is pre-existing and untouched, but worth knowing: the denylist cannot be stepped around by seeding XFF, the rate limiter can.
  • Test helpers for browsers now send sec-fetch-mode: navigate, as every real Chromium does; the edge-control tests build their own requests with exactly the headers under test.

Files

  • package.json, pnpm-lock.yaml: @profullstack/x402-gateway 0.2.2.
  • src/lib/crawl-gateway.ts: denyCidrs, chargeSpoofedBrowsers: true, exempt (+ exported hasSessionCookie, hasApiBearer).
  • src/proxy.test.ts: +16 edge-control tests; one session test split in two to reflect the exemption.

Checks

No new env vars. Not merged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC

Bump @profullstack/x402-gateway to 0.2.2 and switch on the two checks
for crawlers that do not say who they are:

- denyCidrs: the OVH VPS fleet ranges measured 2026-08-28 on
  rssamplifier (vps-*.vps.ovh.net spoofing Chrome/148) get a tiny 403
  before anything else. Last x-forwarded-for hop / x-real-ip only, so a
  client-seeded first hop cannot get anyone refused.
- chargeSpoofedBrowsers: a "Chrome/..." user agent with no
  Sec-Fetch-Mode header is an HTTP client wearing a copied string and is
  charged like GPTBot. 0.2.2 never judges anything that declares itself
  (Googlebot's and Bingbot's evergreen strings pass), nor Firefox/Safari.
- exempt: a request carrying a valid-looking Supabase session
  (sb-auth-token JSON with a JWT-shaped access_token) or a valid-looking
  v1 API bearer token (btr_ + 64 hex) is never charged. This site is
  mostly real people, so this is the control that matters most.

Tests: OVH last hop and x-real-ip 403 across every listed range, first
hop ignored; spoofed Chrome 402, real Chrome passes, Googlebot/Bingbot
evergreen and Firefox pass, robots.txt and /crawl still readable;
session cookie and API bearer exempt, junk lookalikes are not, and a
session does not get a hosting range past the 403. The existing browser
helpers now send Sec-Fetch-Mode like every real Chromium.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC
Comment thread src/proxy.test.ts Fixed
Comment thread src/proxy.test.ts Fixed
Comment thread src/proxy.test.ts Fixed
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

99 finding(s)

HIGH/CRITICAL: 11 | MEDIUM: 29 | LOW: 59

Severity Rule Location
HIGH secret-private-key src/app/settings/seedbox-section.tsx:412
HIGH secret-generic-api-key docs/incidents/2026-05-okshanaby-supply-chain.md:18
HIGH tls-verification-disabled src/app/api/iptv-proxy/route.ts:38
HIGH tls-verification-disabled src/app/api/iptv/channels/route.ts:35
HIGH tls-verification-disabled src/app/api/iptv/playlists/[id]/route.ts:73
HIGH tls-verification-disabled src/app/api/iptv/playlists/route.ts:64
HIGH js-cors-origin-reflected src/app/api/public/shares/[slug]/checkout/route.ts:40
HIGH js-cors-origin-reflected src/app/api/public/vod/[slug]/checkout/route.ts:34
HIGH tls-verification-disabled src/lib/iptv/shares/upstream.ts:37
HIGH tls-verification-disabled workers/iptv-cache/epg-fetcher.ts:25
HIGH tls-verification-disabled workers/iptv-cache/playlist-fetcher.ts:62
MEDIUM secret-jwt .github/workflows/ci.yml:120
MEDIUM secret-jwt .github/workflows/ci.yml:121
MEDIUM secret-jwt .github/workflows/ci.yml:123
MEDIUM secret-jwt .github/workflows/ci.yml:162
MEDIUM secret-jwt .github/workflows/ci.yml:164
MEDIUM secret-jwt docs/tunein (2).py:9
MEDIUM secret-jwt docs/tunein.py:9
MEDIUM sh-remote-script-execution scripts/setup-server.sh:182
MEDIUM sh-remote-script-execution scripts/setup-server.sh:419
MEDIUM sh-remote-script-execution scripts/setup-server.sh:428
MEDIUM sh-unquoted-expansion-destructive scripts/setup-server.sh:1073
MEDIUM sh-unquoted-expansion-destructive scripts/setup-server.sh:1083
MEDIUM js-unescaped-html-sink src/app/api/player/route.ts:110
MEDIUM js-unescaped-html-sink src/app/api/player/route.ts:249
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.tsx:40
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.tsx:66
MEDIUM js-unescaped-html-sink src/app/email/email-content.tsx:566
MEDIUM js-open-redirect src/app/login/page.tsx:50
MEDIUM js-open-redirect src/app/pricing/page.tsx:161
MEDIUM js-open-redirect src/app/rent/[slug]/rent-client.tsx:170
MEDIUM js-unescaped-html-sink src/app/rss/rss-content.tsx:615
MEDIUM js-open-redirect src/app/vod/[slug]/vod-client.tsx:134
MEDIUM js-open-redirect src/app/watch/[slug]/watch-client.tsx:129
MEDIUM js-unescaped-html-sink src/app/youtube/youtube-content.tsx:546
MEDIUM js-open-redirect src/components/account/iptv-subscription-section.tsx:124
MEDIUM js-open-redirect src/components/account/iptv-subscription-section.tsx:156
MEDIUM js-unescaped-html-sink src/components/news/news-section.tsx:361
MEDIUM js-unescaped-html-sink src/components/news/news-section.tsx:734
MEDIUM redos-nested-quantifier src/lib/metadata-enrichment/metadata-enrichment.ts:317
LOW tls-verification-disabled docs/tunein (2).py:34
LOW tls-verification-disabled docs/tunein (2).py:37
LOW py-xpath-injection docs/tunein (2).py:37
LOW tls-verification-disabled docs/tunein (2).py:47
LOW py-xpath-injection docs/tunein (2).py:47
LOW tls-verification-disabled docs/tunein.py:34
LOW tls-verification-disabled docs/tunein.py:37
LOW py-xpath-injection docs/tunein.py:37
LOW tls-verification-disabled docs/tunein.py:47
LOW py-xpath-injection docs/tunein.py:47

…and 49 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@socket-security

socket-security Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​profullstack/​x402-gateway@​0.1.0 ⏵ 0.2.276 +1100100 +188100

View full report

The code-scanning gate flagged src/proxy.test.ts:337, the JWT-shaped
session-cookie fixture, under secret-jwt, secret-generic-credential and
secret-generic-api-key. The suppression parser takes one rule id per
directive and each disable-next-line covers only its own next line, so
the fixture is split: the unsigned, 1970-expired JWT and the one-letter
refresh token each sit on their own line behind a named directive with
the reason written above. The third rule keyed on the combined line and
no longer fires. Verified with the CLI the workflow installs
(@profullstack/threatcrush@latest, 0.11.9): 2 suppressed, 0 findings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC
@ralyodio
ralyodio merged commit cad22fe into master Sep 5, 2026
9 checks passed
@ralyodio
ralyodio deleted the edge-controls branch September 5, 2026 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants