Files
vibn-frontend/lib/preview-embed-allowlist.ts

52 lines
1.8 KiB
TypeScript

/**
* SSRF guard + client/server agreement for /api/preview/embed.
* Only tunnel-like preview hosts should be proxied — never arbitrary URLs.
*/
export function isPrivateIpHostname(hostname: string): boolean {
const m = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(hostname);
if (!m) return false;
const a = Number(m[1]);
const b = Number(m[2]);
if (a === 10) return true;
if (a === 172 && b >= 16 && b <= 31) return true;
if (a === 192 && b === 168) return true;
if (a === 127) return true;
if (a === 169 && b === 254) return true;
if (a === 0) return true;
return false;
}
/** Server-side hostname allowlist after redirects */
export function serverPreviewHostnameAllowed(hostname: string, protocol: string): boolean {
const h = hostname.toLowerCase();
const p = protocol.toLowerCase();
if (p !== "http:" && p !== "https:") return false;
if (isPrivateIpHostname(h)) return false;
if (h === "localhost" || h === "127.0.0.1") {
return process.env.NODE_ENV === "development";
}
if (h.endsWith(".preview.vibnai.com")) return true;
if (h === "preview.vibnai.com") return true;
const suffixes = (process.env.NEXT_PUBLIC_PREVIEW_EMBED_PROXY_HOST_SUFFIXES ?? "")
.split(",")
.map((s) => s.trim().toLowerCase())
.filter(Boolean);
return suffixes.some((suf) => (suf.startsWith(".") ? h.endsWith(suf) : h === suf));
}
/**
* Remote URLs we should load through /api/preview/embed so the bridge script is same-origin.
* Same-origin targets return false (already works without proxy).
*/
export function previewUrlEligibleForEmbedProxy(rawUrl: string, appOrigin: string): boolean {
try {
const u = new URL(rawUrl);
const app = new URL(appOrigin);
if (u.origin === app.origin) return false;
return serverPreviewHostnameAllowed(u.hostname, u.protocol);
} catch {
return false;
}
}