registry  /  plugin-updater  /  1.5.2

plugin-updater@1.5.2

Plugin lifecycle manager for OpenCode and Claude Code launchers

AI Security Review

scanned 6d ago · by lpm-firewall-ai

No confirmed malware was found, but the updater has a real command-injection/RCE risk because config-derived plugin names, git URLs, branches, and commits are interpolated into shell commands. It also intentionally mutates AI-tool config and startup hooks as part of user-invoked setup.

Static reason
One or more suspicious static signals were detected.; previous stored version diff introduced dangerous source
Trigger
Import/startup activation, earlyLaunch processing of plugins.json/opencode.json, or CLI init/add/run/remove
Impact
A malicious config entry could execute local shell commands during update/install; normal use can install, update, build, deploy, activate, and remove plugins.
Mechanism
plugin manager shelling out to git/npm and writing agent config
Attack narrative
A hostile local config or synced plugin entry could set fields such as gitUrl, branch, commitHash, or npm plugin name to shell metacharacters. On startup or CLI run, plugin-updater passes those values into execSync command strings for git or npm, allowing command execution in the user's environment. This appears to be an unsafe implementation of a legitimate plugin manager rather than deliberate exfiltration or install-time malware.
Rationale
Source inspection shows a legitimate plugin lifecycle manager with no install-time hook or exfiltration, but multiple shell-command construction paths use config-derived values without escaping. That unresolved RCE-class risk warrants a warning rather than a publish block for malware.
Evidence
package.jsondist/index.jsdist/cli.jsdist/git.jsdist/npm.jsdist/deploy.jsdist/syncbridge.jsdist/daemon.jsdist/config.jsdist/env.jsdist/init.js
Network endpoints1
127.0.0.1:<port>/health

Decision evidence

public snapshot
AI called this Suspicious at 86.0% confidence as Critical Vulnerability with medium false-positive risk.
Evidence for warning
  • dist/git.js builds shell commands with pluginName/gitUrl/branch/commitHash from plugins.json and runs execSync
  • dist/npm.js runs npm install/update/uninstall -g with name from opencode.json without shell escaping
  • dist/index.js auto-activates on import unless PLUGIN_UPDATER_LIBRARY_MODE/ACTIVATION is set
  • dist/cli.js init writes Claude SessionStart hook and OpenCode plugin config when user runs init
  • dist/deploy.js imports deployed plugin files and may start declared daemons from cloned plugins
Evidence against
  • package.json has no install/postinstall lifecycle; prepublishOnly is publish-time only
  • CLI/config writes are documented plugin-manager behavior and user-invoked
  • No credential harvesting, broad file enumeration, or exfiltration endpoint found
  • Network behavior is aligned with plugin update/install duties: git, npm, and local daemon health checks
  • Deletion is scoped to configured repos/plugin entries and remove/prune operations
Behavioral surface
Source
ChildProcessCryptoDynamicRequireEnvironmentVarsFilesystemNetworkShell
Supply chain
HighEntropyStringsUrlStrings
ManifestNo manifest risk signals triggered.
scanned 14 file(s), 79.6 KB of source, external domains: 127.0.0.1, github.com, img.shields.io, opencode.ai, www.npmjs.com

Source & flagged code

4 flagged · loading source
dist/npm.jsView file
3import os from "os"; L4: import { execSync } from "child_process"; L5: import { isOpencodeHookInvocation } from "./env.js";
High
Child Process

Package source references child process execution.

dist/npm.jsView on unpkg · L3
82} L83: execSync(`npm install -g ${name}`, { stdio: "pipe", timeout: getNpmTimeoutMs() }); L84: writeLog(`Installed npm plugin: ${name}`);
High
Runtime Package Install

Package source invokes a package manager install command at runtime.

dist/npm.jsView on unpkg · L82
dist/syncbridge.jsView file
29try { L30: const bridge = (await import(pathToFileURL(libPath).href)); L31: if (typeof bridge.syncPlugins !== "function") {
Medium
Dynamic Require

Package source references dynamic require/import behavior.

dist/syncbridge.jsView on unpkg · L29
dist/cli.jsView file
matchType = previous_version_dangerous_delta matchedPackage = plugin-updater@1.5.1 matchedIdentity = npm:cGx1Z2luLXVwZGF0ZXI:1.5.1 similarity = 0.846 summary = stored previous version shares package body but lacks this dangerous source file
Critical
Previous Version Dangerous Delta

This package version adds a dangerous source file absent from the previous stored version.

dist/cli.jsView on unpkg

Findings

1 Critical3 High4 Medium5 Low
CriticalPrevious Version Dangerous Deltadist/cli.js
HighChild Processdist/npm.js
HighShell
HighRuntime Package Installdist/npm.js
MediumDynamic Requiredist/syncbridge.js
MediumNetwork
MediumEnvironment Vars
MediumStructural Risk Force Deep Review
LowNon Install Lifecycle Scripts
LowScripts Present
LowFilesystem
LowHigh Entropy Strings
LowUrl Strings