Skip to content

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.

REPRO-2026-00291 sebhildebrandt/systeminformation · github Command Injection Known vulnerability Jul 16, 2026 CVE entry .txt
Severity
HIGH
CVSS
8.7
Confidence
HIGH
Reproduced in
14m 44s
Tool calls
126
Spend
$1.26
01 · Overview

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).

02 · Severity & CVSS

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 threat level
8.7 / 10 CVSS base
03 · Affected Versions

Affected sebhildebrandt/systeminformation Versions

sebhildebrandt/systeminformation · github versions <= 5.31.6 are affected.

How to Reproduce CVE-2026-50289

$ pruva-verify REPRO-2026-00291
or 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
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-50289

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • high confidence
  • the upstream fix blocks the same trigger
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/*)

Attack chain
  1. public si.networkInterfaces()
  2. getLinuxDHCPNics()
  3. checkLinuxDCHPInterfaces('/etc/network/interfaces')
  4. recursive source traversal
  5. execSync(`cat ${file} 2> /dev/null | grep 'iface\|source'`) with unquoted ${file}
Runnable proof: reproduction_steps.sh
Captured evidence: vulnerable 5 31 6 apifixed 5 31 7 apiA fixed 5 31 7 apiB fixed 5 31 7 api
How the agent worked 465 events · 126 tool calls · 15 min
15 minDuration
126Tool calls
247Reasoning steps
465Events
3Dead-ends
Agent activity over 15 min
Policy
1
Support
12
Repro
78
Judge
263
Variant
106
Verify
1
0:0014:44

Root Cause and Exploit Chain for CVE-2026-50289

Versions: <5.31.7 (verified on 5.31.6 / commit da1cbf5).

On Linux, sebhildebrandt/systeminformation's public networkInterfaces() API reaches lib/network.jsgetLinuxDHCPNics()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: systeminformation npm package, lib/network.js, checkLinuxDCHPInterfaces() (private helper reached by the public networkInterfaces() API and public aggregate callers getStaticData()/getAllData()).
  • Affected versions: <5.31.7 (verified on 5.31.6 / commit da1cbf5).
  • 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/interfaces source recursion 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 touch command only when the vulnerable 5.31.6 build processed the crafted source directive, 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

  1. Script: bundle/repro/reproduction_steps.sh (self-contained, idempotent).
  2. What it does:
    • Reuses the prepared project-cache repo (or clones sebhildebrandt/systeminformation when no cache exists).
    • Checks out the vulnerable release v5.31.6 (da1cbf5, parent of the fix) and the fixed release 5.31.7 (bbfddde).
    • Crafts a controlled /etc/network/interfaces containing a benign source directive (functional control pointing to a real DHCP iface config) and a malicious source directive 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.
  3. Expected evidence:
    • logs/vulnerable_5_31_6_api.logNETWORK_INTERFACES_RESOLVED, marker_exists=true.
    • logs/fixed_5_31_7_api.logNETWORK_INTERFACES_RESOLVED, marker_exists=false.
    • logs/reproduction_steps.logVERDICT: CONFIRMED.
    • repro/runtime_manifest.jsontarget_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/interfaces backup.
  • Key excerpts:

    Vulnerable (5.31.6):

    NETWORK_INTERFACES_RESOLVED
    marker_exists=true
    iface_count=2
    

    Fixed (5.31.7, same crafted source chain):

    NETWORK_INTERFACES_RESOLVED
    marker_exists=false
    iface_count=2
    

    Summary:

    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.7
    
  • Environment: Linux, Node v24.18.0, systeminformation built from source at commits da1cbf5 (v5.31.6) and bbfddde (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 using fs.readFileSync.
  • Defense in depth: For any remaining execSync/exec call sites in the package that interpolate file-system-derived values, switch to execFileSync/readFileSync with arguments passed as array elements, or validate paths against an allow-list.
  • Testing: Add a regression test that places a source directive with shell metacharacters in a controlled interfaces file and asserts no shell side effect occurs, while a benign source chain still parses.

Additional Notes

  • Idempotency: The script was run twice consecutively; both runs produced confirmed=true with identical evidence (vulnerable marker=true, fixed marker=false). The script backs up and restores the original /etc/network/interfaces on every run.
  • Safety: Only a harmless marker oracle (touch of an empty file under /tmp) was used; no destructive payload. The private checkLinuxDCHPInterfaces() helper was never invoked directly — only the public networkInterfaces() 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 benign source parsing.

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.

Event 1/40
0:000:43
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-50289 · REPRO-20
0:03
0:04
0:05
web search
0:07
0:08
0:17
0:17
extract_facts
no facts extracted
0:18
0:18
supportclaim_contract
0:19
0:19
0:19
0:19
0:20
0:20
0:20
0:22
0:22
$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}
0:22
error

Unknown error

0:23
0:26
0:27
$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 | head
1.0s
EXIT=0
bbfddde networkInterfaces() fix unsanitized command (linux)
da1cbf5 5.31.6
87dbb60 fix: smaller issues
0:28
$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]
0:42
0:42
$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.json
fixed 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) => {
0:43
0:43
$cd /data/pruva/project-cache/ebe61b2a-f601-4416-916e-129c2338e243/repo && sed -n '549,575p' lib/network.js
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) => {
      const parts = line.replace(/\s+/g, ' ').trim().split(' ');
08 · How to Fix

How to Fix CVE-2026-50289

Upgrade sebhildebrandt/systeminformation · github to 5.31.7 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-50289 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-50289

What has to be true to exploit CVE-2026-50289?

An attacker needs to influence the contents of /etc/network/interfaces (or a file it 'source's) so a source directive contains shell metacharacters, and then have the application call networkInterfaces() on Linux. Pruva verified command execution as the Node process user against systeminformation <= 5.31.6 with high confidence.

Which versions of systeminformation are affected by CVE-2026-50289, and where is it fixed?

sebhildebrandt/systeminformation versions <= 5.31.6 are affected; the issue is fixed in 5.31.7, which stops interpolating the unquoted source path into a shell command.

How severe is CVE-2026-50289?

It is rated high severity: reachable through a public, commonly used API (networkInterfaces()) and resulting in arbitrary OS command execution as the Node.js process user on Linux.

How can I reproduce CVE-2026-50289?

Download the verified script from this page and run it in an isolated Linux container against systeminformation <= 5.31.6. It plants a crafted source directive and calls networkInterfaces(), showing the injected command run — then confirms 5.31.7 no longer executes it.
11 · References

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.