Skip to content

Commit 2d4c8fa

Browse files
ematipicosarah11918
andauthoredMay 22, 2024··
feat: make CSRF protection stable (#11021)
* feat: make CSRF protection stable * revert change * Apply suggestions from code review Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update packages/astro/src/@types/astro.ts Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * beef up changeset * Update .changeset/chatty-experts-smell.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Update .changeset/chatty-experts-smell.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * move section * Apply suggestions from code review Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
1 parent c30a415 commit 2d4c8fa

File tree

7 files changed

+84
-78
lines changed

7 files changed

+84
-78
lines changed
 

‎.changeset/chatty-experts-smell.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"astro": minor
3+
---
4+
5+
The CSRF protection feature that was introduced behind a flag in [v4.6.0](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#460) is no longer experimental and is available for general use.
6+
7+
To enable the stable version, add the new top-level `security` option in `astro.config.mjs`. If you were previously using the experimental version of this feature, also delete the experimental flag:
8+
9+
```diff
10+
export default defineConfig({
11+
- experimental: {
12+
- security: {
13+
- csrfProtection: {
14+
- origin: true
15+
- }
16+
- }
17+
- },
18+
+ security: {
19+
+ checkOrigin: true
20+
+ }
21+
})
22+
```
23+
24+
Enabling this setting performs a check that the `"origin"` header, automatically passed by all modern browsers, matches the URL sent by each Request.
25+
26+
This check is executed only for pages rendered on demand, and only for the requests `POST`, `PATCH`, `DELETE` and `PUT` with one of the following `"content-type"` headers: `'application/x-www-form-urlencoded'`, `'multipart/form-data'`, `'text/plain'`.
27+
28+
If the `"origin"` header doesn't match the pathname of the request, Astro will return a 403 status code and won't render the page.
29+
30+
For more information, see the [`security` configuration docs](https://docs.astro.build/en/reference/configuration-reference/#security).

‎packages/astro/src/@types/astro.ts

+42-57
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,47 @@ export interface AstroUserConfig {
779779
*/
780780
scopedStyleStrategy?: 'where' | 'class' | 'attribute';
781781

782+
/**
783+
* @docs
784+
* @name security
785+
* @type {boolean}
786+
* @default `{}`
787+
* @version 4.9.0
788+
* @description
789+
*
790+
* Enables security measures for an Astro website.
791+
*
792+
* These features only exist for pages rendered on demand (SSR) using `server` mode or pages that opt out of prerendering in `hybrid` mode.
793+
*
794+
* ```js
795+
* // astro.config.mjs
796+
* export default defineConfig({
797+
* output: "server",
798+
* security: {
799+
* checkOrigin: true
800+
* }
801+
* })
802+
* ```
803+
*/
804+
security?: {
805+
/**
806+
* @name security.checkOrigin
807+
* @type {boolean}
808+
* @default 'false'
809+
* @version 4.6.0
810+
* @description
811+
*
812+
* When enabled, performs a check that the "origin" header, automatically passed by all modern browsers, matches the URL sent by each `Request`. This is used to provide Cross-Site Request Forgery (CSRF) protection.
813+
*
814+
* The "origin" check is executed only for pages rendered on demand, and only for the requests `POST, `PATCH`, `DELETE` and `PUT` with
815+
* the following `content-type` header: 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'.
816+
*
817+
* If the "origin" header doesn't match the `pathname` of the request, Astro will return a 403 status code and will not render the page.
818+
*/
819+
820+
checkOrigin?: boolean;
821+
};
822+
782823
/**
783824
* @docs
784825
* @name vite
@@ -1955,63 +1996,7 @@ export interface AstroUserConfig {
19551996
* In the event of route collisions, where two routes of equal route priority attempt to build the same URL, Astro will log a warning identifying the conflicting routes.
19561997
*/
19571998
globalRoutePriority?: boolean;
1958-
1959-
/**
1960-
* @docs
1961-
* @name experimental.security
1962-
* @type {boolean}
1963-
* @default `false`
1964-
* @version 4.6.0
1965-
* @description
1966-
*
1967-
* Enables CSRF protection for Astro websites.
1968-
*
1969-
* The CSRF protection works only for pages rendered on demand (SSR) using `server` or `hybrid` mode. The pages must opt out of prerendering in `hybrid` mode.
1970-
*
1971-
* ```js
1972-
* // astro.config.mjs
1973-
* export default defineConfig({
1974-
* output: "server",
1975-
* experimental: {
1976-
* security: {
1977-
* csrfProtection: {
1978-
* origin: true
1979-
* }
1980-
* }
1981-
* }
1982-
* })
1983-
* ```
1984-
*/
1985-
security?: {
1986-
/**
1987-
* @name security.csrfProtection
1988-
* @type {object}
1989-
* @default '{}'
1990-
* @version 4.6.0
1991-
* @description
1992-
*
1993-
* Allows you to enable security measures to prevent CSRF attacks: https://owasp.org/www-community/attacks/csrf
1994-
*/
1995-
1996-
csrfProtection?: {
1997-
/**
1998-
* @name security.csrfProtection.origin
1999-
* @type {boolean}
2000-
* @default 'false'
2001-
* @version 4.6.0
2002-
* @description
2003-
*
2004-
* When enabled, performs a check that the "origin" header, automatically passed by all modern browsers, matches the URL sent by each `Request`.
2005-
*
2006-
* The "origin" check is executed only for pages rendered on demand, and only for the requests `POST, `PATCH`, `DELETE` and `PUT` with
2007-
* the following `content-type` header: 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'.
2008-
*
2009-
* If the "origin" header doesn't match the `pathname` of the request, Astro will return a 403 status code and will not render the page.
2010-
*/
2011-
origin?: boolean;
2012-
};
2013-
};
2014-
1999+
20152000
/**
20162001
* @docs
20172002
* @name experimental.rewriting

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,6 @@ function createBuildManifest(
558558
buildFormat: settings.config.build.format,
559559
middleware,
560560
rewritingEnabled: settings.config.experimental.rewriting,
561-
checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
561+
checkOrigin: settings.config.security?.checkOrigin ?? false,
562562
};
563563
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ function buildManifest(
277277
assets: staticFiles.map(prefixAssetPath),
278278
i18n: i18nManifest,
279279
buildFormat: settings.config.build.format,
280-
checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
280+
checkOrigin: settings.config.security?.checkOrigin ?? false,
281281
rewritingEnabled: settings.config.experimental.rewriting,
282282
};
283283
}

‎packages/astro/src/core/config/schema.ts

+7-12
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,14 @@ const ASTRO_CONFIG_DEFAULTS = {
7979
vite: {},
8080
legacy: {},
8181
redirects: {},
82+
security: {},
8283
experimental: {
8384
actions: false,
8485
directRenderScript: false,
8586
contentCollectionCache: false,
8687
contentCollectionJsonSchema: false,
8788
clientPrerender: false,
8889
globalRoutePriority: false,
89-
security: {},
9090
rewriting: false,
9191
},
9292
} satisfies AstroUserConfig & { server: { open: boolean } };
@@ -492,6 +492,12 @@ export const AstroConfigSchema = z.object({
492492
}
493493
})
494494
),
495+
security: z
496+
.object({
497+
checkOrigin: z.boolean().default(false),
498+
})
499+
.optional()
500+
.default(ASTRO_CONFIG_DEFAULTS.security),
495501
experimental: z
496502
.object({
497503
actions: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.actions),
@@ -515,17 +521,6 @@ export const AstroConfigSchema = z.object({
515521
.boolean()
516522
.optional()
517523
.default(ASTRO_CONFIG_DEFAULTS.experimental.globalRoutePriority),
518-
security: z
519-
.object({
520-
csrfProtection: z
521-
.object({
522-
origin: z.boolean().default(false),
523-
})
524-
.optional()
525-
.default({}),
526-
})
527-
.optional()
528-
.default(ASTRO_CONFIG_DEFAULTS.experimental.security),
529524
rewriting: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.rewriting),
530525
})
531526
.strict(

‎packages/astro/src/vite-plugin-astro-server/plugin.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
144144
componentMetadata: new Map(),
145145
inlinedScripts: new Map(),
146146
i18n: i18nManifest,
147-
checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
147+
checkOrigin: settings.config.security?.checkOrigin ?? false,
148148
rewritingEnabled: settings.config.experimental.rewriting,
149149
middleware(_, next) {
150150
return next();

‎packages/astro/test/fixtures/csrf-check-origin/astro.config.mjs

+2-6
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@ import { defineConfig } from 'astro/config';
33
// https://astro.build/config
44
export default defineConfig({
55
output: "server",
6-
experimental: {
7-
security: {
8-
csrfProtection: {
9-
origin: true
10-
}
11-
}
6+
security: {
7+
checkOrigin: true
128
}
139
});
1410

0 commit comments

Comments
 (0)
Please sign in to comment.