Skip to content

Commit e7d14c3

Browse files
authoredDec 5, 2024··
fix: checkOrigin headers check (#12632)
* Merge commit from fork * fix: enforce check origin logic * address feedback * --amend
1 parent 839979d commit e7d14c3

File tree

3 files changed

+57
-15
lines changed

3 files changed

+57
-15
lines changed
 

‎.changeset/swift-pandas-serve.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes an issue where the `checkOrigin` feature wasn't correctly checking the `content-type` header

‎packages/astro/src/core/app/middlewares.ts

+36-15
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,43 @@ const FORM_CONTENT_TYPES = [
2121
export function createOriginCheckMiddleware(): MiddlewareHandler {
2222
return defineMiddleware((context, next) => {
2323
const { request, url } = context;
24-
const contentType = request.headers.get('content-type');
25-
if (contentType) {
26-
if (FORM_CONTENT_TYPES.includes(contentType.toLowerCase())) {
27-
const forbidden =
28-
(request.method === 'POST' ||
29-
request.method === 'PUT' ||
30-
request.method === 'PATCH' ||
31-
request.method === 'DELETE') &&
32-
request.headers.get('origin') !== url.origin;
33-
if (forbidden) {
34-
return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
35-
status: 403,
36-
});
37-
}
24+
if (request.method === "GET") {
25+
return next();
26+
}
27+
const sameOrigin =
28+
(request.method === 'POST' ||
29+
request.method === 'PUT' ||
30+
request.method === 'PATCH' ||
31+
request.method === 'DELETE') &&
32+
request.headers.get('origin') === url.origin;
33+
34+
const hasContentType = request.headers.has('content-type')
35+
if (hasContentType) {
36+
const formLikeHeader = hasFormLikeHeader(request.headers.get('content-type'));
37+
if (formLikeHeader && !sameOrigin) {
38+
return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
39+
status: 403,
40+
});
41+
}
42+
} else {
43+
if (!sameOrigin) {
44+
return new Response(`Cross-site ${request.method} form submissions are forbidden`, {
45+
status: 403,
46+
});
3847
}
3948
}
40-
return next();
49+
50+
return next()
4151
});
4252
}
53+
54+
function hasFormLikeHeader(contentType: string | null): boolean {
55+
if (contentType) {
56+
for (const FORM_CONTENT_TYPE of FORM_CONTENT_TYPES) {
57+
if (contentType.toLowerCase().includes(FORM_CONTENT_TYPE)) {
58+
return true;
59+
}
60+
}
61+
}
62+
return false;
63+
}

‎packages/astro/test/csrf-protection.test.js

+16
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,22 @@ describe('CSRF origin check', () => {
4646
});
4747
response = await app.render(request);
4848
assert.equal(response.status, 403);
49+
50+
request = new Request('http://example.com/api/', {
51+
headers: { origin: 'http://loreum.com', 'content-type': 'application/x-www-form-urlencoded; some-other-value' },
52+
method: 'POST',
53+
});
54+
response = await app.render(request);
55+
assert.equal(response.status, 403);
56+
57+
request = new Request('http://example.com/api/', {
58+
headers: { origin: 'http://loreum.com', },
59+
method: 'POST',
60+
credentials: 'include',
61+
body: new Blob(["a=b"],{})
62+
});
63+
response = await app.render(request);
64+
assert.equal(response.status, 403);
4965
});
5066

5167
it("return 403 when the origin doesn't match and calling a PUT", async () => {

0 commit comments

Comments
 (0)
Please sign in to comment.