import { lookup } from "node:dns/promises" import { apiUrl, appUrl } from "./config.ts" /** * Refuse to run against a machine on the internet. * * The suite creates flows, dashboards and users and deletes them again, so the * cost of pointing it at the wrong instance is somebody's data. That is not a * hypothetical: a stack served under a real domain answers to the same names * its production instance does, and the only thing telling them apart is which * address those names resolve to here. * * So that is what is checked. Loopback and the private ranges are a stack on * this machine or on its docker network; anything else is refused by name and * address, before a single test runs. `PLAYWRIGHT_ALLOW_PUBLIC=1` is the way * to say you meant it. */ const PRIVATE_V4 = [ /^127\./, /^10\./, /^192\.168\./, /^169\.254\./, /^172\.(1[6-9]|2\d|3[01])\./, ] function isPrivate(address: string): boolean { if (address === "::1" || address === "::") return true // ::ffff:172.18.0.2 and friends. const mapped = address.replace(/^::ffff:/i, "") if (mapped !== address) return isPrivate(mapped) if (/^f[cd][0-9a-f]{2}:/i.test(address)) return true // unique-local if (/^fe80:/i.test(address)) return true // link-local return PRIVATE_V4.some((range) => range.test(address)) } export default async function guardTheTarget(): Promise { if (process.env.PLAYWRIGHT_ALLOW_PUBLIC === "1") return for (const target of new Set([appUrl, apiUrl])) { const { hostname } = new URL(target) let address: string try { address = (await lookup(hostname)).address } catch { // Unresolvable is the run's own problem to report; it cannot be a live // instance, which is all this guard is here for. continue } if (isPrivate(address)) continue throw new Error( `Refusing to run: ${target} resolves to ${address}, which is not this ` + "machine or its docker network. This suite creates and deletes flows, " + "dashboards and users.\n" + "Run it against the local stack — `make -C app test-frontend` maps " + "the names onto Traefik — or set PLAYWRIGHT_ALLOW_PUBLIC=1 if you " + "really mean this one.", ) } }