Skip to content

fix(pluginfw): validate dependency names on plugin uninstall + 3.3.9 changelog - #8358

Merged
JohnMcLear merged 3 commits into
developfrom
release/3.3.9-g9xm
Oct 18, 2026
Merged

JohnMcLear merged 3 commits into
developfrom
release/3.3.9-g9xm

Conversation

@JohnMcLear

Copy link
Copy Markdown
Member

This fixes GHSA-g9xm-3pqr-wcph and adds the 3.3.9 CHANGELOG entry. It goes in ahead of the release.

GHSA-92hr-gmr6-h8cp added dependency-name checks to the plugin installer, but only on install. On uninstall, uninstallDependency, removeSubDependency, unlinkSubDependency and unlinkDependency still passed the raw package.json key to rmSync/unlinkSync. A plugin with a dependency named like zzz/../../.. could make an admin's uninstall delete a directory outside the plugin folder.

All of these paths now go through resolveDependencyPath() in the new pluginfw/dependencyPath.ts. It validates the name and checks that the resolved path stays inside the base directory. Invalid names are logged and skipped, and never fall back to the raw name.

Reported by @baeseungwon1010.

Tests: new vitest unit tests for dependencyPath, plus mocha regressions in tests/backend/specs/LinkInstaller.ts covering uninstall, sub-dependency removal and unlink. The full vitest suite passes (842), and so does the LinkInstaller spec (7).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y5YroE8t1VhXCVTjJhx2kU

JohnMcLear and others added 2 commits October 18, 2026 12:23
GHSA-92hr-gmr6-h8cp validated plugin dependency names before they
were used to build filesystem paths, but only on the install side.
The uninstall side (removeSubDependency, uninstallDependency,
unlinkSubDependency, unlinkDependency) still joined the raw
package.json key onto pluginInstallPath / node_modules and passed it
to rmSync({recursive, force}) or unlinkSync. A plugin declaring a
dependency such as "zzz/../../../../tmp/x" with a GitHub-spec version
got past live-plugin-manager, and uninstalling it deleted a directory
outside the plugin folder.

Move the name check into dependencyPath.ts, add a containment check
(resolveDependencyPath) and use it at every uninstall/unlink sink.
Names whose segments start with "." are now rejected as well.

Reported by @baeseungwon1010 (GHSA-g9xm-3pqr-wcph).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dDCHnxem1jJTYtTcY4Hqe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y5YroE8t1VhXCVTjJhx2kU
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Prevent path traversal during plugin uninstall

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Validate dependency names and resolved paths before plugin uninstall removes files or symlinks.
• Skip invalid names instead of using raw dependency keys in filesystem operations.
• Add security regressions and document the fix in the 3.3.9 changelog.
Diagram

graph TD
  A["Plugin dependencies"] --> B["Uninstall paths"] --> C["Resolve dependency"] --> D{"Valid and contained?"} -->|Yes| E["Filesystem cleanup"]
  D -->|No| F["Log and skip"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Validate only when plugins are installed
  • ➕ Avoids repeated validation during uninstall.
  • ➖ Does not protect removal paths from existing or otherwise malformed plugin metadata.

Recommendation: Keep validation at the filesystem removal paths: it protects the operation regardless of how dependency metadata entered the system. The shared resolver also avoids falling back to an unsafe raw name.

Files changed (5) +183 / -17

Bug fix (2) +50 / -17
LinkInstaller.tsGuard dependency removal and unlink paths +29/-17

Guard dependency removal and unlink paths

• Routes uninstall, sub-dependency removal, and unlink operations through the shared path resolver, logging and skipping invalid names. Reuses shared name validation on install-side linking.

src/static/js/pluginfw/LinkInstaller.ts

dependencyPath.tsAdd shared dependency path validation +21/-0

Add shared dependency path validation

• Validates plain and scoped dependency names, rejecting traversal and dot-prefixed segments. Returns a path only when it resolves inside the supplied base directory.

src/static/js/pluginfw/dependencyPath.ts

Tests (2) +106 / -0
dependencyPath.test.tsTest dependency name and path validation +40/-0

Test dependency name and path validation

• Covers accepted package names and rejected traversal, malformed, absolute, dot-prefixed, and non-string inputs.

src/tests/backend-new/specs/dependencyPath.test.ts

LinkInstaller.tsRegress traversal attempts against uninstall and unlink +66/-0

Regress traversal attempts against uninstall and unlink

• Uses the LinkInstaller instance to verify that malicious dependency keys cannot delete an external directory or unlink an external file.

src/tests/backend/specs/LinkInstaller.ts

Documentation (1) +27 / -0
CHANGELOG.mdDocument the 3.3.9 release +27/-0

Document the 3.3.9 release

• Adds the 3.3.9 security advisory, including the plugin-uninstall traversal fix and its impact. Also summarizes features and fixes included in the release.

CHANGELOG.md

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can move or hide resolved findings to focus on what still needs action

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

On Windows CI os.tmpdir() is on C: while the checkout is on D:, so no
relative traversal path to the victim exists and the precondition assert
failed. Create the victims next to the plugin folder instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y5YroE8t1VhXCVTjJhx2kU
@JohnMcLear
JohnMcLear merged commit d15d899 into develop Oct 18, 2026
38 checks passed
@JohnMcLear
JohnMcLear deleted the release/3.3.9-g9xm branch October 18, 2026 11:42
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.

1 participant