Spaces:
Running
Running
| import { chromium } from '@playwright/test'; | |
| import { createServer as createHttpServer } from 'node:http'; | |
| import { dirname, resolve } from 'node:path'; | |
| import { fileURLToPath } from 'node:url'; | |
| import { createServer as createViteServer } from 'vite'; | |
| const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); | |
| const probeHits = []; | |
| function listen(server) { | |
| return new Promise((resolveListen, reject) => { | |
| server.once('error', reject); | |
| server.listen(0, '127.0.0.1', () => { | |
| server.off('error', reject); | |
| const address = server.address(); | |
| if (!address || typeof address === 'string') { | |
| reject(new Error('Local probe did not expose a TCP port')); | |
| return; | |
| } | |
| resolveListen(address.port); | |
| }); | |
| }); | |
| } | |
| function close(server) { | |
| return new Promise((resolveClose, reject) => { | |
| server.close((error) => error ? reject(error) : resolveClose()); | |
| }); | |
| } | |
| const probe = createHttpServer((request, response) => { | |
| probeHits.push(request.url ?? '/'); | |
| response.writeHead(204, { 'Access-Control-Allow-Origin': '*' }); | |
| response.end(); | |
| }); | |
| let browser; | |
| let vite; | |
| try { | |
| const probePort = await listen(probe); | |
| const probeBaseUrl = `http://127.0.0.1:${probePort}`; | |
| vite = await createViteServer({ | |
| root, | |
| logLevel: 'error', | |
| server: { host: '127.0.0.1', port: 0 }, | |
| plugins: [{ | |
| name: 'artifact-csp-probe-page', | |
| configureServer(server) { | |
| server.middlewares.use('/artifact-csp-probe.html', (_request, response) => { | |
| response.setHeader('Content-Type', 'text/html; charset=utf-8'); | |
| response.end('<!doctype html><title>Artifact CSP probe</title>'); | |
| }); | |
| }, | |
| }], | |
| }); | |
| await vite.listen(); | |
| const viteAddress = vite.httpServer?.address(); | |
| if (!viteAddress || typeof viteAddress === 'string') throw new Error('Vite probe page did not expose a TCP port'); | |
| const controlResponse = await fetch(`${probeBaseUrl}/control`); | |
| if (!controlResponse.ok || probeHits.length !== 1) throw new Error('Local probe control request failed'); | |
| probeHits.length = 0; | |
| browser = await chromium.launch({ headless: true }); | |
| const page = await browser.newPage(); | |
| await page.goto(`http://127.0.0.1:${viteAddress.port}/artifact-csp-probe.html`); | |
| const payloads = [ | |
| { id: 'comment-head', html: `<!-- <head> --><img src="${probeBaseUrl}/probe/comment-head">` }, | |
| { id: 'before-head', html: `<img src="${probeBaseUrl}/probe/before-head"><head><title>late head</title></head>` }, | |
| { id: 'inline-script', html: `<script>location.href=${JSON.stringify(`${probeBaseUrl}/probe/inline-script`)}</script>` }, | |
| { id: 'meta-refresh', html: `<meta http-equiv="refresh" content="0;url=${probeBaseUrl}/probe/meta-refresh">` }, | |
| ]; | |
| for (const payload of payloads) { | |
| await page.evaluate(async ({ artifactId, sourceHtml }) => { | |
| const { prepareHtmlArtifact } = await import('/src/lib/agent-tools.ts'); | |
| const artifact = prepareHtmlArtifact(sourceHtml, 'CSP probe', artifactId); | |
| const iframe = document.createElement('iframe'); | |
| iframe.dataset.artifactId = artifactId; | |
| iframe.setAttribute('sandbox', ''); | |
| iframe.setAttribute('credentialless', ''); | |
| await new Promise((resolveLoad) => { | |
| iframe.addEventListener('load', resolveLoad, { once: true }); | |
| iframe.srcdoc = artifact.sandboxedSource; | |
| document.body.append(iframe); | |
| }); | |
| }, { artifactId: payload.id, sourceHtml: payload.html }); | |
| } | |
| await page.evaluate(async () => { | |
| const { prepareHtmlArtifact } = await import('/src/lib/agent-tools.ts'); | |
| const artifact = prepareHtmlArtifact( | |
| '<style>#static-card{color:rgb(17, 34, 51);background:rgb(238, 244, 240)}</style><section id="static-card">Static artifact rendered</section>', | |
| 'Static render probe', | |
| 'static-render', | |
| ); | |
| const iframe = document.createElement('iframe'); | |
| iframe.dataset.artifactId = artifact.id; | |
| iframe.setAttribute('sandbox', ''); | |
| iframe.setAttribute('credentialless', ''); | |
| await new Promise((resolveLoad) => { | |
| iframe.addEventListener('load', resolveLoad, { once: true }); | |
| iframe.srcdoc = artifact.sandboxedSource; | |
| document.body.append(iframe); | |
| }); | |
| }); | |
| await new Promise((resolveWait) => setTimeout(resolveWait, 200)); | |
| if (probeHits.length > 0) { | |
| throw new Error(`Artifact CSP leaked ${probeHits.length} request(s): ${probeHits.join(', ')}`); | |
| } | |
| const staticCard = page.frameLocator('iframe[data-artifact-id="static-render"]').locator('#static-card'); | |
| await staticCard.waitFor(); | |
| const staticRender = await staticCard.evaluate((element) => ({ | |
| text: element.textContent, | |
| color: getComputedStyle(element).color, | |
| background: getComputedStyle(element).backgroundColor, | |
| })); | |
| if (staticRender.text !== 'Static artifact rendered' | |
| || staticRender.color !== 'rgb(17, 34, 51)' | |
| || staticRender.background !== 'rgb(238, 244, 240)') { | |
| throw new Error(`Static HTML/CSS render failed: ${JSON.stringify(staticRender)}`); | |
| } | |
| console.log(`artifact CSP probe passed: ${payloads.length} bypass payloads emitted 0 requests; static HTML/CSS rendered`); | |
| } finally { | |
| await browser?.close(); | |
| await vite?.close(); | |
| await close(probe); | |
| } | |