From a4257f361197fdfec27709d236e776182885fb1a Mon Sep 17 00:00:00 2001 From: mauriceboe Date: Fri, 3 Jul 2026 18:36:32 +0200 Subject: [PATCH] fix(plugins): allow inline scripts in the sandboxed frame + fix server lint error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Plugin frame CSP: the frame runs at an opaque origin (sandbox without allow-same-origin), so script-src 'self' matches nothing and the widget's own script never runs (stuck on 'Loading…'). Allow 'unsafe-inline' — the sandbox, not this directive, is the isolation boundary, and the plugin author controls the frame code either way. - Fix a no-constant-binary-expression eslint error in registry.test.ts that was failing the server lint:check (eslint .) in CI. --- server/src/nest/plugins/plugin-frame.controller.ts | 6 +++++- server/tests/integration/plugins/registry.test.ts | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/server/src/nest/plugins/plugin-frame.controller.ts b/server/src/nest/plugins/plugin-frame.controller.ts index dd92e4d0f..72c3ceb0d 100644 --- a/server/src/nest/plugins/plugin-frame.controller.ts +++ b/server/src/nest/plugins/plugin-frame.controller.ts @@ -71,7 +71,11 @@ export class PluginFrameController { const connect = ["'self'", ...outbound.map((h) => (h.includes('://') ? h : `https://${h}`))].join(' '); return [ "default-src 'none'", - "script-src 'self'", + // The frame runs at an OPAQUE origin (sandbox without allow-same-origin), + // so 'self' matches nothing — inline is the only way its own script can run. + // Safe here: the sandbox (not this script-src) is the isolation boundary, + // and the plugin author controls the frame's code either way. + "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "font-src 'self' data:", diff --git a/server/tests/integration/plugins/registry.test.ts b/server/tests/integration/plugins/registry.test.ts index 57629315c..4d3cf1cdc 100644 --- a/server/tests/integration/plugins/registry.test.ts +++ b/server/tests/integration/plugins/registry.test.ts @@ -120,7 +120,8 @@ describe('PluginRegistryService', () => { const row = testDb.prepare("SELECT status, source_repo, source_commit FROM plugins WHERE id='flight-tracker'").get() as { status: string; source_repo: string; source_commit: string }; expect(row).toMatchObject({ status: 'inactive', source_repo: 'acme/trek-flight', source_commit: 'a'.repeat(40) }); // no staging left behind - expect(fs.existsSync(path.join(dataRoot, '.staging'))).toBe(false || fs.readdirSync(path.join(dataRoot, '.staging')).length === 0); + const staging = path.join(dataRoot, '.staging'); + expect(!fs.existsSync(staging) || fs.readdirSync(staging).length === 0).toBe(true); }); it('rejects an sha256 mismatch', async () => {