low

CVE-2026-63641

npm · magicmirror

Summary

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

Severity
low
EPSS
0.5% (p40)
CWE
CWE-284
Also known as
GHSA-w26r-fwg8-rcp3
Published
2026-08-18
Updated
2026-08-18

Advisory details

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:

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:

False-positive screening performed:

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.

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:
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:

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.

  1. Confirm a default module helper will perform a server-side request to an attacker-supplied loopback URL when driven through its socket notification handler:
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:

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.

  1. Confirm the conditional command-execution sink is reachable from the trusted socket-driven update path using a harmless /tmp marker command:
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("./defaultmodu

References

Related advisories

Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.

Check my repo

Summarize with AI

ChatGPTClaudePerplexity

Sources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.