Skip to content

Commit caeb5c3

Browse files
refactor: replace express-ipfilter with lightweight custom middleware
This fixes security issue CVE-2023-42282, which is not very likely to be exploitable in MagicMirror² setups, but still should be fixed. The `express-ipfilter` package depends on the unmaintained `ip` package, which has known security vulnerabilities. Since no fix is available, this commit replaces both dependencies with a custom middleware using the better maintained `ipaddr.js` library. Changes: - Add new `js/ip_access_control.js` with lightweight middleware - Remove `express-ipfilter` dependency, add `ipaddr.js` - Update `js/server.js` to use new middleware
1 parent 6ac7608 commit caeb5c3

File tree

5 files changed

+89
-84
lines changed

5 files changed

+89
-84
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ planned for 2026-01-01
2121

2222
- feat: add ESlint rule `no-sparse-arrays` for config check to fix #3910 (#3911)
2323
- fixed eslint warnings shown in #3911 and updated npm publish docs (#3913)
24+
- [core] refactor: replace `express-ipfilter` with lightweight custom middleware (#3917) - This fixes security issue [CVE-2023-42282](https://github.com/advisories/GHSA-78xj-cgh5-2h22), which is not very likely to be exploitable in MagicMirror² setups, but still should be fixed.
2425

2526
### Updated
2627

js/ip_access_control.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const ipaddr = require("ipaddr.js");
2+
const Log = require("logger");
3+
4+
/**
5+
* Checks if a client IP matches any entry in the whitelist
6+
* @param {string} clientIp - The IP address to check
7+
* @param {string[]} whitelist - Array of IP addresses or CIDR ranges
8+
* @returns {boolean} True if IP is allowed
9+
*/
10+
function isAllowed (clientIp, whitelist) {
11+
try {
12+
const addr = ipaddr.process(clientIp);
13+
14+
return whitelist.some((entry) => {
15+
try {
16+
// CIDR notation
17+
if (entry.includes("/")) {
18+
const [rangeAddr, prefixLen] = ipaddr.parseCIDR(entry);
19+
return addr.match(rangeAddr, prefixLen);
20+
}
21+
22+
// Single IP address - let ipaddr.process normalize both
23+
const allowedAddr = ipaddr.process(entry);
24+
return addr.toString() === allowedAddr.toString();
25+
} catch (err) {
26+
Log.warn(`Invalid whitelist entry: ${entry}`);
27+
return false;
28+
}
29+
});
30+
} catch (err) {
31+
Log.warn(`Failed to parse client IP: ${clientIp}`);
32+
return false;
33+
}
34+
}
35+
36+
/**
37+
* Creates an Express middleware for IP whitelisting
38+
* @param {string[]} whitelist - Array of allowed IP addresses or CIDR ranges
39+
* @returns {import("express").RequestHandler} Express middleware function
40+
*/
41+
function ipAccessControl (whitelist) {
42+
// Empty whitelist means allow all
43+
if (!Array.isArray(whitelist) || whitelist.length === 0) {
44+
return function (req, res, next) {
45+
res.header("Access-Control-Allow-Origin", "*");
46+
next();
47+
};
48+
}
49+
50+
return function (req, res, next) {
51+
const clientIp = req.ip || req.socket.remoteAddress;
52+
53+
if (isAllowed(clientIp, whitelist)) {
54+
res.header("Access-Control-Allow-Origin", "*");
55+
next();
56+
} else {
57+
Log.log(`IP ${clientIp} is not allowed to access the mirror`);
58+
res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
59+
}
60+
};
61+
}
62+
63+
module.exports = { ipAccessControl };

js/server.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ const http = require("node:http");
33
const https = require("node:https");
44
const path = require("node:path");
55
const express = require("express");
6-
const ipfilter = require("express-ipfilter").IpFilter;
76
const helmet = require("helmet");
87
const socketio = require("socket.io");
98
const Log = require("logger");
109
const { cors, getConfig, getHtml, getVersion, getStartup, getEnvVars } = require("#server_functions");
1110

11+
const { ipAccessControl } = require(`${__dirname}/ip_access_control`);
12+
1213
const vendor = require(`${__dirname}/vendor`);
1314

1415
/**
@@ -84,17 +85,7 @@ function Server (config) {
8485
Log.warn("You're using a full whitelist configuration to allow for all IPs");
8586
}
8687

87-
app.use(function (req, res, next) {
88-
ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
89-
if (err === undefined) {
90-
res.header("Access-Control-Allow-Origin", "*");
91-
return next();
92-
}
93-
Log.log(err.message);
94-
res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
95-
});
96-
});
97-
88+
app.use(ipAccessControl(config.ipWhitelist));
9889
app.use(helmet(config.httpHeaders));
9990
app.use("/js", express.static(__dirname));
10091

package-lock.json

Lines changed: 21 additions & 71 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)