CVE-2026-63641: Magicmirror MagicMirror Socket.IO Module Namespaces Bypass Vulnerability

LOW
Advisory Identifier: GHSA-w26r-fwg8-rcp3
CWE-284
Published
Advisory Summary

CVE-2026-63641 is a LOW magicmirror socket.io module namespaces bypass vulnerability (CWE-284: Improper Access Control) affecting Magicmirror libraries in the npm ecosystem. The issue involves magicmirror socket.io module namespaces bypass configured ip whitelist and allow unauthenticated server-side actions. Remediation is available by upgrading affected packages to version 2.37.0.

CVE-2026-63641 at a Glance

CVE Identifier CVE-2026-63641
GHSA Identifier GHSA-w26r-fwg8-rcp3
Severity LOW
CVSS v3.1 Base Score N/A
CWE Classification CWE-284: Improper Access Control
Affected Product Magicmirror
Package Ecosystem npm
Published Date
Last Updated

How to Fix CVE-2026-63641

To remediate this vulnerability, upgrade all affected Magicmirror dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

magicmirror fixed: 2.37.0
npm install magicmirror@2.37.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls magicmirror

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-63641, inspect your project dependencies using npm:

npm ls magicmirror

Analyze the terminal output to locate any version of Magicmirror in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.37.0), your application is affected.

Note: The npm ls magicmirror command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-284: Improper Access Control, where the product does not restrict or incorrectly restricts access to a resource from an unauthorized actor. In Magicmirror, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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 `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: ```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/updatenotification/node_helper"); const h=new Helper(); h.gitHelper={add:async()=>{},getRepos:async()=>[{module:"evil",behind:1}],checkUpdates:()=>[{module:"evil",behind:1}]}; h.sendSocketNotification=(n,p)=>console.log("SOCKET",n,JSON.stringify(p)); (async()=>{ await h.socketNotificationReceived("CONFIG",{updates:[{evil:"printf ok > /tmp/mm-rce-marker"}],updateTimeout:5000,updateAutorestart:false,ignoreModules:[],sendUpdatesNotifications:false,updateInterval:60000,useModulesFromConfig:true}); await h.socketNotificationReceived("MODULES",["evil"]); console.log("marker=",fs.readFileSync("/tmp/mm-rce-marker","utf8")); process.exit(0); })();' rm -f /tmp/mm-rce-marker rm -rf /tmp/mm-audit ``` Observed output from the executed harness: ```text SOCKET REPO_STATUS {"module":"evil","behind":1} SOCKET UPDATE_STATUS {"name":"evil","updateCommand":"printf ok > /tmp/mm-rce-marker","inProgress":true,"error":false,"updated":true,"needRestart":true} marker= ok ``` Expected vulnerable output: `marker= ok` demonstrates the update command supplied through the trusted socket configuration path reached `child_process.exec` and wrote the harmless marker. Negative/control cases: - With the shipped committed defaults (`js/defaults.js:8-13`), the server binds `localhost` and `ipWhitelist` includes only loopback, so a remote network attacker cannot reach either HTTP or Socket.IO unless the deployment is changed to a documented non-loopback address. - The `updatenotification` command execution path requires at least one non-core module update result; if `checkUpdates()` returns no third-party module with `behind > 0`, `update_helper.parse(...)` does not execute an update command. - The explicit `/cors` route was not used for the confirmed request primitive and has separate protocol/hostname/DNS checks. Final repro re-check: the sink search, newsfeed socket request harness, and update marker harness were re-run after drafting; the observed outputs above are from this environment. Cleanup removed `/tmp/mm-rce-marker` and `/tmp/mm-audit`. ### Impact In a documented common non-loopback deployment that relies on `ipWhitelist` for access control, an unauthenticated network client can bypass the intended IP allowlist for Socket.IO module namespaces. The attacker can send arbitrary module-helper notifications and payloads as if they were a trusted browser client. Confirmed impacts include: - Confidentiality/SSRF: server-side requests to attacker-chosen URLs through default module helpers, including loopback/internal URLs. The newsfeed PoC shows a `HEAD` request to `127.0.0.1`. - Integrity/availability: arbitrary manipulation of module-helper state and periodic fetch/update behavior through trusted socket messages. - Conditional code execution: when a third-party git-managed module is considered behind by the update checker, an attacker can supply a command in the socket `CONFIG` payload and trigger `child_process.exec`; the PoC safely wrote a `/tmp` marker. CVSS 3.1 rationale for the primary trust-boundary bypass with confirmed SSRF and conditional RCE variant: AV:A because MagicMirror's security policy says it is intended for trusted local/private networks and the documented non-loopback exposure is LAN-style; AC:H because default loopback settings must be changed and the RCE variant requires a pending third-party module update, though SSRF requires fewer conditions after reachability; PR:N because no application authentication is required; UI:N because the attacker connects directly to Socket.IO; S:C because the vulnerable application can cause requests/actions against other local/internal services and can execute commands in a child process in the conditional variant; C:L/I:L/A:L for confirmed internal request and helper-state/command side effects, with conservative scoring because unconditional default RCE was not shown. ### Suggested remediation Apply the same access-control decision to Socket.IO handshakes and namespaces as to Express routes. Concretely, add a Socket.IO `allowRequest` or `io.use(...)` middleware that normalizes `socket.handshake.address`/request IPs with the same `ipAccessControl` logic, and reject clients not allowed by `config.ipWhitelist`. Avoid relying on CORS for authorization; keep origin checks as a browser hardening layer only. Also add module-helper authorization so arbitrary clients cannot send privileged server-side module notifications. For example, issue an unguessable per-session/module token to the served client and require it on helper messages, or separate read-only client events from privileged server-maintenance actions. For defense in depth: - Add SSRF protections or allowlists to calendar/newsfeed/weather helper fetch paths, not only `/cors`. - Do not accept `updatenotification` update commands from socket payloads; use the server-loaded config only, validate module names against configured modules, and avoid shell execution where possible. - Add regression tests proving a disallowed IP receives a rejected Socket.IO handshake even when Express routes are protected, and proving `/newsfeed`/`/calendar` helper messages from unauthenticated sockets cannot trigger server-side requests or update commands.

