Skip to content

Refuse private targets on the audit's page fetch - #232

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

ralyodio merged 1 commit into
masterfrom
x402-ssrf-guard

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #231, whose CodeQL run flagged the paying fetch as SSRF. The audit fetches customer-submitted URLs with no address check (the pre-existing fetch(url) had the same hole). lib/net-guard.ts resolves the host and refuses private/loopback/link-local/CGNAT/multicast answers before the request and on the final URL after redirects. CRAWLPROOF_ALLOW_PRIVATE_TARGETS=1 disables it for local development. 5 new tests; tsc clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NSGS7Sy3QmEgTNZDg4eq7g

CodeQL flagged the paying fetch in #231 as server-side request forgery,
and it was right about the shape if not the novelty: the audit fetches
whatever URL a customer submits, and nothing checked where that resolved.
A submitted http://169.254.169.254/ would have read the cloud metadata
service from our server. The old fetch(url) in smartFetch had the same
hole; the wrapper only made the flow visible.

lib/net-guard.ts resolves the host and refuses any private, loopback,
link-local, CGNAT or multicast answer — the same rules outreach's mailbox
discovery applies — before the request, and again on the final URL after
redirects. CRAWLPROOF_ALLOW_PRIVATE_TARGETS=1 turns it off for auditing a
local server in development. Link-status probes and uptime pings remain on
the plain fetch as before.

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

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

39 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 28 | LOW: 9

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

Comment thread lib/paid-fetch.ts Dismissed
Comment thread lib/paid-fetch.ts Dismissed
@ralyodio
ralyodio merged commit 97f9956 into master Sep 5, 2026
10 checks passed
@ralyodio
ralyodio deleted the x402-ssrf-guard branch September 5, 2026 21:46
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