Skip to content

Charge AI training crawlers for access (x402 gateway) - #545

Merged
ralyodio merged 1 commit into
masterfrom
x402-gateway
Sep 5, 2026
Merged

Charge AI training crawlers for access (x402 gateway)#545
ralyodio merged 1 commit into
masterfrom
x402-gateway

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

What changes, for whom

Visitor Before After
People, Googlebot, Bingbot, Applebot the site the site, unchanged
Retrieval crawlers behind AI search (OAI-SearchBot, ChatGPT-User, Claude-SearchBot, PerplexityBot, Google-Extended…) the site the site, and named as welcome in robots.txt
Training crawlers (GPTBot, ClaudeBot, CCBot, meta-externalagent, FacebookBot, Bytespider, Applebot-Extended) the site, free 402 Payment Required with an x402 offer (JSON), or the sales page if they ask for HTML; robots.txt says Disallow: / + Allow: /crawl
Anyone at /crawl 404 the sales page: $1 buys a day of access, paid in USDC via coinpay x402 pay or any x402 client
A request carrying a valid pass in x-crawl-pass n/a the site

Last week 65% of hits were training crawlers (Meta and Applebot). This is @profullstack/x402-gateway 0.1.0, settled by CoinPay.

Files

  • src/lib/crawl-gateway.ts builds the gateway (site URL from getAppUrl(), siteName: "ugig", bulk-deal contact support@ugig.net) and exports gate.
  • proxy.ts runs gate(request) first and returns its answer if there is one; otherwise everything works as before (TRACE block, polling throttle, Supabase session refresh, ref cookie). /api/ routes stay covered on purpose: a training crawler hitting the API is charged like everywhere else. The matcher additionally skips fonts, scripts, css, source maps and media by extension.
  • src/app/robots.txt/route.ts replaces the static public/robots.txt (deleted: a static file would shadow the route) with robotsRoute(gateway, { disallow: [...] }), so robots.txt and the gate are generated from one list; the previous private paths (/api/, /dashboard/, /settings/, /conversations/, /profile/edit) are repeated in every named group, since a crawler that finds its own name ignores User-agent: *.
  • public/ai.txt: Training: disallow (was allow), Retention/Commercial-Use likewise, with a comment pointing at /crawl. crawl stays in Capabilities, Contact unchanged.
  • src/lib/crawl-gateway.test.ts: robots output refuses GPTBot and meta-externalagent with Disallow: /, names OAI-SearchBot with Allow: /, keeps Disallow: /dashboard/; the gate 402s a meta-externalagent request to /gigs/anything and to /api/gigs, serves HTML on Accept: text/html, lets crawlers read robots.txt, passes Chrome/Googlebot/OAI-SearchBot/Claude-SearchBot/PerplexityBot through; the proxy answers a crawler before updateSession runs and hands a person's request on.
  • .env.example documents the two new variables.

Deployment needs two env vars

