Two guards, both from real incidents in the same session. 1. invoice-create.sh — chronology (CGI art. 289). Dolibarr assigns the number at validation, in creation order, so issuing a document dated BEFORE the last one already issued gives a higher number to an earlier date. The July plan walked straight into it: the M3 deferred part is due 2026-10-23 and must be issued at D-60 (24/08) to stay under the L.441-10 I ceiling, while the M4 fixed part is dated 23/08 — issue them in the wrong order and the numbering breaks. The guard reads the last issued document of the same kind and refuses an earlier date, with ARCO_ALLOW_BACKDATE as a loud, documented override. Verified: refuses a 01/07 invoice against FAC008 (23/07), accepts 23/08. 2. test/scripts/guard.ts — production opt-in. The sandbox-only guard had no way to express a deliberate production run, so any prod work meant bypassing it entirely (which is how guards die). Production now requires BOTH ARCO_ALLOW_PRODUCTION=<exact host> and ARCO_PROD_CONFIRM=I-UNDERSTAND-THIS-WRITES-PROD, and prints a banner. Nothing reaches prod by inheriting an ambient variable. Also fixes a misleading "(sandbox verified)" log that printed even on prod. grantAgentRight.ts joins the repo (it was never committed) and gains --revoke, so a temporarily elevated right can be handed back — used today to attach a payment in production and revoked immediately after. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh
67 lines
2.7 KiB
TypeScript
67 lines
2.7 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)) {
|
|
// 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 };
|