From bb0993d25b13080676402afdf9103250d1623ddf Mon Sep 17 00:00:00 2001 From: Karsten Hassel Date: Fri, 3 Apr 2026 23:07:36 +0200 Subject: [PATCH 1/2] add option to disable or restrict cors endpoint --- js/defaults.js | 2 ++ js/server_functions.js | 5 +++++ tests/unit/functions/server_functions_spec.js | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/js/defaults.js b/js/defaults.js index bd590894cb..81b5264e61 100644 --- a/js/defaults.js +++ b/js/defaults.js @@ -11,6 +11,8 @@ const defaults = { basePath: "/", electronOptions: {}, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], + cors: "disabled", // or "allowAll" or "allowWhitelist" + corsDomainWhitelist: [], // example: ["api.mapbox.com"] language: "en", logLevel: ["INFO", "LOG", "WARN", "ERROR"], diff --git a/js/server_functions.js b/js/server_functions.js index 1d0f5e1ff5..82d69c666e 100644 --- a/js/server_functions.js +++ b/js/server_functions.js @@ -25,6 +25,7 @@ async function isPrivateTarget (url) { const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); if (hostname.toLowerCase() === "localhost") return true; + if (config.cors === "allowWhitelist" && !config.corsDomainWhitelist.includes(hostname.toLowerCase())) return true; try { const results = await dns.lookup(hostname, { all: true }); @@ -68,6 +69,10 @@ function replaceSecretPlaceholder (input) { * @returns {Promise} A promise that resolves when the response is sent */ async function cors (req, res) { + if (config.cors === "disabled") { + Log.error("CORS is disabled, you need to enable it in `config.js` by setting `cors` to `allowAll` or `allowWhitelist`"); + return false; + } try { const urlRegEx = "url=(.+?)$"; let url; diff --git a/tests/unit/functions/server_functions_spec.js b/tests/unit/functions/server_functions_spec.js index 2b855e42a0..979a6a633d 100644 --- a/tests/unit/functions/server_functions_spec.js +++ b/tests/unit/functions/server_functions_spec.js @@ -35,6 +35,7 @@ describe("server_functions tests", () => { let fetchMock; beforeEach(() => { + global.config = { cors: "allowAll" }; fetchResponseHeadersGet = vi.fn(() => {}); fetchResponseArrayBuffer = vi.fn(() => {}); fetchResponse = { @@ -263,4 +264,22 @@ describe("server_functions tests", () => { expect(response.json).toHaveBeenCalledWith({ error: "Forbidden: private or reserved addresses are not allowed" }); }); }); + + describe("The isPrivateTarget method with allowWhitelist", () => { + beforeEach(() => { + mockLookup.mockReset(); + }); + + it("Block public unicast IPs if not whitelistet", async () => { + global.config = { cors: "allowWhitelist", corsDomainWhitelist: [] }; + mockLookup.mockResolvedValue([{ address: "93.184.216.34", family: 4 }]); + expect(await isPrivateTarget("http://example.com/api")).toBe(true); + }); + + it("Allow public unicast IPs if whitelistet", async () => { + global.config = { cors: "allowWhitelist", corsDomainWhitelist: ["example.com"] }; + mockLookup.mockResolvedValue([{ address: "93.184.216.34", family: 4 }]); + expect(await isPrivateTarget("http://example.com/api")).toBe(false); + }); + }); }); From 10534156ce71df16e30ba1da1d48d5e85714eb83 Mon Sep 17 00:00:00 2001 From: Karsten Hassel Date: Sat, 4 Apr 2026 11:20:31 +0200 Subject: [PATCH 2/2] use global.config, fix return Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> --- js/server_functions.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/server_functions.js b/js/server_functions.js index 82d69c666e..b1b0b19c83 100644 --- a/js/server_functions.js +++ b/js/server_functions.js @@ -25,7 +25,7 @@ async function isPrivateTarget (url) { const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); if (hostname.toLowerCase() === "localhost") return true; - if (config.cors === "allowWhitelist" && !config.corsDomainWhitelist.includes(hostname.toLowerCase())) return true; + if (global.config.cors === "allowWhitelist" && !global.config.corsDomainWhitelist.includes(hostname.toLowerCase())) return true; try { const results = await dns.lookup(hostname, { all: true }); @@ -69,9 +69,9 @@ function replaceSecretPlaceholder (input) { * @returns {Promise} A promise that resolves when the response is sent */ async function cors (req, res) { - if (config.cors === "disabled") { + if (global.config.cors === "disabled") { Log.error("CORS is disabled, you need to enable it in `config.js` by setting `cors` to `allowAll` or `allowWhitelist`"); - return false; + return res.status(403).json({ error: "CORS proxy is disabled" }); } try { const urlRegEx = "url=(.+?)$";