33 lines · 1.2 KB
/** Pure EDITOR_ORIGIN validation (no Worker bindings), so it unit-tests under bun. */

function within(host: string, domain: string | undefined): boolean {
  const base = (domain ?? "")
    .trim()
    .toLowerCase()
    .replace(/^\.+|\.+$/g, "");
  return base !== "" && (host === base || host.endsWith(`.${base}`));
}

/**
 * Why an EDITOR_ORIGIN is unsafe, or null. It must be its own host: never the forge host (cookies and
 * pages), and never under SITES_DOMAIN or PAGES_DOMAIN, where user content could frame or impersonate it.
 */
export function editorOriginProblem(
  raw: string,
  forgeHost: string,
  domains: { sites?: string; pages?: string },
): string | null {
  let origin: URL;
  try {
    origin = new URL(raw);
  } catch {
    return "EDITOR_ORIGIN is not a valid URL";
  }
  const host = origin.hostname.toLowerCase();
  if (host === forgeHost.toLowerCase()) return "EDITOR_ORIGIN must not be the forge host";
  if (within(host, domains.sites)) return "EDITOR_ORIGIN must not be under SITES_DOMAIN (user content)";
  if (within(host, domains.pages)) return "EDITOR_ORIGIN must not be under PAGES_DOMAIN (user content)";
  if (origin.protocol !== "https:" && !within(host, "localhost")) return "EDITOR_ORIGIN must use https";
  return null;
}