test/.env ships DOLIBARR_ADDRESS pointing at PRODUCTION and test/main.ts defaults to it, so any Playwright admin script run with the ambient environment drives the real ERP. The REST path has been structurally safe since ADR-0003 (dol-write.sh refuses non-sandbox hosts); the UI path had no equivalent. scripts/guard.ts closes that gap — assertSandbox() resolves the target and refuses anything that is not erp-sandbox.*, with the override spelled out in the error. Verified: an unqualified run now dies instead of reaching prod. Two settings the write agent cannot reach (non-admin by design, 403 on /setup/conf), rehearsed on the sandbox: - sandboxLegalSetup.ts — capital social + multi-currency module. Finding: the capital was NOT missing, it was stored as "1000€"; the symbol made the value unusable by the PDF template, which is why "Capital de 1 000 €" was absent from every invoice since January (C. com. R.123-238). Normalised to "1000" → the mention now renders. - sandboxCurrencySetup.ts — registers the USD reference rate. Enabling the module is not enough: a currency absent from the rate table makes Dolibarr silently fall back to EUR (observed on a probe invoice). With the rate registered, an invoice carries USD 3,000.00 with its EUR counter-value, i.e. the contractual obligation itself rather than a drifting equivalent. Both scripts are report-only when they cannot recognise a form, and screenshot what they did. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh
52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
/*
|
|
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)) {
|
|
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 ...",
|
|
);
|
|
}
|
|
return target;
|
|
}
|
|
|
|
export default { assertSandbox, UnsafeTargetError };
|