Skip to content

Commit

Permalink
feat: make CSRF protection stable (#11021)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
ematipico and sarah11918 committed May 22, 2024
1 parent c30a415 commit 2d4c8fa
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 78 deletions.
30 changes: 30 additions & 0 deletions .changeset/chatty-experts-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
"astro": minor
---

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.

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:

```diff
export default defineConfig({
- experimental: {
- security: {
- csrfProtection: {
- origin: true
- }
- }
- },
+ security: {
+ checkOrigin: true
+ }
})
```

Enabling this setting performs a check that the `"origin"` header, automatically passed by all modern browsers, matches the URL sent by each Request.

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'`.

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.

For more information, see the [`security` configuration docs](https://docs.astro.build/en/reference/configuration-reference/#security).
99 changes: 42 additions & 57 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,47 @@ export interface AstroUserConfig {
*/
scopedStyleStrategy?: 'where' | 'class' | 'attribute';

/**
* @docs
* @name security
* @type {boolean}
* @default `{}`
* @version 4.9.0
* @description
*
* Enables security measures for an Astro website.
*
* These features only exist for pages rendered on demand (SSR) using `server` mode or pages that opt out of prerendering in `hybrid` mode.
*
* ```js
* // astro.config.mjs
* export default defineConfig({
* output: "server",
* security: {
* checkOrigin: true
* }
* })
* ```
*/
security?: {
/**
* @name security.checkOrigin
* @type {boolean}
* @default 'false'
* @version 4.6.0
* @description
*
* 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.
*
* The "origin" check is executed only for pages rendered on demand, and only for the requests `POST, `PATCH`, `DELETE` and `PUT` with
* the following `content-type` header: 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'.
*
* 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.
*/

checkOrigin?: boolean;
};

/**
* @docs
* @name vite
Expand Down Expand Up @@ -1955,63 +1996,7 @@ export interface AstroUserConfig {
* 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.
*/
globalRoutePriority?: boolean;

/**
* @docs
* @name experimental.security
* @type {boolean}
* @default `false`
* @version 4.6.0
* @description
*
* Enables CSRF protection for Astro websites.
*
* 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.
*
* ```js
* // astro.config.mjs
* export default defineConfig({
* output: "server",
* experimental: {
* security: {
* csrfProtection: {
* origin: true
* }
* }
* }
* })
* ```
*/
security?: {
/**
* @name security.csrfProtection
* @type {object}
* @default '{}'
* @version 4.6.0
* @description
*
* Allows you to enable security measures to prevent CSRF attacks: https://owasp.org/www-community/attacks/csrf
*/

csrfProtection?: {
/**
* @name security.csrfProtection.origin
* @type {boolean}
* @default 'false'
* @version 4.6.0
* @description
*
* When enabled, performs a check that the "origin" header, automatically passed by all modern browsers, matches the URL sent by each `Request`.
*
* The "origin" check is executed only for pages rendered on demand, and only for the requests `POST, `PATCH`, `DELETE` and `PUT` with
* the following `content-type` header: 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'.
*
* 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.
*/
origin?: boolean;
};
};


/**
* @docs
* @name experimental.rewriting
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 @@ -558,6 +558,6 @@ function createBuildManifest(
buildFormat: settings.config.build.format,
middleware,
rewritingEnabled: settings.config.experimental.rewriting,
checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
checkOrigin: settings.config.security?.checkOrigin ?? false,
};
}
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ function buildManifest(
assets: staticFiles.map(prefixAssetPath),
i18n: i18nManifest,
buildFormat: settings.config.build.format,
checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
checkOrigin: settings.config.security?.checkOrigin ?? false,
rewritingEnabled: settings.config.experimental.rewriting,
};
}
19 changes: 7 additions & 12 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ const ASTRO_CONFIG_DEFAULTS = {
vite: {},
legacy: {},
redirects: {},
security: {},
experimental: {
actions: false,
directRenderScript: false,
contentCollectionCache: false,
contentCollectionJsonSchema: false,
clientPrerender: false,
globalRoutePriority: false,
security: {},
rewriting: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };
Expand Down Expand Up @@ -492,6 +492,12 @@ export const AstroConfigSchema = z.object({
}
})
),
security: z
.object({
checkOrigin: z.boolean().default(false),
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security),
experimental: z
.object({
actions: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.actions),
Expand All @@ -515,17 +521,6 @@ export const AstroConfigSchema = z.object({
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.globalRoutePriority),
security: z
.object({
csrfProtection: z
.object({
origin: z.boolean().default(false),
})
.optional()
.default({}),
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.security),
rewriting: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.rewriting),
})
.strict(
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/vite-plugin-astro-server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest
componentMetadata: new Map(),
inlinedScripts: new Map(),
i18n: i18nManifest,
checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
checkOrigin: settings.config.security?.checkOrigin ?? false,
rewritingEnabled: settings.config.experimental.rewriting,
middleware(_, next) {
return next();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
output: "server",
experimental: {
security: {
csrfProtection: {
origin: true
}
}
security: {
checkOrigin: true
}
});

0 comments on commit 2d4c8fa

Please sign in to comment.