From 948e7d32608fd385395d0e4cd1bbed286049a969 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 22 Dec 2023 11:04:23 +0100 Subject: [PATCH] feat(sveltekit): Add options to configure fetch instrumentation script for CSP (#9969) This PR adds options to the `sentryHandle` request handler that allows control over CSP-relevant aspects for the ` - `; - return html.replace('', content); - } +/** + * Adds Sentry tracing tags to the returned html page. + * Adds Sentry fetch proxy script to the returned html page if enabled in options. + * Also adds a nonce attribute to the script tag if users specified one for CSP. + * + * Exported only for testing + */ +export function addSentryCodeToPage(options: SentryHandleOptions): NonNullable { + const { fetchProxyScriptNonce, injectFetchProxyScript } = options; + // if injectFetchProxyScript is not set, we default to true + const shouldInjectScript = injectFetchProxyScript !== false; + const nonce = fetchProxyScriptNonce ? `nonce="${fetchProxyScriptNonce}"` : ''; - return html; -}; + return ({ html }) => { + const transaction = getActiveTransaction(); + if (transaction) { + const traceparentData = transaction.toTraceparent(); + const dynamicSamplingContext = dynamicSamplingContextToSentryBaggageHeader( + transaction.getDynamicSamplingContext(), + ); + const contentMeta = ` + + + `; + const contentScript = shouldInjectScript ? `` : ''; + + const content = `${contentMeta}\n${contentScript}`; + + return html.replace('', content); + } + + return html; + }; +} /** * A SvelteKit handle function that wraps the request for Sentry error and @@ -89,13 +127,14 @@ export const transformPageChunk: NonNullable { - const res = await resolve(event, { transformPageChunk }); + const res = await resolve(event, { + transformPageChunk: addSentryCodeToPage(options), + }); if (span) { span.setHttpStatus(res.status); } diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 1444b75d9ea5..cca809006d27 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -6,7 +6,7 @@ import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; -import { sentryHandle, transformPageChunk } from '../../src/server/handle'; +import { FETCH_PROXY_SCRIPT, addSentryCodeToPage, sentryHandle } from '../../src/server/handle'; import { getDefaultNodeClientOptions } from '../utils'; const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => 'xx'); @@ -337,7 +337,7 @@ describe('handleSentry', () => { }); }); -describe('transformPageChunk', () => { +describe('addSentryCodeToPage', () => { const html = ` @@ -351,16 +351,41 @@ describe('transformPageChunk', () => { `; it('does not add meta tags if no active transaction', () => { + const transformPageChunk = addSentryCodeToPage({}); const transformed = transformPageChunk({ html, done: true }); expect(transformed).toEqual(html); }); - it('adds meta tags if there is an active transaction', () => { + it('adds meta tags and the fetch proxy script if there is an active transaction', () => { + const transformPageChunk = addSentryCodeToPage({}); const transaction = hub.startTransaction({ name: 'test' }); hub.getScope().setSpan(transaction); const transformed = transformPageChunk({ html, done: true }) as string; - expect(transformed.includes('${FETCH_PROXY_SCRIPT}`); + }); + + it('adds a nonce attribute to the script if the `fetchProxyScriptNonce` option is specified', () => { + const transformPageChunk = addSentryCodeToPage({ fetchProxyScriptNonce: '123abc' }); + const transaction = hub.startTransaction({ name: 'test' }); + hub.getScope().setSpan(transaction); + const transformed = transformPageChunk({ html, done: true }) as string; + + expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`); + }); + + it('does not add the fetch proxy script if the `injectFetchProxyScript` option is false', () => { + const transformPageChunk = addSentryCodeToPage({ injectFetchProxyScript: false }); + const transaction = hub.startTransaction({ name: 'test' }); + hub.getScope().setSpan(transaction); + const transformed = transformPageChunk({ html, done: true }) as string; + + expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`); }); });