summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/astro/src/core/app/middlewares.ts51
-rw-r--r--packages/astro/test/csrf-protection.test.js16
2 files changed, 52 insertions, 15 deletions
diff --git a/packages/astro/src/core/app/middlewares.ts b/packages/astro/src/core/app/middlewares.ts
index 1c7d6cde0..6a804627d 100644
--- a/packages/astro/src/core/app/middlewares.ts
+++ b/packages/astro/src/core/app/middlewares.ts
@@ -25,22 +25,43 @@ export function createOriginCheckMiddleware(): MiddlewareHandler {
if (isPrerendered) {
return next();
}
- 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,
- });
- }
+ if (request.method === "GET") {
+ return next();
+ }
+ const sameOrigin =
+ (request.method === 'POST' ||
+ request.method === 'PUT' ||
+ request.method === 'PATCH' ||
+ request.method === 'DELETE') &&
+ request.headers.get('origin') === url.origin;
+
+ const hasContentType = request.headers.has('content-type')
+ if (hasContentType) {
+ const formLikeHeader = hasFormLikeHeader(request.headers.get('content-type'));
+ if (formLikeHeader && !sameOrigin) {
+ return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
+ status: 403,
+ });
+ }
+ } else {
+ if (!sameOrigin) {
+ return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
+ status: 403,
+ });
}
}
- return next();
+
+ return next()
});
}
+
+function hasFormLikeHeader(contentType: string | null): boolean {
+ if (contentType) {
+ for (const FORM_CONTENT_TYPE of FORM_CONTENT_TYPES) {
+ if (contentType.toLowerCase().includes(FORM_CONTENT_TYPE)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
diff --git a/packages/astro/test/csrf-protection.test.js b/packages/astro/test/csrf-protection.test.js
index 25aa9d059..f8067000d 100644
--- a/packages/astro/test/csrf-protection.test.js
+++ b/packages/astro/test/csrf-protection.test.js
@@ -46,6 +46,22 @@ describe('CSRF origin check', () => {
});
response = await app.render(request);
assert.equal(response.status, 403);
+
+ request = new Request('http://example.com/api/', {
+ headers: { origin: 'http://loreum.com', 'content-type': 'application/x-www-form-urlencoded; some-other-value' },
+ method: 'POST',
+ });
+ response = await app.render(request);
+ assert.equal(response.status, 403);
+
+ request = new Request('http://example.com/api/', {
+ headers: { origin: 'http://loreum.com', },
+ method: 'POST',
+ credentials: 'include',
+ body: new Blob(["a=b"],{})
+ });
+ response = await app.render(request);
+ assert.equal(response.status, 403);
});
it("return 403 when the origin doesn't match and calling a PUT", async () => {