failsafe.mjs
Language JavaScript2.8 KB51 lines
// Offline verification of the write-prevention failsafe. NO network, NO API key.// Run after `npm run build`: node verify/failsafe.mjsimport { assertReadMethod, assertSafeParams, assertUrlIsRead, WriteAttemptError } from "../dist/lib/guard.js";import { buildUrl, plinthRequest } from "../dist/lib/http.js";import { configFromEnv } from "../dist/lib/config.js";let pass = 0, fail = 0;const BASE = "https://orders.meridiandisplay.example/api/v2/dispatch";function blocks(desc, fn) {try { fn(); console.log(`FAIL (should have thrown): ${desc}`); fail++; }catch (e) {if (e instanceof WriteAttemptError) { console.log(`BLOCKED: ${desc}`); pass++; }else { console.log(`FAIL (wrong error ${e.name}): ${desc}`); fail++; }}}function allows(desc, fn) {try { fn(); console.log(`ALLOWED: ${desc}`); pass++; }catch (e) { console.log(`FAIL (blocked a legit read ${e.message}): ${desc}`); fail++; }}blocks('method "inventory.adjust"', () => assertReadMethod("inventory.adjust"));blocks('method "orders.setStatus"', () => assertReadMethod("orders.setStatus"));blocks('method SQL-ish junk', () => assertReadMethod("orders.fetch; DROP TABLE"));allows('method "orders.fetch"', () => assertReadMethod("orders.fetch"));blocks('param {method:"inventory.adjust"}', () => assertSafeParams({ method: "inventory.adjust" }));blocks('param {api_key:"..."}', () => assertSafeParams({ api_key: "abc" }));blocks('write-intent {setStatus:"0"}', () => assertSafeParams({ setStatus: "0" }));blocks('write-intent {notify:1}', () => assertSafeParams({ notify: 1 }));blocks('write-intent {deleteOrder:1}', () => assertSafeParams({ deleteOrder: 1 }));allows('legit read params', () => assertSafeParams({ email: "a@example.com", status_code: "5", shop: "2", subshops: 0, date_field: "last_login", since: "20260101" }));const u = buildUrl(BASE, "inventory.fetch", { method: "inventory.adjust", foo: "bar" });if (u.searchParams.get("method") === "inventory.fetch" && u.searchParams.getAll("method").length === 1) {console.log("BLOCKED: buildUrl override (method stays inventory.fetch)"); pass++;} else { console.log(`FAIL: method overridden -> ${u}`); fail++; }const forged = new URL(BASE); forged.searchParams.set("method", "inventory.adjust");blocks("final URL gate on forged inventory.adjust", () => assertUrlIsRead(forged));try {await plinthRequest({ config: configFromEnv({ PLINTH_API_KEY: "dummy", PLINTH_ALLOW_LIVE_REQUESTS: "false" }) }, "statuses.list");console.log("FAIL: plinthRequest ran with gate off"); fail++;} catch (e) {if (e.name === "ConfigError") { console.log("gate off -> no network"); pass++; }else { console.log(`FAIL wrong error ${e.name}`); fail++; }}console.log(`\n${fail === 0 ? "ALL PASS" : "FAILURES"} - ${pass} passed, ${fail} failed`);process.exit(fail === 0 ? 0 : 1);