CVE-2026-63641 Affected Magicmirror Versions

1 component(s)
Fixed Versions:
2.37.0
Product / Package Ecosystem Affected Versions Fixed Version
magicmirror
npm
< 2.37.0
2.37.0

References

Frequently Asked Questions

What is CVE-2026-63641?
CVE-2026-63641 is a security vulnerability in Magicmirror involving MagicMirror Socket.IO module namespaces bypass configured IP whitelist and allow unauthenticated server-side actions under CWE-284: Improper Access Control.
Which Magicmirror versions are affected?
Affected versions include releases prior to 2.37.0 across affected packages: magicmirror.
What version fixes CVE-2026-63641?
Upgrading to 2.37.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is magicmirror affected?
Yes, magicmirror is affected in releases prior to 2.37.0. Upgrade your dependency to 2.37.0 or later.
What CWE is associated with CVE-2026-63641?
CVE-2026-63641 is classified under CWE-284: Improper Access Control (The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.).
How can I check npm dependencies for CVE-2026-63641?
Run the dependency tree command: 'npm ls magicmirror'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-w26r-fwg8-rcp3",
  "modified" : "2026-08-18T17:26:41Z",
  "published" : "2026-08-18T17:26:41Z",
  "aliases" : [ "CVE-2026-63641" ],
  "summary" : "MagicMirror Socket.IO module namespaces bypass configured IP whitelist and allow unauthenticated server-side actions",
  "details" : "### Summary\nMagicMirror 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.\n\n### Details\nThe affected product is the npm package/application `magicmirror` at version `2.36.0`, tested at commit `fb41d24ef522e91e802e2a623ff6afbddeb3c9d8` from `https://github.com/MagicMirrorOrg/MagicMirror.git`.\n\nDefault 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.\n\nThe trust-boundary issue is that Socket.IO is configured before and outside the Express middleware chain:\n\n- `js/server.js:42-50` creates Socket.IO directly on the HTTP(S) server with `cors.origin: /.*$/`.\n- `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.\n- 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`.\n- `js/node_helper.js:88-103` registers every module namespace and dispatches every socket event and payload directly to `socketNotificationReceived(...)`.\n\nOnce 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:\n\n- `defaultmodules/newsfeed/node_helper.js:12-17` accepts `CHECK_ARTICLE_URL` and calls `checkArticleUrl(payload.url)`.\n- `defaultmodules/newsfeed/node_helper.js:25-38` performs `fetch(url, { method: \"HEAD\" })` on the supplied URL and sends the result back.\n- `defaultmodules/calendar/node_helper.js:13-24` accepts `ADD_CALENDAR`/`FETCH_CALENDAR` socket messages.\n- `defaultmodules/calendar/node_helper.js:40-58` accepts an arbitrary syntactically valid calendar URL and creates a fetcher.\n- `defaultmodules/calendar/calendarfetcher.js:35-44` passes the URL into `HTTPFetcher`, whose fetch sink is `js/http_fetcher.js:286-294`.\n- `defaultmodules/updatenotification/node_helper.js:46-68` accepts `CONFIG`, `MODULES`, and `SCAN_UPDATES` notifications and trusts the socket-provided config/module list.\n- `defaultmodules/updatenotification/update_helper.js:43-47` stores update commands from config.\n- `defaultmodules/updatenotification/update_helper.js:96-116` executes the selected update command with `child_process.exec` in the module directory.\n- `defaultmodules/updatenotification/update_helper.js:221-227` looks up the command from `config.updates` by module name.\n\nFalse-positive screening performed:\n\n- 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.\n- 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`.\n- 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.\n- 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.\n\nAffected-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.\n\n### PoC\nThe 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.\n\n1. Confirm that the runtime lacks Socket.IO IP allowlist controls:\n\n```bash\ngrep -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\n```\n\nObserved output:\n\n```text\njs/server.js:44:\t\t\t\tcors: {\njs/server.js:90:\t\t\tapp.use(ipAccessControl(config.ipWhitelist));\n```\n\nThis confirms the IP allowlist appears only as Express middleware and no Socket.IO handshake/namespace allowlist was present in the reviewed runtime files.\n\n2. Confirm a default module helper will perform a server-side request to an attacker-supplied loopback URL when driven through its socket notification handler:\n\n```bash\nnode -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);'\n```\n\nObserved output:\n\n```text\nSOCKET ARTICLE_URL_STATUS {\"url\":\"http://127.0.0.1:65535/internal\",\"canFrame\":false}\nfetchCalls= [{\"url\":\"http://127.0.0.1:65535/internal\",\"opts\":{\"method\":\"HEAD\"}}]\n```\n\nExpected 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.\n\n3. Confirm the conditional command-execution sink is reachable from the trusted socket-driven update path using a harmless `/tmp` marker command:\n\n```bash\nrm -f /tmp/mm-rce-marker\nmkdir -p /tmp/mm-audit/modules/evil /tmp/mm-audit/defaultmodules\nnode -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/updatenotification/node_helper\"); const h=new Helper(); h.gitHelper={add:async()=>{},getRepos:async()=>[{module:\"evil\",behind:1}],checkUpdates:()=>[{module:\"evil\",behind:1}]}; h.sendSocketNotification=(n,p)=>console.log(\"SOCKET\",n,JSON.stringify(p)); (async()=>{ await h.socketNotificationReceived(\"CONFIG\",{updates:[{evil:\"printf ok > /tmp/mm-rce-marker\"}],updateTimeout:5000,updateAutorestart:false,ignoreModules:[],sendUpdatesNotifications:false,updateInterval:60000,useModulesFromConfig:true}); await h.socketNotificationReceived(\"MODULES\",[\"evil\"]); console.log(\"marker=\",fs.readFileSync(\"/tmp/mm-rce-marker\",\"utf8\")); process.exit(0); })();'\nrm -f /tmp/mm-rce-marker\nrm -rf /tmp/mm-audit\n```\n\nObserved output from the executed harness:\n\n```text\nSOCKET REPO_STATUS {\"module\":\"evil\",\"behind\":1}\nSOCKET UPDATE_STATUS {\"name\":\"evil\",\"updateCommand\":\"printf ok > /tmp/mm-rce-marker\",\"inProgress\":true,\"error\":false,\"updated\":true,\"needRestart\":true}\nmarker= ok\n```\n\nExpected vulnerable output: `marker= ok` demonstrates the update command supplied through the trusted socket configuration path reached `child_process.exec` and wrote the harmless marker.\n\nNegative/control cases:\n\n- With the shipped committed defaults (`js/defaults.js:8-13`), the server binds `localhost` and `ipWhitelist` includes only loopback, so a remote network attacker cannot reach either HTTP or Socket.IO unless the deployment is changed to a documented non-loopback address.\n- The `updatenotification` command execution path requires at least one non-core module update result; if `checkUpdates()` returns no third-party module with `behind > 0`, `update_helper.parse(...)` does not execute an update command.\n- The explicit `/cors` route was not used for the confirmed request primitive and has separate protocol/hostname/DNS checks.\n\nFinal repro re-check: the sink search, newsfeed socket request harness, and update marker harness were re-run after drafting; the observed outputs above are from this environment. Cleanup removed `/tmp/mm-rce-marker` and `/tmp/mm-audit`.\n\n### Impact\nIn a documented common non-loopback deployment that relies on `ipWhitelist` for access control, an unauthenticated network client can bypass the intended IP allowlist for Socket.IO module namespaces. The attacker can send arbitrary module-helper notifications and payloads as if they were a trusted browser client.\n\nConfirmed impacts include:\n\n- Confidentiality/SSRF: server-side requests to attacker-chosen URLs through default module helpers, including loopback/internal URLs. The newsfeed PoC shows a `HEAD` request to `127.0.0.1`.\n- Integrity/availability: arbitrary manipulation of module-helper state and periodic fetch/update behavior through trusted socket messages.\n- Conditional code execution: when a third-party git-managed module is considered behind by the update checker, an attacker can supply a command in the socket `CONFIG` payload and trigger `child_process.exec`; the PoC safely wrote a `/tmp` marker.\n\nCVSS 3.1 rationale for the primary trust-boundary bypass with confirmed SSRF and conditional RCE variant: AV:A because MagicMirror's security policy says it is intended for trusted local/private networks and the documented non-loopback exposure is LAN-style; AC:H because default loopback settings must be changed and the RCE variant requires a pending third-party module update, though SSRF requires fewer conditions after reachability; PR:N because no application authentication is required; UI:N because the attacker connects directly to Socket.IO; S:C because the vulnerable application can cause requests/actions against other local/internal services and can execute commands in a child process in the conditional variant; C:L/I:L/A:L for confirmed internal request and helper-state/command side effects, with conservative scoring because unconditional default RCE was not shown.\n\n### Suggested remediation\nApply the same access-control decision to Socket.IO handshakes and namespaces as to Express routes. Concretely, add a Socket.IO `allowRequest` or `io.use(...)` middleware that normalizes `socket.handshake.address`/request IPs with the same `ipAccessControl` logic, and reject clients not allowed by `config.ipWhitelist`. Avoid relying on CORS for authorization; keep origin checks as a browser hardening layer only.\n\nAlso add module-helper authorization so arbitrary clients cannot send privileged server-side module notifications. For example, issue an unguessable per-session/module token to the served client and require it on helper messages, or separate read-only client events from privileged server-maintenance actions.\n\nFor defense in depth:\n\n- Add SSRF protections or allowlists to calendar/newsfeed/weather helper fetch paths, not only `/cors`.\n- Do not accept `updatenotification` update commands from socket payloads; use the server-loaded config only, validate module names against configured modules, and avoid shell execution where possible.\n- Add regression tests proving a disallowed IP receives a rejected Socket.IO handshake even when Express routes are protected, and proving `/newsfeed`/`/calendar` helper messages from unauthenticated sockets cannot trigger server-side requests or update commands.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "magicmirror"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.37.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/MagicMirrorOrg/MagicMirror/security/advisories/GHSA-w26r-fwg8-rcp3"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/MagicMirrorOrg/MagicMirror/pull/4169"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/MagicMirrorOrg/MagicMirror/commit/58c2a5e675a7d367b64d72e1d35680d202ff5c9f"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/MagicMirrorOrg/MagicMirror"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/MagicMirrorOrg/MagicMirror/releases/tag/v2.37.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-284" ],
    "severity" : "LOW",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-18T17:26:41Z",
    "nvd_published_at" : null
  }
}