From 1754624d495adcc864d3cc0dc36ed5d84e6647e7 Mon Sep 17 00:00:00 2001 From: sunil Date: Mon, 19 Oct 2026 10:05:35 +0530 Subject: [PATCH] Allow YouTube, Vimeo and Canva frames in the sandbox app CSP. Activity embeds were blocked because frame-src was missing and fell back to default-src 'self'. The nonce and strict-dynamic script-src control is unchanged. Co-authored-by: Cursor --- infrastructure/csp/p2-sandbox-app.csp | 2 +- scripts/assert-app-csp.cjs | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/infrastructure/csp/p2-sandbox-app.csp b/infrastructure/csp/p2-sandbox-app.csp index 397370e62..54af2125a 100644 --- a/infrastructure/csp/p2-sandbox-app.csp +++ b/infrastructure/csp/p2-sandbox-app.csp @@ -1 +1 @@ -default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self' https://*.practera.com; script-src 'nonce-__CSP_NONCE__' 'strict-dynamic' 'unsafe-inline' 'self' https://acsbapp.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://*.practera.com wss://*.pusher.com wss://socket.p2-sandbox.practera.com https://*.amazonaws.com; img-src 'self' data: blob: https: +default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self' https://*.practera.com; frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com https://player.vimeo.com https://www.canva.com; script-src 'nonce-__CSP_NONCE__' 'strict-dynamic' 'unsafe-inline' 'self' https://acsbapp.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://*.practera.com wss://*.pusher.com wss://socket.p2-sandbox.practera.com https://*.amazonaws.com; img-src 'self' data: blob: https: diff --git a/scripts/assert-app-csp.cjs b/scripts/assert-app-csp.cjs index f6ea6a960..22752c81d 100644 --- a/scripts/assert-app-csp.cjs +++ b/scripts/assert-app-csp.cjs @@ -9,8 +9,10 @@ * clears the host-allowlist bypass check. 'unsafe-inline', 'self', and * https://acsbapp.com stay as fallbacks for older browsers. * - * Every other directive stays exactly as deployed. This test fails if the - * sandbox policy drifts into unrelated directive changes. + * frame-src allows the activity embeds that otherwise fall back to + * default-src 'self': YouTube, YouTube no-cookie (Plyr), Vimeo, and Canva. + * Every other directive stays as deployed. This test fails if the sandbox + * policy drifts into unrelated directive changes. */ const assert = require('assert'); const fs = require('fs'); @@ -48,15 +50,27 @@ for (const directive of unchangedDirectives) { assert.ok(rendered.includes(directive), `sandbox policy changed an unrelated directive: ${directive}`); } assert.equal(rendered.includes('media-src'), false); -assert.equal(rendered.includes('frame-src'), false); assert.equal(rendered.includes('form-action'), false); +const frameSrc = "frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com https://player.vimeo.com https://www.canva.com"; +assert.ok(rendered.includes(frameSrc), 'sandbox policy frame-src does not match the activity embed allowlist'); +assert.equal(rendered.match(/frame-src/g).length, 1); +assert.equal(frameSrc.includes('*'), false); + const scriptSrc = rendered.match(/script-src ([^;]+)/)[1]; assert.ok(scriptSrc.includes(`'nonce-${nonce}'`)); assert.ok(scriptSrc.includes("'strict-dynamic'")); +assert.equal(scriptSrc.includes('youtube'), false); +assert.equal(scriptSrc.includes('vimeo'), false); +assert.equal(scriptSrc.includes('canva'), false); const hostWithoutStrictDynamic = scriptSrc.includes('https://acsbapp.com') && scriptSrc.includes("'strict-dynamic'"); assert.equal(hostWithoutStrictDynamic, true); +const connectSrc = rendered.match(/connect-src ([^;]+)/)[1]; +assert.equal(connectSrc.includes('youtube'), false); +assert.equal(connectSrc.includes('vimeo'), false); +assert.equal(connectSrc.includes('canva'), false); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'app-csp-')); const tempTemplate = path.join(tempDir, 'serverless.yml'); fs.copyFileSync(templatePath, tempTemplate); @@ -68,6 +82,7 @@ execFileSync(process.execPath, [ ], { stdio: 'inherit' }); const applied = fs.readFileSync(tempTemplate, 'utf8'); assert.ok(applied.includes(`script-src 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline' 'self' https://acsbapp.com`)); +assert.ok(applied.includes(frameSrc)); assert.equal(applied.includes('__CSP_NONCE__'), false); const dist = path.join(tempDir, 'dist');