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.
New SQS For Stripe Links #706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Uh oh!
There was an error while loading. Please reload this page.
New SQS For Stripe Links #706
Changes from all commits
b4f30647a38e1288eef1abb8db25d705b017798d6bFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
There are no files selected for viewing
Check warning on line 1 in src/api/functions/subscriberCallback.ts
Prefer `node:crypto` over `crypto`.
Check warning on line 2 in src/api/functions/subscriberCallback.ts
Prefer `node:dns/promises` over `dns/promises`.
Check warning on line 3 in src/api/functions/subscriberCallback.ts
Prefer `node:net` over `net`.
Check warning on line 7 in src/api/functions/subscriberCallback.ts
Make sure using a hardcoded IP address 10.0.0.0 is safe here.
Check notice
Code scanning / SonarCloud
IP addresses should not be hardcoded Low
Check warning on line 8 in src/api/functions/subscriberCallback.ts
Make sure using a hardcoded IP address 172.16.0.0 is safe here.
Check notice
Code scanning / SonarCloud
IP addresses should not be hardcoded Low
Check warning on line 9 in src/api/functions/subscriberCallback.ts
Make sure using a hardcoded IP address 192.168.0.0 is safe here.
Check notice
Code scanning / SonarCloud
IP addresses should not be hardcoded Low
Check warning on line 11 in src/api/functions/subscriberCallback.ts
Make sure using a hardcoded IP address 169.254.0.0 is safe here.
Check notice
Code scanning / SonarCloud
IP addresses should not be hardcoded Low
Check warning on line 13 in src/api/functions/subscriberCallback.ts
Make sure using a hardcoded IP address 100.64.0.0 is safe here.
Check notice
Code scanning / SonarCloud
IP addresses should not be hardcoded Low
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SSRF coverage gap: missing special-use IPv4 ranges.
The blocklist omits several non-routable / special-use ranges that should not be valid callback destinations:
224.0.0.0/4— multicast240.0.0.0/4— reserved/future198.18.0.0/15— benchmarking (RFC 2544)192.0.0.0/24— IETF protocol assignments192.0.2.0/24,198.51.100.0/24,203.0.113.0/24— documentation (RFC 5737)Any of these reaching
fetchcould either fail unexpectedly or, on misconfigured networks, reach internal services.🛡️ Proposed additions
const PRIVATE_IPV4_RANGES: { cidr: string; mask: number }[] = [ { cidr: "10.0.0.0", mask: 8 }, { cidr: "172.16.0.0", mask: 12 }, { cidr: "192.168.0.0", mask: 16 }, { cidr: "127.0.0.0", mask: 8 }, { cidr: "169.254.0.0", mask: 16 }, { cidr: "0.0.0.0", mask: 8 }, { cidr: "100.64.0.0", mask: 10 }, + { cidr: "224.0.0.0", mask: 4 }, // multicast + { cidr: "240.0.0.0", mask: 4 }, // reserved + { cidr: "198.18.0.0", mask: 15 }, // benchmarking + { cidr: "192.0.0.0", mask: 24 }, // IETF protocol assignments + { cidr: "192.0.2.0", mask: 24 }, // TEST-NET-1 + { cidr: "198.51.100.0", mask: 24 }, // TEST-NET-2 + { cidr: "203.0.113.0", mask: 24 }, // TEST-NET-3 ];📝 Committable suggestion
🧰 Tools
🪛 GitHub Check: SonarCloud
[notice] 7-7: IP addresses should not be hardcoded
Make sure using a hardcoded IP address 10.0.0.0 is safe here.See more on SonarQube Cloud
[notice] 8-8: IP addresses should not be hardcoded
Make sure using a hardcoded IP address 172.16.0.0 is safe here.See more on SonarQube Cloud
[notice] 9-9: IP addresses should not be hardcoded
Make sure using a hardcoded IP address 192.168.0.0 is safe here.See more on SonarQube Cloud
[notice] 11-11: IP addresses should not be hardcoded
Make sure using a hardcoded IP address 169.254.0.0 is safe here.See more on SonarQube Cloud
[notice] 13-13: IP addresses should not be hardcoded
Make sure using a hardcoded IP address 100.64.0.0 is safe here.See more on SonarQube Cloud
🪛 GitHub Check: SonarCloud Code Analysis
[warning] 8-8: Make sure using a hardcoded IP address 172.16.0.0 is safe here.
See more on https://sonarcloud.io/project/issues?id=acm-uiuc_core&issues=AZ4j1gOuz_qhAFfqmu2D&open=AZ4j1gOuz_qhAFfqmu2D&pullRequest=706
[warning] 13-13: Make sure using a hardcoded IP address 100.64.0.0 is safe here.
See more on https://sonarcloud.io/project/issues?id=acm-uiuc_core&issues=AZ4j1gOuz_qhAFfqmu2G&open=AZ4j1gOuz_qhAFfqmu2G&pullRequest=706
[warning] 9-9: Make sure using a hardcoded IP address 192.168.0.0 is safe here.
See more on https://sonarcloud.io/project/issues?id=acm-uiuc_core&issues=AZ4j1gOuz_qhAFfqmu2E&open=AZ4j1gOuz_qhAFfqmu2E&pullRequest=706
[warning] 7-7: Make sure using a hardcoded IP address 10.0.0.0 is safe here.
See more on https://sonarcloud.io/project/issues?id=acm-uiuc_core&issues=AZ4j1gOuz_qhAFfqmu2C&open=AZ4j1gOuz_qhAFfqmu2C&pullRequest=706
[warning] 11-11: Make sure using a hardcoded IP address 169.254.0.0 is safe here.
See more on https://sonarcloud.io/project/issues?id=acm-uiuc_core&issues=AZ4j1gOuz_qhAFfqmu2F&open=AZ4j1gOuz_qhAFfqmu2F&pullRequest=706
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IPv6 SSRF bypass: IPv4-mapped addresses not detected.
isPrivateIPv6does not handle IPv4-mapped IPv6 addresses (::ffff:0:0/96). A hostname with an AAAA record like::ffff:127.0.0.1or::ffff:10.0.0.1will return fromisIP()as version 6 and pass this check, thenfetchwill route the request to the embedded IPv4 address — defeating the IPv4 private-range guard entirely.Also worth covering for completeness:
::(unspecified)::ffff:0:0/96(IPv4-mapped)64:ff9b::/96(NAT64)2001:db8::/32(documentation)🛡️ Suggested approach
When
isIP(host) === 6, extract the embedded IPv4 from IPv4-mapped forms and re-runisPrivateIPv4on it before doing the prefix-based checks. Example sketch:const isPrivateIPv6 = (ip: string): boolean => { const normalized = ip.toLowerCase(); + if (normalized === "::" || normalized === "::1") { + return true; + } + // IPv4-mapped/compat: ::ffff:a.b.c.d or ::ffff:0:a.b.c.d + const mappedMatch = normalized.match( + /^::(?:ffff(?::0)?:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/, + ); + if (mappedMatch) { + return isPrivateIPv4(mappedMatch[1]); + } - if (normalized === "::1") { - return true; - } if (normalized.startsWith("fc") || normalized.startsWith("fd")) { return true; } if ( normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb") ) { return true; } + if (normalized.startsWith("2001:db8:") || normalized.startsWith("64:ff9b:")) { + return true; + } return false; };🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
DNS rebinding SSRF mitigation Node.js undici fetch pinned IP💡 Result:
To mitigate DNS rebinding attacks in SSRF scenarios when using Node.js undici fetch, create a custom Agent with a safe DNS lookup function in the connect options. This intercepts DNS resolution after the initial lookup, allowing you to validate the resolved IP address against private ranges, loopback, etc., before connecting. This effectively pins and checks the IP, preventing rebinding where a domain resolves to a safe IP first then rebinds to internal IPs.[1][2] Use a library like ipaddr.js for robust IP range classification.[1] Example code: import { Agent } from 'undici'; import { lookup } from 'dns'; import ipaddr from 'ipaddr.js'; const safeLookup = (hostname, options, callback) => { lookup(hostname, options, (err, address, family) => { if (err) { return callback(err, address, family); } if (!ipaddr.isValid(address)) { return callback(new Error('Invalid address'), address, family); } const ipObj = ipaddr.parse(address); if (ipObj.range!== 'unicast') { // Blocks private, loopback, link-local, etc. return callback(new Error('Not Allowed'), address, family); } return callback(null, address, family); }); }; const safeAgent = new Agent({ connect: { lookup: safeLookup, }, }); // Usage with fetch const response = await fetch('http://example.com', { dispatcher: safeAgent }); This approach is recommended in undici discussions for SSRF protection and directly counters DNS rebinding by validating every resolution.[1][3] Undici also has a DNS interceptor (added in recent versions via PR
#3490, merged 2024) for caching/round-robin, but custom lookup in connect is the standard for security validation.[4][5] OWASP emphasizes resolving hostname, classifying IP, and rejecting unsafe ranges, which this implements.[2] For known/trusted hosts only, resolve once upfront, extract IP, and use a Client with that fixed IP as origin (bypassing DNS entirely).[6]Citations:
🏁 Script executed:
Repository: acm-uiuc/core
Length of output: 5343
🏁 Script executed:
Repository: acm-uiuc/core
Length of output: 455
DNS rebinding TOCTOU vulnerability: validated host may resolve to a different IP at fetch time.
assertCallbackUrlIsExternalresolves the hostname vialookup()and validates the resolved addresses, butfetch(Node's undici) performs its own independent DNS resolution. An attacker controlling DNS for the callback host can return a public IP for the validation lookup and a private IP (with TTL=0) for the subsequentfetch— bypassing the entire IP allow-list.Repro pattern: attacker registers a payment link with
callbackUrl=https://evil.example/. When the webhook fires,evil.examplereturns203.0.113.10for the first resolution and127.0.0.1(or169.254.169.254for cloud metadata) for the second.Recommended mitigation: pin the validated IP and direct
fetchto it explicitly, preserving the Host header via SNI. With undici this looks like:Alternatively, use
undici.requestand supply the resolved IP directly while setting theHostheader to the original hostname.🤖 Prompt for AI Agents
Uh oh!
There was an error while loading. Please reload this page.