GitHub Advisoryadvisory · npm

GHSA-w26r-fwg8-rcp3 MagicMirror Socket.IO module namespaces bypass configured IP whitelist and allow unauthenticated server-side actions

LOW Published 2026-08-18 Modified 2026-08-18 Official page ↗
Aliases
CVE-2026-63641
Ecosystem
npm
Packages
magicmirror
Severity
Low
CVSS vector
CVSS:4.0/AV:A/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:L/SC:L/SI:L/SA:L
Weaknesses
CWE-284
Reviewed
2026-08-18

Affected packages

• npm magicmirror: < 2.37.0 → fixed in 2.37.0

Description

Summary 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. Details 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 `[email protected]` 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. PoC 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. 1. Confirm that the runtime lacks Socket.IO IP allowlist controls: ```bash 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 ``` Observed output: ```text js/server.js:44: cors: { js/server.js:90: app.use(ipAccessControl(config.ipWhitelist)); ``` This confirms the IP allowlist appears only as Express middleware and no Socket.IO handshake/namespace allowlist was present in the reviewed runtime files. 2. Confirm a default module helper will perform a server-side request to an attacker-supplied loopback URL when driven through its socket notification handler: ```bash node -e 'const Module=require("module"); const orig=Module._load; Module._load=(r,p,m)=>{ if(r==="logger") return {log(){},error(){},warn(){},info(){},debug(){}}; if(r==="node_helper") return {create:(o)=>function(){Object.assign(this,o);this.sendSocketNotification=(n,p)=>console.log("SOCKET",n,JSON.stringify(p));}}; if(r==="./newsfeedfetcher") return function(){}; return orig(r,p,m); }; const calls=[]; global.fetch=async(url,opts)=>{calls.push({url,opts}); return {headers:{get:(h)=>h==="x-frame-options"?"deny":null}};}; const Helper=require("./defaultmodules/newsfeed/node_helper"); const h=new Helper(); h.start(); h.socketNotificationReceived("CHECK_ARTICLE_URL",{url:"http://127.0.0.1:65535/internal"}); setTimeout(()=>console.log("fetchCalls=",JSON.stringify(calls)),10);' ``` Observed output: ```text SOCKET ARTICLE_URL_STATUS {"url":"http://127.0.0.1:65535/internal","canFrame":false} fetchCalls= [{"url":"http://127.0.0.1:65535/internal","opts":{"method":"HEAD"}}] ``` Expected vulnerable output: the harness records a server-side `HEAD` request to `http://127.0.0.1:65535/internal` even though `/cors` SSRF protections are not involved. 3. Confirm the conditional command-execution sink is reachable from the trusted socket-driven update path using a harmless `/tmp` marker command: ```bash rm -f /tmp/mm-rce-marker mkdir -p /tmp/mm-audit/modules/evil /tmp/mm-audit/defaultmodules node -e 'const fs=require("node:fs"); const Module=require("module"); const orig=Module._load; Module._load=(r,p,m)=>{ if(r==="logger") return {log(){},error(){},warn(){},info(){},debug(){}}; if(r==="node_helper") return {create:(o)=>function(){Object.assign(this,o);this.sendSocketNotification=()=>{};}}; return orig(r,p,m); }; global.root_path="/tmp/mm-audit"; global.defaultModulesDir="defaultmodules"; fs.writeFileSync("/tmp/mm-audit/defaultmodules/defaultmodules.js","module.exports=[]"); const Helper=require("./defaultmodules/updateno…

Related

Alias · CVE (1)

  • CVE-2026-63641 MagicMirror² is an open source modular smart mirror platform. vulnerability

Weakness · CWE (1)

  • CWE-284 Improper Access Control weakness

References