mattrguerra/meridian-orders-mcpmainPrivate on GitHub, published here in full

failsafe.mjs

Language JavaScript2.8 KB51 lines

mcp-server/verify/failsafe.mjs

  1. // Offline verification of the write-prevention failsafe. NO network, NO API key.
  2. // Run after `npm run build`: node verify/failsafe.mjs
  3. import { assertReadMethod, assertSafeParams, assertUrlIsRead, WriteAttemptError } from "../dist/lib/guard.js";
  4. import { buildUrl, plinthRequest } from "../dist/lib/http.js";
  5. import { configFromEnv } from "../dist/lib/config.js";
  6. let pass = 0, fail = 0;
  7. const BASE = "https://orders.meridiandisplay.example/api/v2/dispatch";
  8. function blocks(desc, fn) {
  9. try { fn(); console.log(`FAIL (should have thrown): ${desc}`); fail++; }
  10. catch (e) {
  11. if (e instanceof WriteAttemptError) { console.log(`BLOCKED: ${desc}`); pass++; }
  12. else { console.log(`FAIL (wrong error ${e.name}): ${desc}`); fail++; }
  13. }
  14. }
  15. function allows(desc, fn) {
  16. try { fn(); console.log(`ALLOWED: ${desc}`); pass++; }
  17. catch (e) { console.log(`FAIL (blocked a legit read ${e.message}): ${desc}`); fail++; }
  18. }
  19. blocks('method "inventory.adjust"', () => assertReadMethod("inventory.adjust"));
  20. blocks('method "orders.setStatus"', () => assertReadMethod("orders.setStatus"));
  21. blocks('method SQL-ish junk', () => assertReadMethod("orders.fetch; DROP TABLE"));
  22. allows('method "orders.fetch"', () => assertReadMethod("orders.fetch"));
  23. blocks('param {method:"inventory.adjust"}', () => assertSafeParams({ method: "inventory.adjust" }));
  24. blocks('param {api_key:"..."}', () => assertSafeParams({ api_key: "abc" }));
  25. blocks('write-intent {setStatus:"0"}', () => assertSafeParams({ setStatus: "0" }));
  26. blocks('write-intent {notify:1}', () => assertSafeParams({ notify: 1 }));
  27. blocks('write-intent {deleteOrder:1}', () => assertSafeParams({ deleteOrder: 1 }));
  28. allows('legit read params', () => assertSafeParams({ email: "a@example.com", status_code: "5", shop: "2", subshops: 0, date_field: "last_login", since: "20260101" }));
  29. const u = buildUrl(BASE, "inventory.fetch", { method: "inventory.adjust", foo: "bar" });
  30. if (u.searchParams.get("method") === "inventory.fetch" && u.searchParams.getAll("method").length === 1) {
  31. console.log("BLOCKED: buildUrl override (method stays inventory.fetch)"); pass++;
  32. } else { console.log(`FAIL: method overridden -> ${u}`); fail++; }
  33. const forged = new URL(BASE); forged.searchParams.set("method", "inventory.adjust");
  34. blocks("final URL gate on forged inventory.adjust", () => assertUrlIsRead(forged));
  35. try {
  36. await plinthRequest({ config: configFromEnv({ PLINTH_API_KEY: "dummy", PLINTH_ALLOW_LIVE_REQUESTS: "false" }) }, "statuses.list");
  37. console.log("FAIL: plinthRequest ran with gate off"); fail++;
  38. } catch (e) {
  39. if (e.name === "ConfigError") { console.log("gate off -> no network"); pass++; }
  40. else { console.log(`FAIL wrong error ${e.name}`); fail++; }
  41. }
  42. console.log(`\n${fail === 0 ? "ALL PASS" : "FAILURES"} - ${pass} passed, ${fail} failed`);
  43. process.exit(fail === 0 ? 0 : 1);