// Guards for the cross-site request gate on the loopback API server. // // Run: node --test extensions/connector-namespaces/server.test.mjs // // The server binds an ephemeral 127.0.0.1 port and JSON-parses every POST body, // so without a check any web page the user visits could script-drive their ARM // operations (CSRF). isCrossSiteRequest is the gate: it blocks a POST /api/* // only when the request carries an explicit foreign-origin signal, and lets the // panel's own same-origin fetches — and header-less callers like this test // harness — through untouched. Importing server.mjs has no side effects at eval; // the HTTP server only starts when startServer() is called. import { after, test } from "node:test"; import assert from "node:assert/strict"; import { EventEmitter } from "node:events"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Readable } from "node:stream"; const TEST_COPILOT_HOME = mkdtempSync(join(tmpdir(), "connector-server-test-")); process.env.COPILOT_HOME = TEST_COPILOT_HOME; after(() => rmSync(TEST_COPILOT_HOME, { recursive: true, force: true })); const { getServerConfig, hasCapabilityToken, isCanonicalHost, isCrossSiteRequest, listenOnLoopback, parseBody, requiresCapabilityToken, runIdempotentOperation, startServer, stopServer, } = await import("./server.mjs"); const { isValidConfig } = await import("./state.mjs"); // Minimal request stub: only headers matter to the gate. function req(headers) { return { headers }; } test("same-origin Origin (our own loopback UI) is allowed", () => { const r = req({ host: "127.0.0.1:54321", origin: "http://127.0.0.1:54321" }); assert.equal(isCrossSiteRequest(r, "http://127.0.0.1:54321"), false); }); test("no headers at all (test harness / non-browser client) is allowed", () => { assert.equal(isCrossSiteRequest(req({}), "http://127.0.0.1:54321"), false); }); test("non-web-scheme Origin (host webview) is allowed", () => { // Some app webviews send Origin like "vscode-webview://..." or "app://..." // custom schemes. Those aren't a browsable web page driving a CSRF, so we // don't block them; only http(s) foreign origins and opaque `null` origins // are treated as hostile. const r = req({ host: "127.0.0.1:54321", origin: "app://obsidian.md" }); assert.equal(isCrossSiteRequest(r, "http://127.0.0.1:54321"), false); }); test("opaque null Origin (sandboxed iframe / data: URI) is blocked", () => { // Browsers send the literal string "null" as Origin from sandboxed iframes // (