/** * The sandboxed plugin frame server (#plugins, M3): serves a plugin's client/ * assets with a locked-down per-frame CSP and a strict path guard, only when the * plugin is active and the runtime is enabled. */ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; const { pluginsEnabledMock } = vi.hoisted(() => ({ pluginsEnabledMock: vi.fn(() => true) })); vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled: pluginsEnabledMock })); import { PluginFrameController } from '../../../src/nest/plugins/plugin-frame.controller'; import type { PluginRuntimeService } from '../../../src/nest/plugins/plugin-runtime.service'; let codeRoot: string; let outsideRoot: string; // Windows only lets an unprivileged process create a directory junction. const dirLink = process.platform === 'win32' ? 'junction' : 'dir'; let symlinksAvailable = true; beforeAll(() => { codeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'trekplug-frame-')); outsideRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'trekplug-outside-')); process.env.TREK_PLUGINS_DIR = codeRoot; const dir = path.join(codeRoot, 'widget', 'client'); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(path.join(dir, 'index.html'), '
hi'); // Something worth stealing, plus a link out of client/ that points at it. fs.mkdirSync(path.join(outsideRoot, 'secrets'), { recursive: true }); fs.writeFileSync(path.join(outsideRoot, 'secrets', 'leak.js'), 'TOP SECRET'); // A dev-linked plugin: the whole tree is a link, which must still be served. fs.mkdirSync(path.join(outsideRoot, 'linked', 'client'), { recursive: true }); fs.writeFileSync(path.join(outsideRoot, 'linked', 'client', 'index.html'), 'dev'); try { fs.symlinkSync(path.join(outsideRoot, 'secrets'), path.join(dir, 'esc'), dirLink); fs.symlinkSync(path.join(outsideRoot, 'linked'), path.join(codeRoot, 'linked'), dirLink); } catch { symlinksAvailable = false; } }); afterAll(() => { delete process.env.TREK_PLUGINS_DIR; fs.rmSync(codeRoot, { recursive: true, force: true }); fs.rmSync(outsideRoot, { recursive: true, force: true }); }); function fakeRes() { const res = { statusCode: 0, headers: {} as Record