In-app reader
MagicMirror applies ipWhitelist only as Express middleware, but the Socket.IO server is attached directly to the HTTP server without equivalent IP allowlist, origin, or namespace authentication checks. In a documented common deployment where MagicMirror listens on a non-loopback interface but expects ipWhitelist to restrict access, an untrusted network client can connect directly to module Socket.IO namespaces and send arbitrary module-helper notifications. This allows unauthenticated server-side requests through default modules and can reach command execution in the default updatenotification helper when a third-party module update is pending and the attacker supplies the update command through the trusted socket configuration path.
The affected product is the npm package/application magicmirror at version 2.36.0, tested at commit fb41d24ef522e91e802e2a623ff6afbddeb3c9d8 from https://github.com/MagicMirrorOrg/MagicMirror.git.
Default committed settings bind to loopback and allow loopback only (js/defaults.js:8-13), so the remote network impact requires a documented common configuration where the server is reachable beyond loopback. The shipped sample explicitly documents non-loopback binding and IP allowlist behavior: config/config.js.sample:11-20 says address may be another interface or 0.0.0.0/::, and ipWhitelist controls allowed clients.
The trust-boundary issue is that Socket.IO is configured before and outside the Express middleware chain:
js/server.js:42-50 creates Socket.IO directly on the HTTP(S) server with cors.origin: /.*$/.
js/server.js:89-90 applies ipAccessControl(config.ipWhitelist) only with app.use(...), which protects Express routes and static files but not Socket.IO handshakes or namespaces.
A search of runtime files found no allowRequest, io.use(...), handshake IP check, or namespace authentication for Socket.IO; the only relevant matches were js/server.js:44 and js/server.js:90.
js/node_helper.js:88-103 registers every module namespace and dispatches every socket event and payload directly to socketNotificationReceived(...).
Once a client can reach the Socket.IO server, the following default-module server-side actions are reachable without an equivalent IP whitelist or module-authentication check:
defaultmodules/newsfeed/node_helper.js:12-17 accepts CHECK_ARTICLE_URL and calls checkArticleUrl(payload.url).
defaultmodules/newsfeed/node_helper.js:25-38 performs fetch(url, { method: "HEAD" }) on the supplied URL and sends the result back.
defaultmodules/calendar/node_helper.js:13-24 accepts ADD_CALENDAR/FETCH_CALENDAR socket messages.
defaultmodules/calendar/node_helper.js:40-58 accepts an arbitrary syntactically valid calendar URL and creates a fetcher.
defaultmodules/calendar/calendarfetcher.js:35-44 passes the URL into HTTPFetcher, whose fetch sink is js/http_fetcher.js:286-294.
defaultmodules/updatenotification/node_helper.js:46-68 accepts CONFIG, MODULES, and SCAN_UPDATES notifications and trusts the socket-provided config/module list.
defaultmodules/updatenotification/update_helper.js:43-47 stores update commands from config.
defaultmodules/updatenotification/update_helper.js:96-116 executes the selected update command with child_process.exec in the module directory.
defaultmodules/updatenotification/update_helper.js:221-227 looks up the command from config.updates by module name.
False-positive screening performed:
Express HTTP routes are protected by ipAccessControl(config.ipWhitelist) at js/server.js:89-90; this does not protect Socket.IO because Socket.IO is attached to the raw HTTP server and no Socket.IO middleware was found.
The explicit /cors HTTP endpoint has separate SSRF mitigations (js/server_functions.js:47-117) and is disabled by default (js/defaults.js:14); the confirmed request primitive here uses module-helper socket paths, not /cors.
The command-execution variant is not an unconditional default RCE: updatenotification only executes an update command for a non-core git-managed module that is considered behind. However, the trusted command source is attacker-controlled through the unauthenticated socket CONFIG message once this boundary is crossed.
Default loopback-only binding lowers default remote exposure, but the sample configuration documents exactly the deployment model where users rely on ipWhitelist for network restrictions.
Affected-version evidence: only magicmirror@2.36.0 at commit fb41d24ef522e91e802e2a623ff6afbddeb3c9d8 was tested. The affected range is unknown from this audit; earlier versions were not tested. No patched version or fix commit was identified locally.
The following safe local PoCs were run from a clean checkout of MagicMirror at commit fb41d24ef522e91e802e2a623ff6afbddeb3c9d8. Because node_modules were not installed in this audit environment and package.json:52 has a destructive postinstall (git clean -df fonts vendor modules/default), the commands use small Node harnesses with stubs for missing dependencies while exercising the vulnerable repository code paths directly. They do not contact external hosts and write only disposable /tmp marker files.
grep -RIn --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=.claude --exclude-dir=reports -E "allowRequest|io\.use\(|handshake|ipAccessControl\(|cors: \{|origin: /\.\*\$/" js defaultmodules serveronly config tests
Discussion
Sign in to join the discussion.
Keep reading
Optional: create a free account to save items, track programs, and sync across web + app. Reading stays free.