CVE-2026-50289: Verified Reproduction
CVE-2026-50289: systeminformation networkInterfaces Linux source-directive command injection
CVE-2026-50289 is verified against sebhildebrandt/systeminformation · github. Affected versions: <= 5.31.6. Fixed in 5.31.7. Vulnerability class: Command Injection. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00291.
What Is CVE-2026-50289?
CVE-2026-50289 is a high-severity OS command injection (CWE-78) in the sebhildebrandt/systeminformation npm library on Linux. Calling the public networkInterfaces() API can execute attacker-controlled shell commands as the Node.js process user. Pruva reproduced it (reproduction REPRO-2026-00291).
CVE-2026-50289 Severity & CVSS Score
CVE-2026-50289 is rated high severity, with a CVSS base score of 8.7 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
Affected sebhildebrandt/systeminformation Versions
sebhildebrandt/systeminformation · github versions <= 5.31.6 are affected.
How to Reproduce CVE-2026-50289
pruva-verify REPRO-2026-00291 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00291/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-50289
- reached the target end-to-end
- full exploit chain demonstrated
- high confidence
- the upstream fix blocks the same trigger
Shell-metacharacter-bearing path inside a Debian/Ubuntu 'source' directive line in a file reachable through /etc/network/interfaces source recursion (e.g. /etc/network/interfaces.d/*)
- public si.networkInterfaces()
- getLinuxDHCPNics()
- checkLinuxDCHPInterfaces('/etc/network/interfaces')
- recursive source traversal
- execSync(`cat ${file} 2> /dev/null | grep 'iface\|source'`) with unquoted ${file}
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-50289
On Linux, sebhildebrandt/systeminformation's public networkInterfaces() API
reaches lib/network.js → getLinuxDHCPNics() →
checkLinuxDCHPInterfaces('/etc/network/interfaces'). That helper reads the
Debian/Ubuntu network-interface file, follows source directives recursively,
and in versions <5.31.7 interpolates the parsed source path unquoted into a
shell command executed via child_process.execSync():
function checkLinuxDCHPInterfaces(file) {
let result = [];
try {
const cmd = `cat ${file} 2> /dev/null | grep 'iface\\|source'`;
const lines = execSync(cmd, util.execOptsLinux).toString().split('\n');
...
if (line.toLowerCase().includes('source')) {
const file = line.split(' ')[1];
result = result.concat(checkLinuxDCHPInterfaces(file)); // recursive, unquoted
}
...
Because ${file} is treated as shell text rather than a filename argument,
shell metacharacters inside a source target execute arbitrary commands as the
Node.js process user. The injected value originates from internal file content
(a source line in a file reachable through /etc/network/interfaces source
recursion, e.g. a file under /etc/network/interfaces.d/*), not from a direct
API argument.
- Package/component:
systeminformationnpm package,lib/network.js,checkLinuxDCHPInterfaces()(private helper reached by the publicnetworkInterfaces()API and public aggregate callersgetStaticData()/getAllData()). - Affected versions:
<5.31.7(verified on 5.31.6 / commitda1cbf5). - Risk level: High. Arbitrary command execution as the Node.js process user
whenever an attacker can write or influence a file reached through the
/etc/network/interfacessourcerecursion on a Debian/Ubuntu-style Linux host that calls the public network API.
Impact Parity
- Disclosed/claimed maximum impact: Arbitrary command execution (code
execution) via the public
networkInterfaces()API on Linux. - Reproduced impact from this run: Arbitrary command execution confirmed —
a harmless marker oracle file was created by the injected
touchcommand only when the vulnerable 5.31.6 build processed the craftedsourcedirective, and the call resolved normally (NETWORK_INTERFACES_RESOLVED,marker_exists=true). - Parity:
full. The proof demonstrates attacker-controlled shell command execution (creation of an attacker-chosen file) through the public API on the vulnerable version, with the fixed version negative control showing no execution while the same benign source chain still parses. - Not demonstrated: A destructive payload was intentionally not used (safety constraint); only a harmless marker oracle was employed.
Root Cause
checkLinuxDCHPInterfaces(file) builds a shell command with an unquoted JS
template-string interpolation of file:
const cmd = `cat ${file} 2> /dev/null | grep 'iface\\|source'`;
const lines = execSync(cmd, util.execOptsLinux).toString().split('\n');
file is initially /etc/network/interfaces but, on each source directive
line, the code extracts line.split(' ')[1] and recurses into
checkLinuxDCHPInterfaces(file) with that raw, attacker-influenced value. Since
the value is spliced into a shell string (not passed as an argument to
execFileSync/readFileSync), any shell metacharacters in the source target are
interpreted by /bin/sh. A source line such as:
source /tmp/pruva_inj_nonexist;touch<TAB>/tmp/pruva_cve_2026_50289_marker
is split by JS on the single space into file = "/tmp/pruva_inj_nonexist;touch\t/tmp/pruva_cve_2026_50289_marker" (the literal
TAB survives because split(' ') only splits on the space character). The shell
then parses cat /tmp/pruva_inj_nonexist;touch\t/tmp/marker 2> /dev/null | grep ... as two commands separated by ;, executing touch /tmp/marker (TAB is
shell whitespace), creating the marker file.
Fix commit: bbfddde48672d0ee124fefdb3cb4442fd9dd4f03
(https://github.com/sebhildebrandt/systeminformation/commit/bbfddde48672d0ee124fefdb3cb4442fd9dd4f03)
replaced execSync('cat ... | grep ...') with fs.readFileSync(file, { encoding: 'utf8' }) plus an in-JS .filter((l) => /iface|source/.test(l)), eliminating the
shell entirely while preserving source-directive recursion.
Reproduction Steps
- Script:
bundle/repro/reproduction_steps.sh(self-contained, idempotent). - What it does:
- Reuses the prepared project-cache repo (or clones
sebhildebrandt/systeminformationwhen no cache exists). - Checks out the vulnerable release
v5.31.6(da1cbf5, parent of the fix) and the fixed release5.31.7(bbfddde). - Crafts a controlled
/etc/network/interfacescontaining a benignsourcedirective (functional control pointing to a real DHCP iface config) and a malicioussourcedirective whose target embeds;touch<TAB>followed by a harmless marker path under/tmp. - Invokes only the public
si.networkInterfaces(true)API for each build (no private helper is called directly). - Observes a harmless marker oracle file: created on 5.31.6, absent on 5.31.7.
- Restores the original
/etc/network/interfaces.
- Reuses the prepared project-cache repo (or clones
- Expected evidence:
logs/vulnerable_5_31_6_api.log→NETWORK_INTERFACES_RESOLVED,marker_exists=true.logs/fixed_5_31_7_api.log→NETWORK_INTERFACES_RESOLVED,marker_exists=false.logs/reproduction_steps.log→VERDICT: CONFIRMED.repro/runtime_manifest.json→target_path_reached=true,confirmed=true.
Evidence
Log files:
bundle/logs/reproduction_steps.log— full run transcript.bundle/logs/vulnerable_5_31_6_api.log— vulnerable API output.bundle/logs/fixed_5_31_7_api.log— fixed API output (negative control).bundle/logs/interfaces.backup— original/etc/network/interfacesbackup.
Key excerpts:
Vulnerable (5.31.6):
NETWORK_INTERFACES_RESOLVED marker_exists=true iface_count=2Fixed (5.31.7, same crafted source chain):
NETWORK_INTERFACES_RESOLVED marker_exists=false iface_count=2Summary:
vulnerable 5.31.6 marker created : true fixed 5.31.7 marker created : false VERDICT: CONFIRMED - command injection via public networkInterfaces() on 5.31.6; absent on fixed 5.31.7Environment: Linux, Node v24.18.0,
systeminformationbuilt from source at commitsda1cbf5(v5.31.6) andbbfddde(5.31.7). No sanitizers; the marker oracle is a real filesystem side effect of an attacker-controlled shell command.
Recommendations / Next Steps
- Upgrade: Update to
systeminformation >= 5.31.7. The fix removes the shell entirely by usingfs.readFileSync. - Defense in depth: For any remaining
execSync/execcall sites in the package that interpolate file-system-derived values, switch toexecFileSync/readFileSyncwith arguments passed as array elements, or validate paths against an allow-list. - Testing: Add a regression test that places a
sourcedirective with shell metacharacters in a controlled interfaces file and asserts no shell side effect occurs, while a benignsourcechain still parses.
Additional Notes
- Idempotency: The script was run twice consecutively; both runs produced
confirmed=truewith identical evidence (vulnerable marker=true,fixed marker=false). The script backs up and restores the original/etc/network/interfaceson every run. - Safety: Only a harmless marker oracle (
touchof an empty file under/tmp) was used; no destructive payload. The privatecheckLinuxDCHPInterfaces()helper was never invoked directly — only the publicnetworkInterfaces()API was called. - Scope: The claim surface is
library_api(function_call), matching the accepted claim contract. The proof exercises the real public API end-to-end on a real Linux runtime with the real package source; the fixed-version negative control confirms the patch blocks the injection while preserving benignsourceparsing.
CVE-2026-50289 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired. Phases: support · claim contract · reproduction · judge · variant analysis
Full session Replay every step — scrub the timeline or play it back.
ls -la /data/pruva/project-cache/ebe61b2a-f601-4416-916e-129c2338e243/ 2>/dev/null; echo "---repo---"; ls -la /data/pruva/project-cache/ebe61b2a-f601-4416-916e-129c2338e243/repo/ 2>/dev/null{"cwd":"/data/pruva/runs/871f48bd-5e2c-49d2-b789-559e145055b3","duration_ms":7,"exit_code":2,"stderr":"","stdout":"total 0\ndrwxr-xr-x 1 vscode vscode 24 Jul 16 08:10 .\ndrwxr-xr-x 1 vscode vscode 7388 Jul 16 08:10 ..\ndrwxr-xr-x 1 vscode vscode 0 Jul 16 08:10 repo-mirrors\n---repo---\n","success":false,"timeout_secs":120}Unknown error
cd /data/pruva/project-cache/ebe61b2a-f601-4416-916e-129c2338e243 && git clone --quiet https://github.com/sebhildebrandt/systeminformation.git repo 2>&1 | tail -5; echo "EXIT=$?"; cd repo && git log --oneline -3 bbfddde48672d0ee124fefdb3cb4442fd9dd4f03 2>&1 | headEXIT=0 bbfddde networkInterfaces() fix unsanitized command (linux) da1cbf5 5.31.6 87dbb60 fix: smaller issues
cd /data/pruva/project-cache/ebe61b2a-f601-4416-916e-129c2338e243/repo && git checkout --quiet da1cbf5 2>&1; echo "vuln checkout EXIT=$?"; sed -n '530,640p' lib/network.js{"cwd":"/data/pruva/runs/871f48bd-5e2c-49d2-b789-559e145055b3","duration_ms":14,"exit_code":0,"stderr":"","stdout":"vuln checkout EXIT=0\n}\n\nfunction getLinuxIfaceConnectionName(interfaceName) {\n try {\n const output = execFileSync('nmcli', ['device', 'status'], { ...util.execOptsLinux, stdio: ['ignore', 'pipe', 'ignore'] }).toString();\n const result = util.grep(output, interfaceName);\n\n const resultFormat = result.replace(/\\s+/g, ' ').trim();\n const connectionNameLines = resultFormat.split(' ').slice(3);\n const connectionName = connectionNameLines.join(' ');\n const connectionNameSanitized = util.sanitizeString(connectionName, false);\n\n return connectionNameSanitized !== '--' ? connectionNameSanitized : '';\n } catch {\n return '';\n }\n}\n\nfunction checkLinuxDCHPInterfaces(file) {\n let result = [];\n try {\n const cmd = `cat ${file} 2> /dev/null | grep 'iface\\\\|source'`;\n const lines = execSync(cmd, util.execOptsLinux).toString().split('\\n');\n\n lines.forEach((line) => {\n const parts = line.replace(/\\s+/g, ' ').trim().split(' ');\n if (parts.length >= 4) {\n if (line.toLowerCase().indexOf(' inet ') >= 0 &&… [truncated]cd /data/pruva/project-cache/ebe61b2a-f601-4416-916e-129c2338e243/repo && git checkout --quiet bbfddde48672d0ee124fefdb3cb4442fd9dd4f03 2>&1; echo "fixed EXIT=$?"; sed -n '549,560p' lib/network.js; echo "---pkg main---"; grep -E '"main"|"name"|"version"' package.jsonfixed EXIT=0
function checkLinuxDCHPInterfaces(file) {
let result = [];
try {
const content = readFileSync(file, { encoding: 'utf8' });
const lines = content.split('\n').filter((l) => /iface|source/.test(l));
lines.forEach((line) => {cd /data/pruva/project-cache/ebe61b2a-f601-4416-916e-129c2338e243/repo && sed -n '549,575p' lib/network.jsfunction checkLinuxDCHPInterfaces(file) {
let result = [];
try {
const content = readFileSync(file, { encoding: 'utf8' });
const lines = content.split('\n').filter((l) => /iface|source/.test(l));
lines.forEach((line) => {
const parts = line.replace(/\s+/g, ' ').trim().split(' ');Artifacts and Evidence for CVE-2026-50289
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-50289
Upgrade sebhildebrandt/systeminformation · github to 5.31.7 or later.
FAQ: CVE-2026-50289
What has to be true to exploit CVE-2026-50289?
Which versions of systeminformation are affected by CVE-2026-50289, and where is it fixed?
How severe is CVE-2026-50289?
How can I reproduce CVE-2026-50289?
References for CVE-2026-50289
Authoritative sources for CVE-2026-50289 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.