Oct 29, 2026·11 min read·1 visit
Prior to version 5.33.16, the systeminformation Node.js package fails to strip Unicode typographic quotation marks in sanitizeShellString() on Windows. Attackers who can pass input to services() or getDynamicData() can use Unicode quotes to break out of a PowerShell Get-CimInstance filter string and execute arbitrary OS commands.
CVE-2026-116576 (GHSA-3f7j-7jwc-fxfj) is an OS Command Injection vulnerability (CWE-78) in the Node.js systeminformation library prior to version 5.33.16 on Microsoft Windows. The services() API function dynamically constructs a PowerShell Get-CimInstance command using caller-supplied service names passed through sanitizeShellString(). While the sanitizer strips standard ASCII quotation marks, it permits Unicode typographic quotation marks (U+2018 through U+201E). Because the Windows PowerShell tokenizer recognizes Unicode quotation marks as valid string literal delimiters, an attacker who controls the service name input can break out of the CIM filter string and execute arbitrary PowerShell commands within the security context of the Node.js process.
The systeminformation package is a widely deployed Node.js library designed to retrieve hardware, operating system, and process metrics across Linux, macOS, and Windows environments. Because Node.js does not provide native bindings for all operating system queries, systeminformation frequently invokes underlying system utilities and shell interpreters—such as Windows PowerShell, WMI, and standard POSIX binaries—to gather runtime telemetry. When applications expose system monitoring endpoints or administration interfaces, user-supplied parameters are frequently passed into systeminformation functions to filter specific processes, network interfaces, or system services.
In versions prior to 5.33.16, the library contains an OS command injection vulnerability (CWE-78) affecting Microsoft Windows deployments. The flaw resides in the interaction between services() in lib/processes.js and the input sanitization routine sanitizeShellString() in lib/util.js. When querying the state of specific Windows services, services() interpolates caller-supplied service names directly into a PowerShell Get-CimInstance Win32_Service -Filter command string.
Although systeminformation processes caller arguments through sanitizeShellString() to strip shell metacharacters and standard ASCII quotes, the sanitizer fails to account for character encoding equivalencies in the Windows PowerShell parser. Specifically, PowerShell treats typographic Unicode quotation marks (U+2018 through U+201E) as functionally identical to standard ASCII single (') and double (") quotes.
Consequently, an attacker who controls the string passed to services() or the wrapper function getDynamicData() can supply Unicode quotation marks to prematurely terminate the WQL/CIM filter literal. Once outside the quoted context, the attacker can append arbitrary PowerShell expressions that execute with the privileges of the host Node.js runtime.
The root cause of CVE-2026-116576 is an incomplete denylist in lib/util.js combined with dynamic command string concatenation in lib/processes.js without parameterized execution. When systeminformation.services(srv) executes on Windows (_windows === true), it parses the srv argument into an array of target service names and constructs a PowerShell pipeline using Get-CimInstance.
To prevent command injection across its API surface, systeminformation passes user-supplied arguments through sanitizeShellString(str, strict). This function iterates over characters in the input string and strips known dangerous characters, including ASCII single quotes (' / U+0027), ASCII double quotes (" / U+0022), backticks (` / U+0060), and various shell operators. However, the sanitizer relies on explicit character comparisons against standard ASCII code points.
The Windows PowerShell lexical analyzer (tokenizer) is explicitly engineered to normalize typographic punctuation so that scripts copied from web pages or word processors execute without syntax errors. In the PowerShell tokenizer source (System.Management.Automation.Language.Tokenizer), the following Unicode code points are evaluated as valid string literal delimiters:
U+2018 (‘) — Left Single Quotation Mark (treated as ')U+2019 (’) — Right Single Quotation Mark (treated as ')U+201A (‚) — Single Low-9 Quotation Mark (treated as ')U+201B (‛) — Single High-Reversed-9 Quotation Mark (treated as ')U+201C (“) — Left Double Quotation Mark (treated as ")U+201D (”) — Right Double Quotation Mark (treated as ")U+201E („) — Double Low-9 Quotation Mark (treated as ")Similarly, PowerShell normalizes typographic dashes (U+2013 En Dash –, U+2014 Em Dash —, and U+2015 Horizontal Bar ―) into standard parameter hyphens (- / U+002D).
Because sanitizeShellString() only inspects single-unit UTF-16 code units against ASCII quote characters, characters in the U+2018–U+201E range pass through unmodified. When services() embeds the sanitized output into -Filter "Name='${srv}' or ...", a Unicode double quote (”) or single quote (’) breaks the syntax boundary in PowerShell, transitioning the parser from a string literal context into an executable command or expression context.
Prior to version 5.33.16, services() in lib/processes.js constructed the Windows service query by concatenating each sanitized service name directly into the wincommand string variable:
// Vulnerable implementation in lib/processes.js (prior to 5.33.16)
if (_windows) {
let wincommand = 'Get-CimInstance Win32_Service';
if (srvs[0] !== '*') {
wincommand += ' -Filter "';
srvs.forEach((srv) => {
// srv has only been filtered by sanitizeShellString(), which permits Unicode quotes
wincommand += `Name='${srv}' or `;
});
wincommand = `${wincommand.slice(0, -4)}"`;
}
wincommand += ' | select Name,Caption,Started,StartMode,ProcessId | fl';
util.powerShell(wincommand).then((stdout, error) => {
// Process output...
});
}In lib/util.js, sanitizeShellString() checked individual characters against a static list of ASCII symbols. Because Unicode typographic quotes are represented as single 16-bit code units in JavaScript (s[i].length === 1), they also bypassed the s[i].length > 1 check intended to block surrogate pairs:
// Vulnerable check in sanitizeShellString (lib/util.js prior to 5.33.16)
function sanitizeShellString(str, strict) {
const s = str || '';
let result = '';
for (let i = 0; i <= mathMin(s.length, 2000); i++) {
if (
!(
s[i] === undefined ||
s[i] === '>' ||
s[i] === '<' ||
s[i] === '*' ||
s[i] === '?' ||
s[i] === '[' ||
s[i] === ']' ||
s[i] === '|' ||
s[i] === '˚' ||
s[i] === '$' ||
s[i] === ';' ||
s[i] === '&' ||
s[i] === '(' ||
s[i] === ')' ||
s[i] === ']' ||
s[i] === '#' ||
s[i] === '\\' ||
s[i] === '\t' ||
s[i] === '\n' ||
s[i] === '\r' ||
s[i] === "'" ||
s[i] === '`' ||
s[i] === '"' ||
s[i].length > 1 ||
(strict && s[i] === '(') ||
(strict && s[i] === ')')
)
) {
result = result + s[i];
}
}
return result;
}Note that in services(), sanitizeShellString() is invoked earlier in the function without the strict flag, though parentheses, semicolons, and dollar signs are blocked in non-strict mode depending on the exact version branch. However, even when ;, $, and | are stripped by sanitizeShellString(), breaking out of the double-quoted -Filter "..." string with a Unicode double quote (”, U+201D) allows an attacker to inject additional parameters or pipeline/cmdlet arguments directly into Get-CimInstance or chain commands using whitespace and PowerShell operators that are not blocked.
In commit 5014c6d25cfce7363b937977327baa853cdba0bf, the maintainer implemented a two-layer defense-in-depth fix. First, lib/processes.js was updated to enforce a strict character allowlist (/[^a-zA-Z0-9 _.\-@]/g) on each service name immediately prior to interpolation:
diff --git a/lib/processes.js b/lib/processes.js
index 4758d10..ad741f2 100644
--- a/lib/processes.js
+++ b/lib/processes.js
@@ -377,22 +360,23 @@ function services(srv, callback) {
if (srvs[0] !== '*') {
wincommand += ' -Filter "';
srvs.forEach((srv) => {
- wincommand += `Name='${srv}' or `;
+ // whitelist: srv is inserted into a PowerShell string
+ wincommand += `Name='${srv.replace(/[^a-zA-Z0-9 _.\-@]/g, '')}' or `;
});
wincommand = `${wincommand.slice(0, -4)}"`;
}
wincommand += ' | select Name,Caption,Started,StartMode,ProcessId | fl';
util.powerShell(wincommand).then((stdout, error) => {Second, sanitizeShellString() in lib/util.js was updated to strip Unicode quotation marks (U+2018–U+201E) and Unicode dashes (U+2013–U+2015):
diff --git a/lib/util.js b/lib/util.js
index 72bc631..1f14ec0 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -749,6 +749,9 @@ function sanitizeShellString(str, strict) {
s[i] === "'" ||
s[i] === '`' ||
s[i] === '"' ||
+ // unicode quotes (U+2018-U+201E) and dashes (U+2013-U+2015) are treated as ' " - by PowerShell
+ (s[i] >= '‘' && s[i] <= '„') ||
+ (s[i] >= '–' && s[i] <= '―') ||
s[i].length > 1 ||
(strict && s[i] === '(') ||
(strict && s[i] === ')') ||> [!NOTE]
> Fix Completeness Assessment: The addition of a strict regular expression allowlist (/[^a-zA-Z0-9 _.\-@]/g) in lib/processes.js completely neutralizes injection vectors within services() regardless of PowerShell parser quirks. However, for other functions relying solely on sanitizeShellString(), the character range check (s[i] >= '‘' && s[i] <= '„') covers U+2018 to U+201E, and (s[i] >= '–' && s[i] <= '―') covers U+2013 to U+2015. While this blocks the standard typographic quote and dash sets in PowerShell 5.1, denylist-based shell sanitization remains inherently fragile compared to parameterized execution or strict allowlisting, especially if other Unicode characters (such as U+FF02 Fullwidth Quotation Mark " or U+FF07 Fullwidth Apostrophe ', or U+00AD Soft Hyphen) are handled specially by downstream command interpreters.
Exploiting CVE-2026-116576 requires a target Node.js application running on Microsoft Windows with systeminformation version < 5.33.16. Because systeminformation is a library and does not expose network sockets by default, an attacker must identify an application entry point—such as an HTTP API parameter, WebSocket message, IPC channel in an Electron application, or CLI argument—that passes attacker-controlled strings into si.services(srv) or si.getDynamicData(srv, ...).
When si.services() processes the input, it splits the input string by comma (,) and sanitizes it. To break out of the Get-CimInstance Win32_Service -Filter "Name='...'" context, the attacker includes Unicode quotation marks such as ” (U+201D) or ’ (U+2019).
Consider the following vulnerable application code:
const express = require('express');
const si = require('systeminformation');
const app = express();
app.get('/api/service-status', async (req, res) => {
const serviceName = req.query.name;
// User input passed directly to si.services()
const data = await si.services(serviceName);
res.json(data);
});
app.listen(3000);When the application invokes si.services(payload) on Windows, util.powerShell() executes the assembled command string via child_process.exec invoking powershell.exe. By injecting ” (U+201D), the attacker closes the -Filter " double-quoted argument early:
const si = require('systeminformation');
// Conceptual Proof-of-Concept on Windows (< 5.33.16)
// Using Unicode quotation marks (U+2019 / U+201D) to terminate the -Filter string literal
const payload = "spooler’” ...";
si.services(payload).then(data => {
console.log(data);
});In the PowerShell tokenizer, the injected Unicode double quote (”) terminates the string argument bound to -Filter. Any subsequent tokens in the payload are parsed as top-level arguments or expressions in the PowerShell pipeline executed under the node.exe process token.
CVE-2026-116576 is rated 7.8 (High) under CVSS v3.1 with the vector CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. The Attack Vector is scored as Local (AV:L) because systeminformation is a local Node.js package that does not inherently listen on a network interface. However, if a consuming web application or microservice forwards remote HTTP request parameters to services() or getDynamicData(), the effective exposure in that specific deployment becomes network-reachable.
Successful exploitation grants the attacker arbitrary PowerShell execution under the Windows security context of the running node.exe process. This results in complete compromise of Confidentiality (C:H), Integrity (I:H), and Availability (A:H) within the scope of the service account running the Node.js application:
Because many system monitoring agents, desktop Electron utilities, and infrastructure management tools utilizing systeminformation run with elevated privileges (such as NT AUTHORITY\SYSTEM or local Administrator) to query privileged OS metrics on Windows, successful command injection frequently leads to full host compromise.
According to FIRST EPSS metrics, the vulnerability currently carries an EPSS score of 0.00145 (0.145%, 3.76th percentile), reflecting that automated mass exploitation is uncommon for library-level vulnerabilities that depend on application-specific routing. CVE-2026-116576 is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
The primary remediation is to upgrade the systeminformation package to version 5.33.16 or later. Version 5.33.16 incorporates both the strict regular expression allowlist in lib/processes.js and the extended Unicode quote and dash filtering in lib/util.js.
npm install systeminformation@^5.33.16For environments where dependencies are locked or immediate upgrading is not feasible, developers must enforce strict allowlist validation at the application boundary before passing any external input to systeminformation.services() or systeminformation.getDynamicData():
const SAFE_SERVICE_REGEX = /^[a-zA-Z0-9 _.\-@,]+$/;
function queryWindowsServices(userInput) {
if (typeof userInput !== 'string' || !SAFE_SERVICE_REGEX.test(userInput)) {
throw new Error('Invalid service name parameter');
}
return si.services(userInput);
}In addition to input validation, operators deploying Node.js applications on Windows should ensure that the node.exe process runs under a dedicated, least-privilege local service account rather than NT AUTHORITY\SYSTEM or an administrative user, unless privileged WMI/CIM access is strictly required.
From a defensive monitoring perspective, security teams should configure Windows Event Logging (PowerShell Script Block Logging, Event ID 4104) and Endpoint Detection and Response (EDR) or Sysmon (Event ID 1 Process Creation) to inspect powershell.exe child processes spawned by node.exe. Alert on command lines containing Get-CimInstance Win32_Service -Filter that include Unicode quotation marks (\u2018–\u201e) or anomalous PowerShell cmdlets.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
systeminformation sebhildebrandt | < 5.33.16 | 5.33.16 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | OS Command Injection (CWE-78) |
| Affected Component | systeminformation (services() in lib/processes.js & sanitizeShellString() in lib/util.js) |
| Target Platform | Microsoft Windows (Node.js runtime) |
| CVSS v3.1 Score | 7.8 HIGH (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| EPSS Score | 0.145% (3.76th Percentile) |
| Root Cause | Incomplete quote sanitization allowing Unicode smart quotes (U+2018-U+201E) to break out of PowerShell Get-CimInstance filter strings |
| Patched Version | 5.33.16 |
| CISA KEV Status | Not Listed |
The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
CVE-2026-115923 (GHSA-w658-mh9p-ww8f) is a path traversal and access control bypass vulnerability in Spotify's Backstage developer portal framework, specifically within the @backstage/plugin-catalog-backend package. Prior to patched versions, DefaultCatalogRulesEnforcer evaluated user-supplied URL catalog locations against configured catalog.rules patterns using raw, uncanonicalized strings. Because downstream URL readers canonicalize URLs and resolve dot segments before fetching resources, an authenticated attacker with location registration permissions can supply percent-encoded traversal sequences (such as %2e%2e) to bypass location restrictions and register untrusted catalog entities.
An interpretation conflict vulnerability (CWE-436) exists in the Ruby CSS parser library Crass prior to version 1.0.8. When consuming the remnants of a malformed URL token, the tokenizer erroneously checks the previously consumed character to determine if a valid escape sequence is present. Consequently, a closing parenthesis immediately following an escaped backslash sequence is treated as escaped by Crass, while spec-compliant web browsers treat the closing parenthesis as the end of the bad URL token. Downstream CSS sanitizers such as Sanitize, Loofah, and rails-html-sanitizer that rely on Crass to parse and reserialize untrusted CSS can be bypassed to inject arbitrary CSS declarations, style rules, and external @import directives.
Tornado versions prior to 6.6.0 contain an inefficient algorithmic complexity vulnerability in the HTTP header parser. When processing requests with obsolete line folding (obs-fold), the HTTPHeaders.parse_line method repeatedly concatenates immutable Python strings within a list structure. An unauthenticated remote attacker can send an HTTP request containing thousands of short header continuation lines to trigger quadratic O(N^2) CPU consumption on the main IOLoop thread, starving concurrent connections and resulting in a denial of service.
Tornado versions prior to 6.6.0 contain an open redirect vulnerability (CWE-601) in tornado.web.RedirectHandler. When route templates incorporate regular expression capture groups at the start of a path, directly after a fixed host without a trailing slash, or at the beginning of a destination template, unvalidated URL-decoded parameters allow attackers to manipulate the redirect scheme or network location. Because modern browsers normalize URLs according to the WHATWG specification—treating backslashes as forward slashes and stripping control characters—attackers can bypass naive prefix checks and redirect victims to arbitrary external domains.
Tornado versions prior to 6.6.0 contain an uncontrolled resource consumption vulnerability (CWE-400) in the HTTP/1.1 chunked transfer encoding parser. When processing incoming chunked payloads that have already buffered in the socket read buffer, Tornado parses chunks in a tight synchronous loop without yielding to the event loop. Furthermore, non-streaming request handlers and SimpleAsyncHTTPClient store each chunk as a discrete Python bytes object inside a list, inducing an approximately 20-fold memory overhead for 1-byte chunks.
A missing cryptographic signature verification vulnerability exists in Electrum Bitcoin Wallet versions prior to 4.8.3. In Electrum's Simplified Payment Verification (SPV) architecture, the client failed to validate input signatures on unconfirmed mempool transactions received from connected Electrum servers. A malicious Electrum server can exploit this flaw by intercepting a legitimate user broadcast, suppressing it, and returning a spoofed unconfirmed transaction with an invalid signature and zero fee that redirects the user's UTXOs to an attacker-controlled address. When the user initiates a Replace-By-Fee (RBF) action to accelerate the seemingly stuck transaction, Electrum signs the spoofed outputs with the user's private keys, resulting in the direct theft of funds.