feat(test): host guard for UI admin scripts + sandbox legal-mentions setup

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
This commit is contained in:
2026-07-25 23:14:15 +02:00
co-authored by Claude Opus 5
parent 87a176e5b1
commit aa43862076
3 changed files with 254 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
/*
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 };