UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

122 lines • 6.61 kB
// Test-state isolation: the RUNTIME half of "the suite must never write the // developer's real ~/.comfyui-mcp". // // Two mechanisms, one property: // // 1. The vitest setup file (src/__tests__/helpers/isolated-test-home.ts) points // HOME/USERPROFILE at a fresh temp directory for the ENTIRE run, before any // test module — and therefore any product module — is imported. Every // homedir()-based default lands in the throwaway home, however the writer // spelled it, including module-eval-time captures and child processes that // inherit the environment. That is the catch-all: it does not enumerate // writers, so a writer nobody audited is still safe (#866, #879). // // 2. `assertNotWritingRealHomeInTests` below refuses, at the moment of the // write, any write whose target is inside the REAL home recorded before the // redirect. It exists for the writers whose loss is live-harmful (pending // panel operations, the panel pin/settings, prompt overrides), so that a // test which deliberately points one of those back at the real home fails // loudly instead of silently damaging it. // // The credential stores keep their own, stricter guards in panel-secrets.ts: // there a test must redirect the store explicitly, because a dummy token // landing in the REAL .env once already cost a live CivitAI token. import { join, resolve, sep } from "node:path"; /** * Are we running INSIDE the test runner? * * The first version of this guard asked `NODE_ENV === "test" || VITEST || * VITEST_WORKER_ID` — ordinary environment variables that any user can have set, * for reasons of their own, in the shell this app was launched from. A user with * a leftover `VITEST` export, or `NODE_ENV=test` from an adjacent project, was * then REFUSED when saving their own API key (codex gate). Refusing a real user * their credential is a worse outcome than the accidental store overwrite this * guard replaced, so the guard must key off something only the runner itself * produces. * * Vitest injects these into the global scope of every worker it runs. They are * not environment variables and there is no plausible way for a user's shell to * put them there. When none is present we do NOT know we are under a runner — * and in that uncertainty the write is ALLOWED, deliberately: the failure mode * we accept is "a rogue test slips past the runtime guard and is caught by the * static sweep instead", never "a real user cannot save a token". * * `scope` is a parameter so this can be tested for what it must NOT do. Some of * the runner's own globals are non-configurable, so a test cannot remove them to * check that the answer does not fall back to `VITEST` / `NODE_ENV`; passing a * bare object asks the question directly. */ export function runningUnderTestRunner(scope = globalThis) { return ("__vitest_worker__" in scope || "__vitest_index__" in scope || "__vitest_environment__" in scope); } /** * Where the REAL home directory is recorded for the duration of a test run. * A global, not an environment variable: an env var is something a user can * plausibly have set (or a test can wipe by replacing `process.env`), and this * value must only ever come from the test setup file. The name cannot collide * with anything the runner itself defines. */ const REAL_HOME_GLOBAL = "__comfyui_mcp_test_real_home__"; /** * Called ONCE by the vitest setup file, before it redirects HOME: remember * where the developer's real home directory is, so write-guards can still * recognise a target inside it after the redirect makes `homedir()` answer * with the throwaway one. */ export function recordRealHomeForTestIsolation(realHome) { globalThis[REAL_HOME_GLOBAL] = realHome; } /** The home the run started with, if the setup file recorded one. Exported so * tests can assert the redirect is actually in force (a guard nobody wired * up guards nothing). */ export function realHomeRecordedForTestIsolation() { const value = globalThis[REAL_HOME_GLOBAL]; return typeof value === "string" && value !== "" ? value : undefined; } /** * REFUSE to write the developer's real ~/.comfyui-mcp state from a test run. * * This is the write-side guard for state files whose corruption is * live-harmful but which have no per-store redirect convention worth * enforcing (the global HOME redirect already keeps the default path safe). * It refuses only the one target that is never legitimate under a runner: * inside the real home's .comfyui-mcp directory, recorded at setup. * * The comparison root is the STATE DIRECTORY, not the home directory itself: * on Windows the OS temp directory lives INSIDE the user's home * (C:\Users\x\AppData\Local\Temp), so "outside the real home" would refuse * every correctly-redirected temp-path write on exactly the platform class * that produced #866. * * The fail-open cases are deliberate and match the credential-store guards: * no runner globals (production — the guard is inert there) or no recorded * real home (a runner without our setup file) both ALLOW the write. Refusing * a real user their own state on an uncertain detection is the worse failure. */ export function assertNotWritingRealHomeInTests(targetPath, what) { if (!runningUnderTestRunner()) return; // detection is uncertain → ALLOW the write const realHome = realHomeRecordedForTestIsolation(); if (!realHome) return; // no redirect installed → nothing to compare against // Case-insensitive on Windows, where the filesystem is; a prefix check must // include the separator so ".../.comfyui-mcp-old" is not "inside" // ".../.comfyui-mcp". const normalize = (p) => { const r = resolve(p); return process.platform === "win32" ? r.toLowerCase() : r; }; const target = normalize(targetPath); const stateRoot = normalize(join(realHome, ".comfyui-mcp")); const rootWithSep = stateRoot.endsWith(sep) ? stateRoot : stateRoot + sep; if (target === stateRoot || target.startsWith(rootWithSep)) { throw new Error(`Refusing to write ${what} from a test run: ${targetPath} is inside the developer's ` + `real state directory (${stateRoot}). The test run redirects HOME to a throwaway ` + `directory precisely so this cannot happen — a test that reaches the real path ` + `has explicitly undone that isolation. Point the store's redirect variable at a ` + `temp path instead. Nothing was written.`); } } //# sourceMappingURL=test-isolation-guard.js.map