fix(server): honor proxy trust for forwarded host (#12832)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
4ef6155aae
commit
7dfc769f3b
|
|
@ -92,6 +92,7 @@ describe("boardMutationGuard", () => {
|
|||
|
||||
it("allows HTTPS branch-runtime mutations when forwarded MagicDNS host and non-standard port match", async () => {
|
||||
const app = createApp("board");
|
||||
app.set("trust proxy", "loopback");
|
||||
const res = await request(app)
|
||||
.post("/mutate")
|
||||
.set("Host", "127.0.0.1")
|
||||
|
|
@ -102,6 +103,17 @@ describe("boardMutationGuard", () => {
|
|||
expect([200, 204]).toContain(res.status);
|
||||
});
|
||||
|
||||
it("ignores x-forwarded-host from an untrusted direct client", async () => {
|
||||
const app = createApp("board");
|
||||
const res = await request(app)
|
||||
.post("/mutate")
|
||||
.set("Host", "board.example.test")
|
||||
.set("X-Forwarded-Host", "attacker.example.test")
|
||||
.set("Origin", "https://attacker.example.test")
|
||||
.send({ ok: true });
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("blocks board mutations when x-forwarded-host does not match origin", async () => {
|
||||
const middleware = boardMutationGuard();
|
||||
const req = {
|
||||
|
|
|
|||
|
|
@ -16,10 +16,33 @@ function parseOrigin(value: string | undefined) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the host used for same-origin checks without letting a direct client
|
||||
* promote its own X-Forwarded-Host value into the trusted-origin set. Express
|
||||
* compiles the operator's TRUST_PROXY setting into `trust proxy fn`; only a
|
||||
* trusted immediate peer may supply the forwarded host.
|
||||
*/
|
||||
function requestHost(req: Request): string | undefined {
|
||||
const host = req.header("host")?.trim();
|
||||
const remoteAddress = req.socket?.remoteAddress;
|
||||
const trustProxy = req.app?.get("trust proxy fn") as
|
||||
| ((address: string, hop: number) => boolean)
|
||||
| undefined;
|
||||
|
||||
if (
|
||||
remoteAddress
|
||||
&& typeof trustProxy === "function"
|
||||
&& trustProxy(remoteAddress, 0)
|
||||
) {
|
||||
return req.header("x-forwarded-host")?.split(",")[0]?.trim() || host;
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
function trustedOriginsForRequest(req: Request) {
|
||||
const origins = new Set(DEFAULT_DEV_ORIGINS.map((value) => value.toLowerCase()));
|
||||
const forwardedHost = req.header("x-forwarded-host")?.split(",")[0]?.trim();
|
||||
const host = forwardedHost || req.header("host")?.trim();
|
||||
const host = requestHost(req);
|
||||
if (host) {
|
||||
origins.add(`http://${host}`.toLowerCase());
|
||||
origins.add(`https://${host}`.toLowerCase());
|
||||
|
|
|
|||
Loading…
Reference in New Issue