75 lines
1.6 KiB
TypeScript
75 lines
1.6 KiB
TypeScript
const LOOPBACK_IPV4 = /^127(?:\.\d{1,3}){3}$/;
|
|
|
|
/**
|
|
* A release gate must not promote a local preview server as live hosting
|
|
* evidence.
|
|
*
|
|
*/
|
|
export function classifyLiveHostingBaseUrl(value: string):
|
|
| {
|
|
passed: true;
|
|
reason: null;
|
|
url: URL;
|
|
observedOrigin: string;
|
|
}
|
|
| {
|
|
passed: false;
|
|
reason: string;
|
|
url: URL | null;
|
|
observedOrigin: string | null;
|
|
} {
|
|
let url: URL;
|
|
try {
|
|
url = new URL(value);
|
|
} catch {
|
|
return {
|
|
passed: false,
|
|
reason: "HOSTING_BASE_URL must be an absolute URL",
|
|
url: null,
|
|
observedOrigin: null,
|
|
};
|
|
}
|
|
|
|
const observedOrigin = url.origin;
|
|
const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
if (url.protocol !== "https:") {
|
|
return {
|
|
passed: false,
|
|
reason: "live hosting evidence requires HTTPS",
|
|
url,
|
|
observedOrigin,
|
|
};
|
|
}
|
|
if (url.username || url.password) {
|
|
return {
|
|
passed: false,
|
|
reason: "HOSTING_BASE_URL must not contain credentials",
|
|
url,
|
|
observedOrigin,
|
|
};
|
|
}
|
|
if (
|
|
hostname === "localhost" ||
|
|
hostname.endsWith(".localhost") ||
|
|
hostname === "::1" ||
|
|
hostname === "0.0.0.0" ||
|
|
LOOPBACK_IPV4.test(hostname)
|
|
) {
|
|
return {
|
|
passed: false,
|
|
reason: "local or loopback hosts are not live deployment evidence",
|
|
url,
|
|
observedOrigin,
|
|
};
|
|
}
|
|
if (url.pathname !== "/" || url.search || url.hash) {
|
|
return {
|
|
passed: false,
|
|
reason: "HOSTING_BASE_URL must be the canonical root URL",
|
|
url,
|
|
observedOrigin,
|
|
};
|
|
}
|
|
return { passed: true, reason: null, url, observedOrigin };
|
|
}
|