Skip to content

Commit 95c40f7

Browse files
ematipicoflorian-lefebvresarah11918
authored
feat: stabilise CSP (#14946)
Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com>
1 parent b03a635 commit 95c40f7

28 files changed

Lines changed: 347 additions & 334 deletions

File tree

.changeset/tiny-books-scream.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@astrojs/netlify': minor
3+
'@astrojs/vercel': minor
4+
'@astrojs/node': minor
5+
'astro': minor
6+
---
7+
8+
Removes the `experimental.csp` flag and replaces it with a new configuration option `security.csp` - ([v6 upgrade guidance](https://v6.docs.astro.build/en/guides/upgrade-to/v6/#experimental-flags))

packages/astro/e2e/csp-client-only.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { testFactory } from './test-utils.js';
33

44
const test = testFactory(import.meta.url, {
55
root: './fixtures/client-only/',
6-
experimental: {
6+
security: {
77
csp: true,
88
},
99
});

packages/astro/e2e/fixtures/csp-server-islands/astro.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default defineConfig({
1010
adapter: nodejs({ mode: 'standalone' }),
1111
integrations: [react(), mdx()],
1212
trailingSlash: process.env.TRAILING_SLASH ?? 'always',
13-
experimental: {
13+
security: {
1414
csp: true
1515
}
1616
});

packages/astro/src/assets/fonts/vite-plugin-fonts.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
188188
consumableMap = res.consumableMap;
189189

190190
// Handle CSP
191-
if (shouldTrackCspHashes(settings.config.experimental.csp)) {
192-
const algorithm = getAlgorithm(settings.config.experimental.csp);
191+
if (shouldTrackCspHashes(settings.config.security.csp)) {
192+
const algorithm = getAlgorithm(settings.config.security.csp);
193193

194194
// Generate a hash for each style we generate
195195
for (const { css } of internalConsumableMap.values()) {

packages/astro/src/core/build/generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ async function generatePath(
612612

613613
if (
614614
settings.adapter?.adapterFeatures?.experimentalStaticHeaders &&
615-
settings.config.experimental?.csp
615+
settings.config.security?.csp
616616
) {
617617
routeToHeaders.set(pathname, { headers: responseHeaders, route: integrationRoute });
618618
}

packages/astro/src/core/build/plugins/plugin-manifest.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -301,14 +301,14 @@ async function buildManifest(
301301

302302
let csp: SSRManifestCSP | undefined = undefined;
303303

304-
if (shouldTrackCspHashes(settings.config.experimental.csp)) {
305-
const algorithm = getAlgorithm(settings.config.experimental.csp);
304+
if (shouldTrackCspHashes(settings.config.security.csp)) {
305+
const algorithm = getAlgorithm(settings.config.security.csp);
306306
const scriptHashes = [
307-
...getScriptHashes(settings.config.experimental.csp),
307+
...getScriptHashes(settings.config.security.csp),
308308
...(await trackScriptHashes(internals, settings, algorithm)),
309309
];
310310
const styleHashes = [
311-
...getStyleHashes(settings.config.experimental.csp),
311+
...getStyleHashes(settings.config.security.csp),
312312
...settings.injectedCsp.styleHashes,
313313
...(await trackStyleHashes(internals, settings, algorithm)),
314314
];
@@ -318,12 +318,12 @@ async function buildManifest(
318318
? 'adapter'
319319
: undefined,
320320
scriptHashes,
321-
scriptResources: getScriptResources(settings.config.experimental.csp),
321+
scriptResources: getScriptResources(settings.config.security.csp),
322322
styleHashes,
323-
styleResources: getStyleResources(settings.config.experimental.csp),
323+
styleResources: getStyleResources(settings.config.security.csp),
324324
algorithm,
325325
directives: getDirectives(settings),
326-
isStrictDynamic: getStrictDynamic(settings.config.experimental.csp),
326+
isStrictDynamic: getStrictDynamic(settings.config.security.csp),
327327
};
328328
}
329329

packages/astro/src/core/config/schemas/base.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
8989
security: {
9090
checkOrigin: true,
9191
allowedDomains: [],
92+
csp: false,
9293
},
9394
env: {
9495
schema: {},
@@ -99,7 +100,6 @@ export const ASTRO_CONFIG_DEFAULTS = {
99100
experimental: {
100101
clientPrerender: false,
101102
contentIntellisense: false,
102-
csp: false,
103103
chromeDevtoolsWorkspace: false,
104104
svgo: false,
105105
},
@@ -432,6 +432,29 @@ export const AstroConfigSchema = z.object({
432432
)
433433
.optional()
434434
.default(ASTRO_CONFIG_DEFAULTS.security.allowedDomains),
435+
csp: z
436+
.union([
437+
z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.security.csp),
438+
z.object({
439+
algorithm: cspAlgorithmSchema,
440+
directives: z.array(allowedDirectivesSchema).optional(),
441+
styleDirective: z
442+
.object({
443+
resources: z.array(z.string()).optional(),
444+
hashes: z.array(cspHashSchema).optional(),
445+
})
446+
.optional(),
447+
scriptDirective: z
448+
.object({
449+
resources: z.array(z.string()).optional(),
450+
hashes: z.array(cspHashSchema).optional(),
451+
strictDynamic: z.boolean().optional(),
452+
})
453+
.optional(),
454+
}),
455+
])
456+
.optional()
457+
.default(ASTRO_CONFIG_DEFAULTS.security.csp),
435458
})
436459
.optional()
437460
.default(ASTRO_CONFIG_DEFAULTS.security),
@@ -482,29 +505,6 @@ export const AstroConfigSchema = z.object({
482505
.optional()
483506
.default(ASTRO_CONFIG_DEFAULTS.experimental.contentIntellisense),
484507
fonts: z.array(z.union([localFontFamilySchema, remoteFontFamilySchema])).optional(),
485-
csp: z
486-
.union([
487-
z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.csp),
488-
z.object({
489-
algorithm: cspAlgorithmSchema,
490-
directives: z.array(allowedDirectivesSchema).optional(),
491-
styleDirective: z
492-
.object({
493-
resources: z.array(z.string()).optional(),
494-
hashes: z.array(cspHashSchema).optional(),
495-
})
496-
.optional(),
497-
scriptDirective: z
498-
.object({
499-
resources: z.array(z.string()).optional(),
500-
hashes: z.array(cspHashSchema).optional(),
501-
strictDynamic: z.boolean().optional(),
502-
})
503-
.optional(),
504-
}),
505-
])
506-
.optional()
507-
.default(ASTRO_CONFIG_DEFAULTS.experimental.csp),
508508
chromeDevtoolsWorkspace: z
509509
.boolean()
510510
.optional()

packages/astro/src/core/csp/common.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { BuildInternals } from '../build/internal.js';
99
import { generateCspDigest } from '../encryption.js';
1010
import type { CspDirective } from './config.js';
1111

12-
type EnabledCsp = Exclude<AstroConfig['experimental']['csp'], false>;
12+
type EnabledCsp = Exclude<AstroConfig['security']['csp'], false>;
1313

1414
export function shouldTrackCspHashes(csp: any): csp is EnabledCsp {
1515
return csp === true || typeof csp === 'object';
@@ -55,10 +55,10 @@ export function getStyleResources(csp: EnabledCsp): string[] {
5555
// because it has to collect and deduplicate font resources from both the user
5656
// config and the vite plugin for fonts
5757
export function getDirectives(settings: AstroSettings): CspDirective[] {
58-
if (!shouldTrackCspHashes(settings.config.experimental.csp)) {
58+
const { csp } = settings.config.security;
59+
if (!shouldTrackCspHashes(csp)) {
5960
return [];
6061
}
61-
const { csp } = settings.config.experimental;
6262
const userDirectives = csp === true ? [] : [...(csp.directives ?? [])];
6363
const fontResources = Array.from(settings.injectedCsp.fontResources.values());
6464

packages/astro/src/core/dev/restart.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ async function restartContainer(container: Container): Promise<Container | Error
7878

7979
try {
8080
const { astroConfig } = await resolveConfig(container.inlineConfig, 'dev', container.fs);
81-
if (astroConfig.experimental.csp) {
81+
if (astroConfig.security.csp) {
8282
logger.warn(
8383
'config',
8484
"Astro's Content Security Policy (CSP) does not work in development mode. To verify your CSP implementation, build the project and run the preview server.",
@@ -130,7 +130,7 @@ export async function createContainerWithAutomaticRestart({
130130
}: CreateContainerWithAutomaticRestart): Promise<Restart> {
131131
const logger = createNodeLogger(inlineConfig ?? {});
132132
const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'dev', fs);
133-
if (astroConfig.experimental.csp) {
133+
if (astroConfig.security.csp) {
134134
logger.warn(
135135
'config',
136136
"Astro's Content Security Policy (CSP) does not work in development mode. To verify your CSP implementation, build the project and run the preview server.",

packages/astro/src/core/errors/errors-data.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,12 +1422,12 @@ export const FontFamilyNotFound = {
14221422
* @description
14231423
* The CSP feature isn't enabled
14241424
* @message
1425-
* The `experimental.csp` configuration isn't enabled.
1425+
* The `security.csp` configuration isn't enabled.
14261426
*/
14271427
export const CspNotEnabled = {
14281428
name: 'CspNotEnabled',
14291429
title: "CSP feature isn't enabled",
1430-
message: "The `experimental.csp` configuration isn't enabled.",
1430+
message: "The `security.csp` configuration isn't enabled.",
14311431
} satisfies ErrorData;
14321432

14331433
/**

0 commit comments

Comments
 (0)