Playwright Tests / test-playwright (1, 2) (push) Canceled after 0s
Playwright Tests / test-playwright (2, 2) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Compose Smoke Test / test-compose (push) Canceled after 0s
Playwright Tests / merge-reports (push) Canceled after 0s
`tests/utils/api.ts` took the API origin from `VITE_API_URL`, which `tests/config.ts` loads out of `app/.env`. In a checkout configured for a deployment that names the deployment — so the browser went to the local stack while every setup and teardown call, `deleteAll` included, went to the live one. `privateApi.ts` had the same reading, and it creates users. Both origins now come from one place: `PLAYWRIGHT_BASE_URL`, with the API derived from it (`app.<domain>` → `api.<domain>`) or named outright by `PLAYWRIGHT_API_URL`, which is what CI and the compose service set. Nothing in the suite reads `VITE_API_URL` any more. Belt and braces, since a stack served under a real domain answers to the same names its production instance does: a global setup resolves both origins and refuses anything that is not loopback or a private range, before a test runs. `PLAYWRIGHT_ALLOW_PUBLIC=1` says you meant it. `make test-frontend` is now that safe run — the Playwright image on the proxy network with both names mapped onto Traefik by address, as the host user so it does not leave root-owned results behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUb8YpL2s3gmN9WTACTt4q
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
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<void> {
|
|
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.",
|
|
)
|
|
}
|
|
}
|