Skip to content

fix(crawl): a verified API key or session bearer is never charged; one fetch reads the page - #665

Merged
sh1pt-actions-fleet[bot] merged 1 commit into
masterfrom
riot/plain-fetch-1028
Oct 28, 2026
Merged

sh1pt-actions-fleet[bot] merged 1 commit into
masterfrom
riot/plain-fetch-1028

Conversation

@sh1pt-actions-fleet

Copy link
Copy Markdown
Contributor

Problem

An agent seller with no session reported GET /api/gigs/<id> and /gigs/<id> returning the x402 /crawl 402 for Chrome/129 and Chrome/141 UAs. A bogus Authorization: Bearer also got 402, not 401. A seller with a valid API key could not read its own listing, views or orders.

Cause: chargeSpoofedBrowsers charges any Chrome UA without Sec-Fetch-Mode on every path, API included, before anything checks who is asking (Chrome/129 also trips the stale rule).

Fix

  • @profullstack/x402-gateway 0.16.0 (0.16.0: one fetch reads the page; a site's verified key is never charged x402-gateway#29) + throttle 0.4.1.
  • New src/lib/crawl-auth.ts, wired as the gateway's authenticate hook. It VERIFIES the credential: a ugig API key (Bearer/ApiKey/Token/x-api-key, checked against the DB), a Supabase access token (GoTrue) or an AgentPass signature. Verified means never charged. A credential that is ours but wrong gets 401 JSON. A crawl pass (cp_), Power Key or no credential falls through to the normal rules. Only verified results are cached (60s), so a polling integration does not pay bcrypt twice per request.
  • Suspect browser strings (spoofed/stale/headless) now read 20 pages an hour per address before the 402, so one fetch reads and a bulk crawl still pays.

Tests

Full suite 3152+ pass, tsc clean. New crawl-auth.test.ts (9 cases). crawl-gateway.test.ts now covers the allowance and the 401 for a bogus bearer.

🤖 Generated with Claude Code

…e fetch reads the page

x402-gateway 0.16.0 + throttle 0.4.1. The gate now asks crawl-auth.ts first:
a ugig API key (Bearer/ApiKey/Token/x-api-key), Supabase access token or
AgentPass signature that VERIFIES is never charged; one that is ours and
wrong gets 401 instead of the crawl offer. A Chrome UA without Sec-Fetch now
reads 20 pages an hour per address before the 402, so an agent reading its
own listing once gets it and a bulk crawl still pays.

Reported 2026-10-28: GET /api/gigs/<id> and /gigs/<id> with Chrome/129 or
Chrome/141 answered 402, and a bogus bearer got 402 rather than 401.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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
Updated@​profullstack/​throttle@​0.4.0 ⏵ 0.4.17810010095 +1100
Updated@​profullstack/​x402-gateway@​0.15.3 ⏵ 0.16.084100100 +197100

View full report

@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

66 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 12 | LOW: 53

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:80
MEDIUM js-open-redirect src/app/checkout/CheckoutClient.tsx:77
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:102
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:114
MEDIUM js-unescaped-html-sink src/app/page.tsx:70
MEDIUM js-unescaped-html-sink src/app/pricing/page.tsx:84
MEDIUM js-open-redirect src/components/funding/FundingClient.tsx:138
MEDIUM js-open-redirect src/components/gigs/BuyNowButton.tsx:120
MEDIUM js-unescaped-html-sink src/components/layout/PitFooter.tsx:25
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-credential cli/src/commands/webhooks.test.ts:60
LOW secret-generic-credential cli/src/commands/webhooks.test.ts:72
LOW secret-generic-credential cli/src/commands/webhooks.test.ts:109
LOW secret-generic-credential cli/src/commands/webhooks.test.ts:112
LOW secret-generic-api-key docs/agents/integration-guide.md:893
LOW secret-generic-credential src/app/api/auth/agent-register/route.test.ts:27
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:172
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:196
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:215
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:228
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:267
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:297
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:329
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:346
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:46
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:118
LOW secret-generic-credential src/app/api/gigs/[id]/buy/route.test.ts:177
LOW secret-generic-credential src/app/api/orders/[id]/release/route.test.ts:18
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 secret-generic-credential src/lib/coinpay-oauth.test.ts:20
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

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

Snippets are redacted; ThreatCrush never prints matched credential material.

@sh1pt-actions-fleet
sh1pt-actions-fleet Bot merged commit 90b5c7e into master Oct 28, 2026
6 checks passed
@sh1pt-actions-fleet
sh1pt-actions-fleet Bot deleted the riot/plain-fetch-1028 branch October 28, 2026 23:30
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