Variable Value
COINPAY_X402_KEY a scoped CoinPay API key (cp_live_…, from the business's API Keys tab, with payments:create). The legacy business key is refused by CoinPay's x402 routes.
CRAWL_PAY_TO the EVM address that receives the USDC (same address on Base, Polygon and Ethereum)

Until they are set, training crawlers still get 402, but with an empty offer: nothing is sold, nothing is given away. Set both on the Railway service before or after merging; the behaviour switches on at the next deploy. The pass signing secret defaults to the CoinPay key, so rotating the key invalidates outstanding passes (at most a day's worth).

Checks

  • pnpm type-check: clean
  • pnpm lint: 0 errors (40 pre-existing warnings, none in the changed files)
  • pnpm test:run: 215 files, 2044 tests passed
  • pnpm@9 install --frozen-lockfile from scratch: passes (lockfile diff is the single new entry)
  • pnpm build not run

Worth knowing

  • Matching is a substring of the self-declared user agent. A crawler wearing a browser UA walks past this; that needs blocking at the edge.
  • Plain Applebot (Siri/Spotlight search) stays free; only Applebot-Extended (training) is charged. Same split for every operator pair.
  • Do not merge yet: draft until the CoinPay key and payout address exist.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC

65% of last week's hits were training crawlers, mostly Meta and Applebot.
They copy pages into a corpus and send nobody back, so from now on they
pay: GPTBot, ClaudeBot, CCBot, meta-externalagent, Bytespider,
Applebot-Extended and friends get 402 Payment Required with an x402 offer
($1 buys a day, USDC settled by CoinPay) or the sales page at /crawl, and
a paid pass in x-crawl-pass lets them through. People, Googlebot and the
retrieval crawlers behind AI search (OAI-SearchBot, Claude-SearchBot,
PerplexityBot...) are untouched.

- src/lib/crawl-gateway.ts builds the gateway from getAppUrl(),
  COINPAY_X402_KEY and CRAWL_PAY_TO; proxy.ts runs it before the polling
  throttle and the Supabase session refresh. API routes stay covered on
  purpose. The matcher now also skips fonts, scripts, css and media.
- robots.txt is a route handler generated from the gateway's lists
  (training crawlers Disallow: / with Allow: /crawl, retrieval crawlers
  named as welcome, the old private paths kept in every group); the
  static public/robots.txt is deleted because it would shadow the route.
- public/ai.txt now says Training: disallow and points at /crawl.
- Tests cover the robots output, the gate's 402/pass-through decisions
  and the proxy composition order.

Without the two env vars the gateway still answers training crawlers with
402, just with an empty offer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

45 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 8 | LOW: 36

Severity Rule Location
HIGH js-ssrf-outbound-request scripts/scan-all-skills.ts:38
MEDIUM js-open-redirect src/app/agent-login/AgentLoginForm.tsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.tsx:79
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:90
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:113
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:144
MEDIUM js-open-redirect src/components/funding/FundingClient.tsx:137
MEDIUM js-dynamic-code-execution src/lib/skills/metadata-extract.ts:300
MEDIUM js-dynamic-code-execution src/lib/skills/security-scan.ts:48
LOW secret-generic-credential cli/src/commands/auth.test.ts:66
LOW secret-generic-credential cli/src/commands/auth.test.ts:85
LOW secret-generic-api-key docs/agents/integration-guide.md:893
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:53
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:68
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:87
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:158
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:182
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:193
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:232
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:212
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:223
LOW js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:239
LOW secret-generic-credential src/lib/api.test.ts:126
LOW secret-generic-credential src/lib/api.test.ts:131
LOW js-dynamic-code-execution src/lib/skills/composite-scanner.test.ts:106
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:36
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:44
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:66
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:81
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:94
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:103
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:118
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:144
LOW js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:162
LOW js-dynamic-code-execution src/lib/skills/security-scan.test.ts:28
LOW js-dynamic-code-execution src/lib/skills/url-import.test.ts:178
LOW js-dynamic-code-execution src/lib/skills/url-import.test.ts:191
LOW secret-generic-credential src/lib/validations.test.ts:148
LOW secret-generic-credential src/lib/validations.test.ts:512
LOW secret-generic-credential src/lib/validations.test.ts:523
LOW secret-generic-credential src/lib/validations.test.ts:538
LOW secret-generic-credential src/lib/validations.test.ts:548
LOW secret-generic-credential src/lib/validations.test.ts:557
LOW secret-generic-credential src/lib/validations.test.ts:567
LOW secret-generic-credential src/lib/validations.test.ts:582

Snippets are redacted; ThreatCrush never prints matched credential material.

@socket-security

Copy link
Copy Markdown
Contributor

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

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​profullstack/​x402-gateway@​0.1.07610010088100

View full report

@ralyodio
ralyodio marked this pull request as ready for review September 5, 2026 16:31
@ralyodio
ralyodio merged commit 25eecdb into master Sep 5, 2026
6 checks passed
@ralyodio
ralyodio deleted the x402-gateway branch September 5, 2026 16:31
ralyodio added a commit that referenced this pull request Sep 5, 2026
With the app under src/app, Next 16 only looks for src/proxy.ts (or
src/middleware.ts); a proxy.ts at the repository root is silently ignored
and `next build` lists no "ƒ Proxy (Middleware)" entry. That is why #545
deployed and changed nothing: GPTBot still got 200 on / and /crawl was a
404 while the robots.txt route, a plain route handler, went live. It also
means the TRACE block, the polling throttle, the Supabase session refresh
with its /dashboard login redirect, and the ref cookie in that file have
never run in production.

git mv, the one relative import in the test switched to @/proxy, nothing
else changed. The build now lists "ƒ Proxy (Middleware)", and against a
local `next start`: GPTBot on / is 402 with the x402 offer, /crawl is 402
(JSON for the crawler, the HTML sales page for a browser), Chrome on / is
200, and /dashboard without a session is a 307 to /login.


Claude-Session: https://claude.ai/code/session_01WJaXiqE9BDoNfoJBhfXroC

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant