|
1 | 1 | // Import mocked functions |
2 | 2 | import { get as edgeConfigGet } from "@vercel/edge-config"; |
3 | | -import { NextRequest } from "next/server"; |
| 3 | +import { NextRequest, NextResponse } from "next/server"; |
4 | 4 | import type { Mock } from "vitest"; |
5 | 5 | import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; |
6 | 6 |
|
@@ -405,6 +405,56 @@ describe("Middleware Integration Tests", () => { |
405 | 405 | }); |
406 | 406 | }); |
407 | 407 |
|
| 408 | + describe("Header sanitization", () => { |
| 409 | + it("sanitizes non-ASCII request header values to prevent Vercel Runtime Malformed Response Header", async () => { |
| 410 | + const spy = vi.spyOn(NextResponse, "next"); |
| 411 | + |
| 412 | + const req = createTestRequest({ |
| 413 | + url: `${WEBAPP_URL}/team/test`, |
| 414 | + // Next.js will translate request overrides into x-middleware-request-* headers internally |
| 415 | + headers: { |
| 416 | + "cf-region": "São Paulo", // contains non-ASCII "ã" |
| 417 | + }, |
| 418 | + }); |
| 419 | + |
| 420 | + const res = await callMiddleware(req); |
| 421 | + expectStatus(res, 200); |
| 422 | + |
| 423 | + // Assert that middleware forwarded sanitized ASCII-only value |
| 424 | + const initArg = (spy as unknown as Mock).mock.calls.at(-1)?.[0] as { |
| 425 | + request?: { headers?: Headers }; |
| 426 | + }; |
| 427 | + expect(initArg?.request?.headers).toBeDefined(); |
| 428 | + |
| 429 | + const forwarded = initArg?.request?.headers as Headers; |
| 430 | + expect(forwarded.get("cf-region")).toBe("Sao Paulo"); |
| 431 | + |
| 432 | + spy.mockRestore(); |
| 433 | + }); |
| 434 | + |
| 435 | + it("strips non-ASCII bytes in mojibake values (e.g., 'São Paulo' -> 'So Paulo')", async () => { |
| 436 | + const spy = vi.spyOn(NextResponse, "next"); |
| 437 | + |
| 438 | + const req = createTestRequest({ |
| 439 | + url: `${WEBAPP_URL}/team/test`, |
| 440 | + headers: { |
| 441 | + "cf-region": "São Paulo", // mojibake for "São Paulo"; includes non-ASCII bytes |
| 442 | + }, |
| 443 | + }); |
| 444 | + |
| 445 | + const res = await callMiddleware(req); |
| 446 | + expectStatus(res, 200); |
| 447 | + |
| 448 | + const initArg = (spy as unknown as Mock).mock.calls.at(-1)?.[0] as { |
| 449 | + request?: { headers?: Headers }; |
| 450 | + }; |
| 451 | + const forwarded = initArg?.request?.headers as Headers; |
| 452 | + expect(forwarded.get("cf-region")).toBe("So Paulo"); |
| 453 | + |
| 454 | + spy.mockRestore(); |
| 455 | + }); |
| 456 | + }); |
| 457 | + |
408 | 458 | describe("Multiple Features", () => { |
409 | 459 |
|
410 | 460 | it("should handle embed route with routing forms rewrite", async () => { |
|
0 commit comments