GHSA-W26R-FWG8-RCP3
Vulnerability from github – Published: 2026-08-18 17:26 – Updated: 2026-08-18 17:26Summary
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-50creates Socket.IO directly on the HTTP(S) server withcors.origin: /.*$/.js/server.js:89-90appliesipAccessControl(config.ipWhitelist)only withapp.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 werejs/server.js:44andjs/server.js:90. js/node_helper.js:88-103registers every module namespace and dispatches every socket event and payload directly tosocketNotificationReceived(...).
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-17acceptsCHECK_ARTICLE_URLand callscheckArticleUrl(payload.url).defaultmodules/newsfeed/node_helper.js:25-38performsfetch(url, { method: "HEAD" })on the supplied URL and sends the result back.defaultmodules/calendar/node_helper.js:13-24acceptsADD_CALENDAR/FETCH_CALENDARsocket messages.defaultmodules/calendar/node_helper.js:40-58accepts an arbitrary syntactically valid calendar URL and creates a fetcher.defaultmodules/calendar/calendarfetcher.js:35-44passes the URL intoHTTPFetcher, whose fetch sink isjs/http_fetcher.js:286-294.defaultmodules/updatenotification/node_helper.js:46-68acceptsCONFIG,MODULES, andSCAN_UPDATESnotifications and trusts the socket-provided config/module list.defaultmodules/updatenotification/update_helper.js:43-47stores update commands from config.defaultmodules/updatenotification/update_helper.js:96-116executes the selected update command withchild_process.execin the module directory.defaultmodules/updatenotification/update_helper.js:221-227looks up the command fromconfig.updatesby module name.
False-positive screening performed:
- Express HTTP routes are protected by
ipAccessControl(config.ipWhitelist)atjs/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
/corsHTTP 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:
updatenotificationonly 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 socketCONFIGmessage 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
ipWhitelistfor 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.
- 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.
- 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.
- Confirm the conditional command-execution sink is reachable from the trusted socket-driven update path using a harmless
/tmpmarker 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("./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:
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 bindslocalhostandipWhitelistincludes 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
updatenotificationcommand execution path requires at least one non-core module update result; ifcheckUpdates()returns no third-party module withbehind > 0,update_helper.parse(...)does not execute an update command. - The explicit
/corsroute 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
HEADrequest to127.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
CONFIGpayload and triggerchild_process.exec; the PoC safely wrote a/tmpmarker.
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
updatenotificationupdate 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//calendarhelper messages from unauthenticated sockets cannot trigger server-side requests or update commands.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "magicmirror"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.37.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-63641"
],
"database_specific": {
"cwe_ids": [
"CWE-284"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-18T17:26:41Z",
"nvd_published_at": null,
"severity": "LOW"
},
"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 \u0027const Module=require(\"module\"); const orig=Module._load; Module._load=(r,p,m)=\u003e{ if(r===\"logger\") return {log(){},error(){},warn(){},info(){},debug(){}}; if(r===\"node_helper\") return {create:(o)=\u003efunction(){Object.assign(this,o);this.sendSocketNotification=(n,p)=\u003econsole.log(\"SOCKET\",n,JSON.stringify(p));}}; if(r===\"./newsfeedfetcher\") return function(){}; return orig(r,p,m); }; const calls=[]; global.fetch=async(url,opts)=\u003e{calls.push({url,opts}); return {headers:{get:(h)=\u003eh===\"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(()=\u003econsole.log(\"fetchCalls=\",JSON.stringify(calls)),10);\u0027\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 \u0027const fs=require(\"node:fs\"); const Module=require(\"module\"); const orig=Module._load; Module._load=(r,p,m)=\u003e{ if(r===\"logger\") return {log(){},error(){},warn(){},info(){},debug(){}}; if(r===\"node_helper\") return {create:(o)=\u003efunction(){Object.assign(this,o);this.sendSocketNotification=()=\u003e{};}}; 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()=\u003e{},getRepos:async()=\u003e[{module:\"evil\",behind:1}],checkUpdates:()=\u003e[{module:\"evil\",behind:1}]}; h.sendSocketNotification=(n,p)=\u003econsole.log(\"SOCKET\",n,JSON.stringify(p)); (async()=\u003e{ await h.socketNotificationReceived(\"CONFIG\",{updates:[{evil:\"printf ok \u003e /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); })();\u0027\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 \u003e /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 \u003e 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\u0027s 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.",
"id": "GHSA-w26r-fwg8-rcp3",
"modified": "2026-08-18T17:26:41Z",
"published": "2026-08-18T17:26:41Z",
"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"
}
],
"schema_version": "1.4.0",
"severity": [
{
"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",
"type": "CVSS_V4"
}
],
"summary": "MagicMirror Socket.IO module namespaces bypass configured IP whitelist and allow unauthenticated server-side actions"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.