Skip to content

Commit

Permalink
fix: checkOrigin headers check (#12632)
Browse files Browse the repository at this point in the history
* Merge commit from fork

* fix: enforce check origin logic

* address feedback

* --amend
  • Loading branch information
ematipico authored Dec 5, 2024
1 parent 839979d commit e7d14c3
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/swift-pandas-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 1,5 @@
---
'astro': patch
---

Fixes an issue where the `checkOrigin` feature wasn't correctly checking the `content-type` header
51 changes: 36 additions & 15 deletions packages/astro/src/core/app/middlewares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 21,43 @@ const FORM_CONTENT_TYPES = [
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,
});
}
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;
}
16 changes: 16 additions & 0 deletions packages/astro/test/csrf-protection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down

0 comments on commit e7d14c3

Please sign in to comment.