Skip to content

Commit 4a056b8

Browse files
committed
feat: add regex support to trusted-origins
- Add isTrustedOrigin() helper that matches exact strings, '*', or regex literals in the form /pattern/flags (e.g. /^.*\.example\.com$/i) - Use isTrustedOrigin in authenticateOrigin() for --trusted-origins - Add unit tests for isTrustedOrigin and for regex/wildcard in origin auth Made-with: Cursor
1 parent 1af5ce5 commit 4a056b8

2 files changed

Lines changed: 77 additions & 2 deletions

File tree

src/node/http.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,33 @@ 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 regex literals in the form
357+
* `/pattern/flags` (e.g. `/^.*\.example\.com$/i`).
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+
// Regex literal: /pattern/ or /pattern/flags
365+
if (trusted.startsWith("/")) {
366+
const closingSlash = trusted.lastIndexOf("/")
367+
if (closingSlash > 0) {
368+
const pattern = trusted.slice(1, closingSlash)
369+
const flags = trusted.slice(closingSlash + 1)
370+
try {
371+
return new RegExp(pattern, flags).test(origin)
372+
} catch {
373+
return false
374+
}
375+
}
376+
}
377+
return false
378+
})
379+
}
380+
354381
/**
355382
* Authenticate the request origin against the host. Throw if invalid.
356383
*/
@@ -370,7 +397,7 @@ export function authenticateOrigin(req: express.Request): void {
370397
}
371398

372399
const trustedOrigins = req.args["trusted-origins"] || []
373-
if (trustedOrigins.includes(origin) || trustedOrigins.includes("*")) {
400+
if (isTrustedOrigin(origin, trustedOrigins)) {
374401
return
375402
}
376403

test/unit/node/http.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,36 @@ 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 regex patterns", () => {
35+
expect(http.isTrustedOrigin("sub.example.com", ["/\\.example\\.com$/"])).toBe(true)
36+
expect(http.isTrustedOrigin("evil.com", ["/\\.example\\.com$/"])).toBe(false)
37+
})
38+
39+
it("should support regex flags", () => {
40+
expect(http.isTrustedOrigin("SUB.EXAMPLE.COM", ["/\\.example\\.com$/i"])).toBe(true)
41+
})
42+
43+
it("should return false for invalid regex patterns", () => {
44+
expect(http.isTrustedOrigin("example.com", ["/[invalid/"])).toBe(false)
45+
})
46+
47+
it("should return false for an empty trusted origins list", () => {
48+
expect(http.isTrustedOrigin("example.com", [])).toBe(false)
49+
})
50+
})
51+
2252
describe("origin", () => {
2353
;[
2454
{
@@ -54,6 +84,22 @@ describe("http", () => {
5484
host: "localhost:8080",
5585
expected: "malformed", // Parsing fails completely.
5686
},
87+
{
88+
origin: "http://sub.example.com",
89+
host: "other.com",
90+
trustedOrigins: ["/\\.example\\.com$/"],
91+
},
92+
{
93+
origin: "http://evil.com",
94+
host: "other.com",
95+
trustedOrigins: ["/\\.example\\.com$/"],
96+
expected: "does not match",
97+
},
98+
{
99+
origin: "http://sub.example.com",
100+
host: "other.com",
101+
trustedOrigins: ["*"],
102+
},
57103
].forEach((test) => {
58104
;[
59105
["host", test.host],
@@ -70,7 +116,9 @@ describe("http", () => {
70116
origin: test.origin,
71117
[key]: value,
72118
},
73-
args: {},
119+
args: {
120+
"trusted-origins": (test as { trustedOrigins?: string[] }).trustedOrigins,
121+
},
74122
})
75123
if (typeof test.expected === "string") {
76124
expect(() => http.authenticateOrigin(req)).toThrow(test.expected)

0 commit comments

Comments
 (0)