Skip to content

Commit 4d615f1

Browse files
authored
feat: add wildcard support to trusted-origins (#7697)
1 parent d544846 commit 4d615f1

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

src/node/http.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,25 @@ export function ensureOrigin(req: express.Request, _?: express.Response, next?:
351351
}
352352
}
353353

354+
/**
355+
* Return true if the origin matches any trusted origin. Entries are matched
356+
* as exact strings, the special wildcard `"*"`, or `*.example.com`-style
357+
* domain wildcards (same as --proxy-domain).
358+
*/
359+
export function isTrustedOrigin(origin: string, trustedOrigins: string[]): boolean {
360+
return trustedOrigins.some((trusted) => {
361+
if (trusted === "*" || trusted === origin) {
362+
return true
363+
}
364+
// *.example.com style: match origin if it is the domain or a subdomain
365+
if (trusted.startsWith("*.")) {
366+
const domain = trusted.slice(2).toLowerCase()
367+
return origin === domain || origin.endsWith("." + domain)
368+
}
369+
return false
370+
})
371+
}
372+
354373
/**
355374
* Authenticate the request origin against the host. Throw if invalid.
356375
*/
@@ -370,7 +389,7 @@ export function authenticateOrigin(req: express.Request): void {
370389
}
371390

372391
const trustedOrigins = req.args["trusted-origins"] || []
373-
if (trustedOrigins.includes(origin) || trustedOrigins.includes("*")) {
392+
if (isTrustedOrigin(origin, trustedOrigins)) {
374393
return
375394
}
376395

test/unit/node/http.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,30 @@ describe("http", () => {
1919
expect(http.relativeRoot("/foo/bar/")).toStrictEqual("./../..")
2020
})
2121

22+
describe("isTrustedOrigin", () => {
23+
it("should match exact origins", () => {
24+
expect(http.isTrustedOrigin("localhost:8080", ["localhost:8080"])).toBe(true)
25+
expect(http.isTrustedOrigin("example.com", ["example.com"])).toBe(true)
26+
expect(http.isTrustedOrigin("example.com", ["other.com"])).toBe(false)
27+
})
28+
29+
it("should match the wildcard *", () => {
30+
expect(http.isTrustedOrigin("anything.example.com", ["*"])).toBe(true)
31+
expect(http.isTrustedOrigin("localhost:8080", ["*"])).toBe(true)
32+
})
33+
34+
it("should match *.example.com wildcard (same style as --proxy-domain)", () => {
35+
expect(http.isTrustedOrigin("sub.example.com", ["*.example.com"])).toBe(true)
36+
expect(http.isTrustedOrigin("example.com", ["*.example.com"])).toBe(true)
37+
expect(http.isTrustedOrigin("evil.com", ["*.example.com"])).toBe(false)
38+
expect(http.isTrustedOrigin("example.com.evil.com", ["*.example.com"])).toBe(false)
39+
})
40+
41+
it("should return false for an empty trusted origins list", () => {
42+
expect(http.isTrustedOrigin("example.com", [])).toBe(false)
43+
})
44+
})
45+
2246
describe("origin", () => {
2347
;[
2448
{
@@ -54,6 +78,22 @@ describe("http", () => {
5478
host: "localhost:8080",
5579
expected: "malformed", // Parsing fails completely.
5680
},
81+
{
82+
origin: "http://sub.example.com",
83+
host: "other.com",
84+
trustedOrigins: ["*.example.com"],
85+
},
86+
{
87+
origin: "http://evil.com",
88+
host: "other.com",
89+
trustedOrigins: ["*.example.com"],
90+
expected: "does not match",
91+
},
92+
{
93+
origin: "http://sub.example.com",
94+
host: "other.com",
95+
trustedOrigins: ["*"],
96+
},
5797
].forEach((test) => {
5898
;[
5999
["host", test.host],
@@ -70,7 +110,9 @@ describe("http", () => {
70110
origin: test.origin,
71111
[key]: value,
72112
},
73-
args: {},
113+
args: {
114+
"trusted-origins": (test as { trustedOrigins?: string[] }).trustedOrigins,
115+
},
74116
})
75117
if (typeof test.expected === "string") {
76118
expect(() => http.authenticateOrigin(req)).toThrow(test.expected)

0 commit comments

Comments
 (0)