mirror of
https://github.com/withastro/astro.git
synced 2025-02-17 22:44:24 -05:00
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>
This commit is contained in:
parent
c30a415986
commit
2d4c8faa56
7 changed files with 84 additions and 78 deletions
30
.changeset/chatty-experts-smell.md
Normal file
30
.changeset/chatty-experts-smell.md
Normal file
|
@ -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).
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ const ASTRO_CONFIG_DEFAULTS = {
|
|||
vite: {},
|
||||
legacy: {},
|
||||
redirects: {},
|
||||
security: {},
|
||||
experimental: {
|
||||
actions: false,
|
||||
directRenderScript: false,
|
||||
|
@ -86,7 +87,6 @@ const ASTRO_CONFIG_DEFAULTS = {
|
|||
contentCollectionJsonSchema: false,
|
||||
clientPrerender: false,
|
||||
globalRoutePriority: false,
|
||||
security: {},
|
||||
rewriting: false,
|
||||
},
|
||||
} satisfies AstroUserConfig & { server: { open: boolean } };
|
||||
|
@ -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),
|
||||
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue