Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-16631

CVE-2026-16631: publint Command Injection RCE Vulnerability

CVE-2026-16631 is a remote code execution flaw in publint versions up to 0.1.4 caused by OS command injection in the package-manager command handler. This article covers technical details, affected versions, and patches.

Published:

CVE-2026-16631 Overview

CVE-2026-16631 is an OS command injection vulnerability in publint versions up to 0.1.4. The flaw resides in the child_process.exec call inside src/node/pack.js, part of the package-manager command handler. An attacker with local access and low privileges can manipulate input passed to the shell command, resulting in arbitrary OS command execution [CWE-77]. The exploit code is publicly available, but the project maintainer notes that use of the package with untrusted input is uncommon. A fix has been committed as adf2d9a09945fc98c85a2520a89f441d78b2dbd8.

Critical Impact

Local attackers can inject arbitrary OS commands through the package-manager parameter processed by child_process.exec, leading to command execution in the context of the user running publint.

Affected Products

  • publint up to and including version 0.1.4
  • @publint/pack package (source file packages/pack/src/node/pack.js)
  • Node.js projects that invoke publint with attacker-controlled packageManager input

Discovery Timeline

  • 2026-07-23 - CVE-2026-16631 published to NVD
  • 2026-07-23 - Last updated in NVD database

Technical Details for CVE-2026-16631

Vulnerability Analysis

The vulnerability exists in the pack function within packages/pack/src/node/pack.js of the publint project. The function builds a shell command string by concatenating the user-supplied packageManager option with the literal pack subcommand, then passes the resulting string to child_process.exec. Because exec spawns a shell, any shell metacharacters present in packageManager are interpreted by the shell rather than treated as literal arguments.

A parallel code path in packages/pack/src/node/pack-as-json.js exhibited the same unsafe pattern. The maintainer resolved both by replacing child_process.exec with the tinyexec library and by routing the package-manager name through a resolvePackageManagerCommand helper that returns an argv array rather than a shell string.

Root Cause

The root cause is unsanitized string concatenation into a shell command. Values such as the destination path and the packageManager identifier were interpolated directly into a command string. Even the quoted --out and --destination arguments could be broken out of by an attacker who controls the path value, because the shell processes the quoting before argument parsing.

Attack Vector

Exploitation requires local access and low privileges. An attacker must be able to influence the packageManager option or the destination path supplied to publint. Successful injection executes arbitrary commands under the identity of the user running the tool.

javascript
// Security patch in packages/pack/src/node/pack.js
// Fix command injection in `@publint/pack` (#238)
-import cp from 'node:child_process'
 import fs from 'node:fs/promises'
 import path from 'node:path'
-import util from 'node:util'
+import { exec } from 'tinyexec'
+import { resolvePackageManagerCommand } from './utils.js'

 /** @type {import('../index.d.ts').pack} */
 export async function pack(dir, opts) {
   const packageManager = opts?.packageManager ?? 'npm'

-  let command = `${packageManager} pack`
-  if (packageManager === 'bun') {
-    command = command.replace('bun', 'bun pm')
-  }
+  const command = resolvePackageManagerCommand(packageManager)
+  command.push('pack')

   // Handle tarball output
   const packDestination = opts?.destination ?? dir
   if (opts?.destination) {
     switch (packageManager) {
       case 'yarn':
-        command += ` --out \"${path.join(packDestination, 'package.tgz')}\"`
+        command.push('--out', path.join(packDestination, 'package.tgz'))
         break
       case 'bun':
-        command += ` --destination \"${packDestination}\"`
+        command.push('--destination', packDestination)
         break

Source: GitHub Commit adf2d9a

Detection Methods for CVE-2026-16631

Indicators of Compromise

  • Unexpected child processes spawned by Node.js processes running publint or @publint/pack.
  • Shell invocations containing metacharacters such as ;, &&, |, or backticks appearing in command lines that begin with a package-manager binary.
  • Modifications to files or new outbound network connections initiated shortly after a publint invocation.

Detection Strategies

  • Audit package.json scripts, CI configurations, and automation code for calls to publint that receive dynamic packageManager or destination values.
  • Search dependency manifests for publint versions at or below 0.1.4 across build environments.
  • Review process telemetry for node parents spawning /bin/sh -c with concatenated package-manager arguments.

Monitoring Recommendations

  • Enable process-creation logging on developer workstations and CI runners that execute publint.
  • Alert on shell interpreters launched as children of Node.js processes when the command string contains shell metacharacters.
  • Track installations of the publint package through software composition analysis to catch vulnerable versions entering the build pipeline.

How to Mitigate CVE-2026-16631

Immediate Actions Required

  • Upgrade publint and @publint/pack to a release that includes commit adf2d9a09945fc98c85a2520a89f441d78b2dbd8.
  • Never pass untrusted or user-supplied values as the packageManager or destination option to publint.
  • Restrict who can modify CI configurations and build scripts that invoke publint.

Patch Information

The fix is provided in commit adf2d9a09945fc98c85a2520a89f441d78b2dbd8 via pull request #238, tracked in issue #236. The patch replaces node:child_process.exec with tinyexec and introduces resolvePackageManagerCommand, which returns an argv array so arguments are no longer interpreted by a shell.

Workarounds

  • Pin the packageManager option to a hardcoded, allow-listed value such as npm, pnpm, yarn, or bun.
  • Validate any destination path against an allow-list and reject values containing shell metacharacters until the upgrade is applied.
  • Run publint in an isolated environment with least privilege to limit the impact of successful injection.
bash
# Configuration example: upgrade publint to a patched release
npm install publint@latest --save-dev
npm ls publint

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.