-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add origin check for CSRF protection (#10678)
* feat: add origin check for CSRF protection * add tests * chore: documentation * changeset and grammar * chore: add casing check * split function * better naming * make the whole object experimental * remove unused type * update changeset * manually apply Sarah's suggestions * Apply suggestions from code review Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
- Loading branch information
1 parent
ba3af20
commit 2e53b5f
Showing
14 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
--- | ||
"astro": minor | ||
--- | ||
|
||
Adds a new experimental security option to prevent [Cross-Site Request Forgery (CSRF) attacks](https://owasp.org/www-community/attacks/csrf). This feature is available only for pages rendered on demand: | ||
|
||
```js | ||
import { defineConfig } from "astro/config" | ||
export default defineConfig({ | ||
experimental: { | ||
security: { | ||
csrfProtection: { | ||
origin: 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 experimental "origin" 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'. | ||
|
||
It the "origin" header doesn't match the pathname of the request, Astro will return a 403 status code and won't render the page. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import type { MiddlewareHandler } from '../../@types/astro.js'; | ||
import { defineMiddleware } from '../middleware/index.js'; | ||
|
||
/** | ||
* Content types that can be passed when sending a request via a form | ||
* | ||
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype | ||
* @private | ||
*/ | ||
const FORM_CONTENT_TYPES = [ | ||
'application/x-www-form-urlencoded', | ||
'multipart/form-data', | ||
'text/plain', | ||
]; | ||
|
||
/** | ||
* Returns a middleware function in charge to check the `origin` header. | ||
* | ||
* @private | ||
*/ | ||
export function createOriginCheckMiddleware(): MiddlewareHandler { | ||
return defineMiddleware((context, next) => { | ||
const { request, url } = context; | ||
const contentType = request.headers.get('content-type'); | ||
if (contentType) { | ||
if (FORM_CONTENT_TYPES.includes(contentType.toLowerCase())) { | ||
const forbidden = | ||
(request.method === 'POST' || | ||
request.method === 'PUT' || | ||
request.method === 'PATCH' || | ||
request.method === 'DELETE') && | ||
request.headers.get('origin') !== url.origin; | ||
if (forbidden) { | ||
return new Response(`Cross-site ${request.method} form submissions are forbidden`, { | ||
status: 403, | ||
}); | ||
} | ||
} | ||
} | ||
return next(); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.