From f796f7c9e148b4860febe4063c28a2931adb6b2d Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Wed, 21 Jan 2026 08:36:11 +0100 Subject: [PATCH 1/2] feat(csp)!: make runtime utils optional instead of throwing --- .changeset/tall-needles-cross.md | 16 ++ packages/astro/src/core/errors/errors-data.ts | 13 -- packages/astro/src/core/logger/core.ts | 1 + packages/astro/src/core/middleware/index.ts | 11 +- packages/astro/src/core/render-context.ts | 53 ++----- packages/astro/src/types/public/context.ts | 148 +++++++++--------- 6 files changed, 110 insertions(+), 132 deletions(-) create mode 100644 .changeset/tall-needles-cross.md diff --git a/.changeset/tall-needles-cross.md b/.changeset/tall-needles-cross.md new file mode 100644 index 000000000000..927686acb52d --- /dev/null +++ b/.changeset/tall-needles-cross.md @@ -0,0 +1,16 @@ +--- +'astro': major +--- + +Makes `Astro.csp` and `context.csp` optional instead of throwing if CSP is not enabled + +Until now, `context.csp` was always defined but would throw if CSP was not enabled in the Astro config. `context.csp` can now be undefined if CSP is not enabled and its methods will never throw. + +#### What should I do? + +If you are using CSP runtime utilities, access methods conditionally: + +```diff +-Astro.csp.insertDirective("default-src 'self'"); ++Astro.csp?.insertDirective("default-src 'self'"); +``` diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index ee0d567762e5..6646e19cfb67 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1413,19 +1413,6 @@ export const FontFamilyNotFound = { hint: 'This is often caused by a typo. Check that the `` component or `getFontData()` function are using a `cssVariable` specified in your config.', } satisfies ErrorData; -/** - * @docs - * @description - * The CSP feature isn't enabled - * @message - * The `security.csp` configuration isn't enabled. - */ -export const CspNotEnabled = { - name: 'CspNotEnabled', - title: "CSP feature isn't enabled", - message: "The `security.csp` configuration isn't enabled.", -} satisfies ErrorData; - /** * @docs * @description diff --git a/packages/astro/src/core/logger/core.ts b/packages/astro/src/core/logger/core.ts index dff3b575739d..b6971ddbe74c 100644 --- a/packages/astro/src/core/logger/core.ts +++ b/packages/astro/src/core/logger/core.ts @@ -35,6 +35,7 @@ type LoggerLabel = | 'update' | 'adapter' | 'islands' + | 'csp' // SKIP_FORMAT: A special label that tells the logger not to apply any formatting. // Useful for messages that are already formatted, like the server start message. | 'SKIP_FORMAT'; diff --git a/packages/astro/src/core/middleware/index.ts b/packages/astro/src/core/middleware/index.ts index a481a998dd31..31d2a5ddaaaf 100644 --- a/packages/astro/src/core/middleware/index.ts +++ b/packages/astro/src/core/middleware/index.ts @@ -114,15 +114,8 @@ function createContext({ set locals(_) { throw new AstroError(AstroErrorData.LocalsReassigned); }, - get csp(): APIContext['csp'] { - return { - insertDirective() {}, - insertScriptResource() {}, - insertStyleResource() {}, - insertScriptHash() {}, - insertStyleHash() {}, - }; - }, + session: undefined, + csp: undefined, }; return Object.assign(context, { getActionResult: createGetActionResult(context.locals), diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index 76299a462ec1..c83319897ef2 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -17,18 +17,18 @@ import type { RouteData, SSRResult } from '../types/public/internal.js'; import type { ServerIslandMappings, SSRActions } from './app/types.js'; import { ASTRO_GENERATOR, + pipelineSymbol, REROUTE_DIRECTIVE_HEADER, REWRITE_DIRECTIVE_HEADER_KEY, REWRITE_DIRECTIVE_HEADER_VALUE, ROUTE_TYPE_HEADER, - pipelineSymbol, responseSentSymbol, } from './constants.js'; import { AstroCookies, attachCookiesToResponse } from './cookies/index.js'; import { getCookiesFromResponse } from './cookies/response.js'; import { pushDirective } from './csp/runtime.js'; import { generateCspDigest } from './encryption.js'; -import { CspNotEnabled, ForbiddenRewrite } from './errors/errors-data.js'; +import { ForbiddenRewrite } from './errors/errors-data.js'; import { AstroError, AstroErrorData } from './errors/index.js'; import { callMiddleware } from './middleware/callMiddleware.js'; import { sequence } from './middleware/index.js'; @@ -480,11 +480,15 @@ export class RenderContext { return renderContext.session; }, get csp(): APIContext['csp'] { + if (!pipeline.manifest.csp) { + pipeline.logger.warn( + 'csp', + `context.csp was used when rendering the route ${colors.green(this.routePattern)}, but CSP was not configured. For more information, see https://docs.astro.build/en/reference/experimental-flags/csp/`, + ); + return undefined; + } return { insertDirective(payload) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } if (renderContext?.result?.directives) { renderContext.result.directives = pushDirective( renderContext.result.directives, @@ -494,30 +498,16 @@ export class RenderContext { renderContext?.result?.directives.push(payload); } }, - insertScriptResource(resource) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } renderContext.result?.scriptResources.push(resource); }, insertStyleResource(resource) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } - renderContext.result?.styleResources.push(resource); }, insertStyleHash(hash) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } renderContext.result?.styleHashes.push(hash); }, insertScriptHash(hash) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } renderContext.result?.scriptHashes.push(hash); }, }; @@ -745,12 +735,15 @@ export class RenderContext { return getOriginPathname(renderContext.request); }, get csp(): APIContext['csp'] { + if (!pipeline.manifest.csp) { + pipeline.logger.warn( + 'csp', + `Astro.csp was used when rendering the route ${colors.green(this.routePattern)}, but CSP was not configured. For more information, see https://docs.astro.build/en/reference/experimental-flags/csp/`, + ); + return undefined; + } return { insertDirective(payload) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } - if (renderContext?.result?.directives) { renderContext.result.directives = pushDirective( renderContext.result.directives, @@ -760,30 +753,16 @@ export class RenderContext { renderContext?.result?.directives.push(payload); } }, - insertScriptResource(resource) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } renderContext.result?.scriptResources.push(resource); }, insertStyleResource(resource) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } - renderContext.result?.styleResources.push(resource); }, insertStyleHash(hash) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } renderContext.result?.styleHashes.push(hash); }, insertScriptHash(hash) { - if (!pipeline.manifest.csp) { - throw new AstroError(CspNotEnabled); - } renderContext.result?.scriptHashes.push(hash); }, }; diff --git a/packages/astro/src/types/public/context.ts b/packages/astro/src/types/public/context.ts index 36de666408cf..9e4d1e2af3aa 100644 --- a/packages/astro/src/types/public/context.ts +++ b/packages/astro/src/types/public/context.ts @@ -201,7 +201,7 @@ export interface APIContext< * * [Astro reference](https://docs.astro.build/en/reference/api-reference/#session) */ - session?: AstroSession; + session: AstroSession | undefined; /** * A standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing information about the current request. @@ -486,82 +486,84 @@ export interface APIContext< * * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/) */ - csp: { - /** - * It adds a specific CSP directive to the route being rendered. - * - * @param {CspDirective} directive - The directive to add to the current page. - * - * ## Example - * - * ```js - * ctx.insertDirective("default-src 'self' 'unsafe-inline' https://example.com") - * ``` - * - * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertdirective) - */ - insertDirective: (directive: CspDirective) => void; + csp: + | { + /** + * It adds a specific CSP directive to the route being rendered. + * + * @param {CspDirective} directive - The directive to add to the current page. + * + * ## Example + * + * ```js + * ctx.insertDirective("default-src 'self' 'unsafe-inline' https://example.com") + * ``` + * + * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertdirective) + */ + insertDirective: (directive: CspDirective) => void; - /** - * It set the resource for the directive `style-src` in the route being rendered. It overrides Astro's default. - * - * @param {string} payload - The source to insert in the `style-src` directive. - * - * ## Example - * - * ```js - * ctx.insertStyleResource("https://styles.cdn.example.com/") - * ``` - * - * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertstyleresource) - */ - insertStyleResource: (payload: string) => void; + /** + * It set the resource for the directive `style-src` in the route being rendered. It overrides Astro's default. + * + * @param {string} payload - The source to insert in the `style-src` directive. + * + * ## Example + * + * ```js + * ctx.insertStyleResource("https://styles.cdn.example.com/") + * ``` + * + * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertstyleresource) + */ + insertStyleResource: (payload: string) => void; - /** - * Insert a single style hash to the route being rendered. - * - * @param {CspHash} hash - The hash to insert in the `style-src` directive. - * - * ## Example - * - * ```js - * ctx.insertStyleHash("sha256-1234567890abcdef1234567890") - * ``` - * - * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertstylehash) - */ - insertStyleHash: (hash: CspHash) => void; + /** + * Insert a single style hash to the route being rendered. + * + * @param {CspHash} hash - The hash to insert in the `style-src` directive. + * + * ## Example + * + * ```js + * ctx.insertStyleHash("sha256-1234567890abcdef1234567890") + * ``` + * + * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertstylehash) + */ + insertStyleHash: (hash: CspHash) => void; - /** - * It set the resource for the directive `script-src` in the route being rendered. - * - * @param {string} resource - The source to insert in the `script-src` directive. - * - * ## Example - * - * ```js - * ctx.insertScriptResource("https://scripts.cdn.example.com/") - * ``` - * - * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertscriptresource) - */ - insertScriptResource: (resource: string) => void; + /** + * It set the resource for the directive `script-src` in the route being rendered. + * + * @param {string} resource - The source to insert in the `script-src` directive. + * + * ## Example + * + * ```js + * ctx.insertScriptResource("https://scripts.cdn.example.com/") + * ``` + * + * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertscriptresource) + */ + insertScriptResource: (resource: string) => void; - /** - * Insert a single script hash to the route being rendered. - * - * @param {CspHash} hash - The hash to insert in the `script-src` directive. - * - * ## Example - * - * ```js - * ctx.insertScriptHash("sha256-1234567890abcdef1234567890") - * ``` - * - * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertscripthash) - */ - insertScriptHash: (hash: CspHash) => void; - }; + /** + * Insert a single script hash to the route being rendered. + * + * @param {CspHash} hash - The hash to insert in the `script-src` directive. + * + * ## Example + * + * ```js + * ctx.insertScriptHash("sha256-1234567890abcdef1234567890") + * ``` + * + * [Astro reference](https://docs.astro.build/en/reference/experimental-flags/csp/#cspinsertscripthash) + */ + insertScriptHash: (hash: CspHash) => void; + } + | undefined; /** * The route currently rendered. It's stripped of the `srcDir` and the `pages` folder, and it doesn't contain the extension. From 8aa52a7ed5c1be7d4ecf231798add9ed7998e008 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Wed, 21 Jan 2026 15:41:56 +0100 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/tall-needles-cross.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.changeset/tall-needles-cross.md b/.changeset/tall-needles-cross.md index 927686acb52d..1d7c767621b7 100644 --- a/.changeset/tall-needles-cross.md +++ b/.changeset/tall-needles-cross.md @@ -2,13 +2,15 @@ 'astro': major --- -Makes `Astro.csp` and `context.csp` optional instead of throwing if CSP is not enabled +Allows `Astro.csp` and `context.csp` to be undefined instead of throwing errors when `csp: true` is not configured -Until now, `context.csp` was always defined but would throw if CSP was not enabled in the Astro config. `context.csp` can now be undefined if CSP is not enabled and its methods will never throw. +When using the experimental Content Security Policy feature in Astro 5.x, `context.csp` was always defined but would throw if `experimental.csp` was not enabled in the Astro config. + +For the stable version of this API in Astro 6, `context.csp` can now be undefined if CSP is not enabled and its methods will never throw. #### What should I do? -If you are using CSP runtime utilities, access methods conditionally: +If you were using experimental CSP runtime utilities, you must now access methods conditionally: ```diff -Astro.csp.insertDirective("default-src 'self'");