/* Host guard for every UI-driving (Playwright) admin script. The REST write path is already structurally safe: `dol-write.sh` refuses any host that is not the sandbox (ADR-0003). The UI path had no equivalent — and `test/.env` ships DOLIBARR_ADDRESS pointing at PRODUCTION, so a script run with the ambient environment would drive the real ERP. This module closes that gap: an admin script calls `assertSandbox()` before its first click, and dies otherwise. Production changes are never made by a script. They are rehearsed here, then applied by the operator through the human-gated path. */ /** Hosts an admin script is allowed to drive. Sandbox only, by design. */ const ALLOWED_HOST_PATTERN = /^erp-sandbox\./i; export class UnsafeTargetError extends Error {} /** * Resolve the target address and refuse anything that is not the sandbox. * Pass an explicit address, or let it read DOLIBARR_ADDRESS from the env. */ export function assertSandbox(address?: string): string { const target = address ?? Deno.env.get("DOLIBARR_ADDRESS") ?? ""; if (!target) { throw new UnsafeTargetError( "guard: no target address (pass one, or set DOLIBARR_ADDRESS)", ); } let host: string; try { host = new URL(target).host; } catch { throw new UnsafeTargetError(`guard: not a valid URL: ${target}`); } if (!ALLOWED_HOST_PATTERN.test(host)) { // Production opt-in: deliberate, loud, and per-run. The operator must name // the exact host AND type the confirmation phrase, so nothing reaches prod // by inheriting an ambient variable — the same posture the promote flow // takes with DOLIBARR_PROD_WRITE_KEY / ARCO_PROMOTE_CONFIRM. const allowProd = Deno.env.get("ARCO_ALLOW_PRODUCTION") ?? ""; const confirm = Deno.env.get("ARCO_PROD_CONFIRM") ?? ""; if (allowProd === host && confirm === "I-UNDERSTAND-THIS-WRITES-PROD") { console.warn( `\n*** PRODUCTION TARGET: ${host} — explicit opt-in accepted. ***\n` + " Every write below hits the real ledger and cannot be undone.\n", ); return target; } throw new UnsafeTargetError( `REFUSED: '${host}' is not the sandbox.\n` + "UI admin scripts may only drive erp-sandbox.*; production is changed " + "by the operator through the human-gated path, never by a script.\n" + "Override the ambient env explicitly, e.g.\n" + " DOLIBARR_ADDRESS=https://erp-sandbox.arcodange.lab deno run ...\n" + "To target production on purpose, set BOTH:\n" + ` ARCO_ALLOW_PRODUCTION=${host} ARCO_PROD_CONFIRM=I-UNDERSTAND-THIS-WRITES-PROD`, ); } return target; } export default { assertSandbox, UnsafeTargetError };