feat: let plugin frames run work they can stop in a worker

This commit is contained in:
Linus Rath
2026-11-01 10:07:41 +01:00
parent 6b3a220d44
commit 2cd1baa594
2 changed files with 15 additions and 0 deletions
@@ -181,6 +181,17 @@ describe('frame-ancestors of the mail UI', () => {
else process.env.ALLOWED_FRAME_ANCESTORS = original;
});
// Send checks run the admin's patterns in a worker they can stop; only
// plugin frames get it, the app keeps its script-src fallback.
it('lets plugin frames, and only them, start workers from blobs', async () => {
vi.mocked(configManager.getPolicy).mockReturnValueOnce({ features: { pluginsEnabled: true } } as never);
const frame = await proxy(new NextRequest('http://localhost:3000/plugin-sandbox', {
headers: { 'sec-fetch-dest': 'iframe', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'same-origin' },
}));
expect(frame.headers.get('content-security-policy')).toContain("worker-src 'self' blob:");
expect((await load('/en/mail/folder/inbox')).headers.get('content-security-policy')).not.toContain('worker-src');
});
it('allows only the app itself by default', async () => {
delete process.env.ALLOWED_FRAME_ANCESTORS;
const res = await load('/en/mail/folder/inbox');
+4
View File
@@ -420,6 +420,10 @@ export async function proxy(request: NextRequest) {
isSandboxPath ? `form-action 'none'` : `form-action ${["'self'", ...wopiOrigins].join(" ")}`,
`frame-ancestors ${frameAncestors}`,
`media-src ${mediaSrc}`,
// A plugin frame may run work it has to stop at a deadline (send checks'
// admin patterns) in a worker made from a blob; it gets the frame's own
// connect-src, so it reaches nothing the frame can't.
...(isSandboxPath ? [`worker-src 'self' blob:`] : []),
].join("; ");
// Skip intl middleware for routes outside the localized app tree.