Skip to content

Commit 126066d

Browse files
fix(serve): reject opaque Origin null in CSRF guard (#113)
* fix(serve): reject opaque Origin null in CSRF guard Align the Origin fallback with the documented threat model: browsers that send Origin (including the literal "null" opaque value) are blocked on state-changing routes; curl-style clients without Origin still pass. * chore: add patch changeset for serve CSRF fix
1 parent ec31949 commit 126066d

3 files changed

Lines changed: 20 additions & 9 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stainless-code/codemap": patch
3+
---
4+
5+
Reject opaque `Origin: null` in `codemap serve` CSRF checks.

src/application/http-server.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -579,14 +579,19 @@ describe("http-server — CSRF + DNS-rebinding guard", () => {
579579
expect(body.error).toContain("Origin");
580580
});
581581

582-
it("allows Origin: null (file:// pages, sandboxed iframes — non-attack vector)", async () => {
582+
it("rejects Origin: null (opaque browser contexts)", async () => {
583583
serverHandle = await startServer();
584-
const r = await fetch(`http://127.0.0.1:${serverHandle.port}/tool/query`, {
585-
method: "POST",
586-
headers: { "Content-Type": "application/json", Origin: "null" },
587-
body: JSON.stringify({ sql: "SELECT 1" }),
588-
});
589-
expect(r.status).toBe(200);
584+
const r = await fetch(
585+
`http://127.0.0.1:${serverHandle.port}/tool/save_baseline`,
586+
{
587+
method: "POST",
588+
headers: { "Content-Type": "application/json", Origin: "null" },
589+
body: JSON.stringify({ name: "csrf-test", sql: "SELECT 1" }),
590+
},
591+
);
592+
expect(r.status).toBe(403);
593+
const body = (await r.json()) as { error: string };
594+
expect(body.error).toContain("Origin: null");
590595
});
591596

592597
it("rejects POST with mismatched Host header (DNS rebinding)", async () => {

src/application/http-server.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,8 @@ function validate<T extends ZodRawShape>(
571571
* that interface.
572572
* 3. **`Origin`** — fallback for older browsers that don't send
573573
* `Sec-Fetch-Site`. Browsers send `Origin` on every non-GET request
574-
* (and most GETs); non-browser clients don't. Reject if present.
574+
* (and most GETs); non-browser clients don't. Reject if present,
575+
* including the opaque value `null` (file://, sandboxed contexts).
575576
*
576577
* Returns a reason string (becomes the 403 body) or `undefined` to allow.
577578
*/
@@ -601,7 +602,7 @@ function csrfCheck(
601602
}
602603

603604
const origin = req.headers.origin;
604-
if (origin !== undefined && origin !== "" && origin !== "null") {
605+
if (origin !== undefined && origin !== "") {
605606
return `cross-origin request rejected (Origin: ${origin}). codemap serve does not accept browser-driven cross-origin requests.`;
606607
}
607608

0 commit comments

Comments
 (0)