Skip to content
Merged
8 changes: 8 additions & 0 deletions .changeset/tiny-books-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@astrojs/netlify': minor
'@astrojs/vercel': minor
'@astrojs/node': minor
'astro': minor
---

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))
2 changes: 1 addition & 1 deletion packages/astro/e2e/csp-client-only.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { testFactory } from './test-utils.js';

const test = testFactory(import.meta.url, {
root: './fixtures/client-only/',
experimental: {
security: {
csp: true,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default defineConfig({
adapter: nodejs({ mode: 'standalone' }),
integrations: [react(), mdx()],
trailingSlash: process.env.TRAILING_SLASH ?? 'always',
experimental: {
security: {
csp: true
}
});
4 changes: 2 additions & 2 deletions packages/astro/src/assets/fonts/vite-plugin-fonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ export function fontsPlugin({ settings, sync, logger }: Options): Plugin {
consumableMap = res.consumableMap;

// Handle CSP
if (shouldTrackCspHashes(settings.config.experimental.csp)) {
const algorithm = getAlgorithm(settings.config.experimental.csp);
if (shouldTrackCspHashes(settings.config.security.csp)) {
const algorithm = getAlgorithm(settings.config.security.csp);

// Generate a hash for each style we generate
for (const { css } of internalConsumableMap.values()) {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ async function generatePath(

if (
settings.adapter?.adapterFeatures?.experimentalStaticHeaders &&
settings.config.experimental?.csp
settings.config.security?.csp
) {
routeToHeaders.set(pathname, { headers: responseHeaders, route: integrationRoute });
}
Expand Down
14 changes: 7 additions & 7 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,14 +301,14 @@ async function buildManifest(

let csp: SSRManifestCSP | undefined = undefined;

if (shouldTrackCspHashes(settings.config.experimental.csp)) {
const algorithm = getAlgorithm(settings.config.experimental.csp);
if (shouldTrackCspHashes(settings.config.security.csp)) {
const algorithm = getAlgorithm(settings.config.security.csp);
const scriptHashes = [
...getScriptHashes(settings.config.experimental.csp),
...getScriptHashes(settings.config.security.csp),
...(await trackScriptHashes(internals, settings, algorithm)),
];
const styleHashes = [
...getStyleHashes(settings.config.experimental.csp),
...getStyleHashes(settings.config.security.csp),
...settings.injectedCsp.styleHashes,
...(await trackStyleHashes(internals, settings, algorithm)),
];
Expand All @@ -318,12 +318,12 @@ async function buildManifest(
? 'adapter'
: undefined,
scriptHashes,
scriptResources: getScriptResources(settings.config.experimental.csp),
scriptResources: getScriptResources(settings.config.security.csp),
styleHashes,
styleResources: getStyleResources(settings.config.experimental.csp),
styleResources: getStyleResources(settings.config.security.csp),
algorithm,
directives: getDirectives(settings),
isStrictDynamic: getStrictDynamic(settings.config.experimental.csp),
isStrictDynamic: getStrictDynamic(settings.config.security.csp),
};
}

Expand Down
48 changes: 24 additions & 24 deletions packages/astro/src/core/config/schemas/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
security: {
checkOrigin: true,
allowedDomains: [],
csp: false,
},
env: {
schema: {},
Expand All @@ -99,7 +100,6 @@ export const ASTRO_CONFIG_DEFAULTS = {
experimental: {
clientPrerender: false,
contentIntellisense: false,
csp: false,
chromeDevtoolsWorkspace: false,
svgo: false,
},
Expand Down Expand Up @@ -432,6 +432,29 @@ export const AstroConfigSchema = z.object({
)
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security.allowedDomains),
csp: z
.union([
z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.security.csp),
z.object({
algorithm: cspAlgorithmSchema,
directives: z.array(allowedDirectivesSchema).optional(),
styleDirective: z
.object({
resources: z.array(z.string()).optional(),
hashes: z.array(cspHashSchema).optional(),
})
.optional(),
scriptDirective: z
.object({
resources: z.array(z.string()).optional(),
hashes: z.array(cspHashSchema).optional(),
strictDynamic: z.boolean().optional(),
})
.optional(),
}),
])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security.csp),
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security),
Expand Down Expand Up @@ -482,29 +505,6 @@ export const AstroConfigSchema = z.object({
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.contentIntellisense),
fonts: z.array(z.union([localFontFamilySchema, remoteFontFamilySchema])).optional(),
csp: z
.union([
z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.csp),
z.object({
algorithm: cspAlgorithmSchema,
directives: z.array(allowedDirectivesSchema).optional(),
styleDirective: z
.object({
resources: z.array(z.string()).optional(),
hashes: z.array(cspHashSchema).optional(),
})
.optional(),
scriptDirective: z
.object({
resources: z.array(z.string()).optional(),
hashes: z.array(cspHashSchema).optional(),
strictDynamic: z.boolean().optional(),
})
.optional(),
}),
])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.csp),
chromeDevtoolsWorkspace: z
.boolean()
.optional()
Expand Down
6 changes: 3 additions & 3 deletions packages/astro/src/core/csp/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { BuildInternals } from '../build/internal.js';
import { generateCspDigest } from '../encryption.js';
import type { CspDirective } from './config.js';

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

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

Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/core/dev/restart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async function restartContainer(container: Container): Promise<Container | Error

try {
const { astroConfig } = await resolveConfig(container.inlineConfig, 'dev', container.fs);
if (astroConfig.experimental.csp) {
if (astroConfig.security.csp) {
logger.warn(
'config',
"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.",
Expand Down Expand Up @@ -130,7 +130,7 @@ export async function createContainerWithAutomaticRestart({
}: CreateContainerWithAutomaticRestart): Promise<Restart> {
const logger = createNodeLogger(inlineConfig ?? {});
const { userConfig, astroConfig } = await resolveConfig(inlineConfig ?? {}, 'dev', fs);
if (astroConfig.experimental.csp) {
if (astroConfig.security.csp) {
logger.warn(
'config',
"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.",
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1422,12 +1422,12 @@ export const FontFamilyNotFound = {
* @description
* The CSP feature isn't enabled
* @message
* The `experimental.csp` configuration isn't enabled.
* The `security.csp` configuration isn't enabled.
*/
export const CspNotEnabled = {
name: 'CspNotEnabled',
title: "CSP feature isn't enabled",
message: "The `experimental.csp` configuration isn't enabled.",
message: "The `security.csp` configuration isn't enabled.",
} satisfies ErrorData;

/**
Expand Down
16 changes: 8 additions & 8 deletions packages/astro/src/manifest/serialized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const SERIALIZED_MANIFEST_RESOLVED_ID = '\0' + SERIALIZED_MANIFEST_ID;
export function serializedManifestPlugin({
settings,
command,
sync
sync,
}: {
settings: AstroSettings;
command: 'dev' | 'build';
Expand Down Expand Up @@ -102,18 +102,18 @@ async function createSerializedManifest(settings: AstroSettings): Promise<Serial
};
}

if (shouldTrackCspHashes(settings.config.experimental.csp)) {
if (shouldTrackCspHashes(settings.config.security.csp)) {
csp = {
cspDestination: settings.adapter?.adapterFeatures?.experimentalStaticHeaders
? 'adapter'
: undefined,
scriptHashes: getScriptHashes(settings.config.experimental.csp),
scriptResources: getScriptResources(settings.config.experimental.csp),
styleHashes: getStyleHashes(settings.config.experimental.csp),
styleResources: getStyleResources(settings.config.experimental.csp),
algorithm: getAlgorithm(settings.config.experimental.csp),
scriptHashes: getScriptHashes(settings.config.security.csp),
scriptResources: getScriptResources(settings.config.security.csp),
styleHashes: getStyleHashes(settings.config.security.csp),
styleResources: getStyleResources(settings.config.security.csp),
algorithm: getAlgorithm(settings.config.security.csp),
directives: getDirectives(settings),
isStrictDynamic: getStrictDynamic(settings.config.experimental.csp),
isStrictDynamic: getStrictDynamic(settings.config.security.csp),
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type SerializedRouteData = Omit<
};
};

type CspObject = Required<Exclude<AstroConfig['experimental']['csp'], boolean>>;
type CspObject = Required<Exclude<AstroConfig['security']['csp'], boolean>>;

export interface AstroSettings {
config: AstroConfig;
Expand Down
Loading