From 1354903a20a9de339eba7ba0c3291cb42636d534 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 02:19:14 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20path=20traversal=20in=20plugin=20manager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plugin names parsed from untrusted metadata (`# @name`) or extracted from direct URLs were passed unsanitized into `filepath.Join`, allowing path traversal (e.g., writing or deleting arbitrary files outside the plugin directory). This patch sanitizes untrusted input using `filepath.Base(filepath.Clean("/" + name))` *before* joining it with base directories to explicitly restrict it to a single filename. Co-authored-by: himattm <6266621+himattm@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ internal/plugin/manager.go | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000..82edc2f --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-05-23 - Prevent Path Traversal in Plugin Paths +**Vulnerability:** Plugin names parsed from untrusted metadata (`# @name`) or extracted from direct URLs were passed unsanitized into `filepath.Join`, allowing path traversal (e.g., writing or deleting arbitrary files outside the plugin directory). +**Learning:** `filepath.Join` cleans paths but evaluates `../` sequences internally. Thus, combining a base directory with untrusted input containing `../` still allows escaping the base directory. +**Prevention:** Sanitize untrusted input using `filepath.Base(filepath.Clean("/" + name))` *before* joining it with base directories to explicitly restrict it to a single filename. diff --git a/internal/plugin/manager.go b/internal/plugin/manager.go index e242db4..e7d550f 100644 --- a/internal/plugin/manager.go +++ b/internal/plugin/manager.go @@ -31,6 +31,12 @@ func NewManager() *Manager { } } +// sanitizePluginName ensures the given name cannot escape the target directory +// by stripping path components. +func sanitizePluginName(name string) string { + return filepath.Base(filepath.Clean("/" + name)) +} + // Discover finds all installed plugins (both scripts and binaries) func (m *Manager) Discover() ([]Plugin, error) { if err := os.MkdirAll(m.pluginDir, 0755); err != nil { @@ -365,7 +371,7 @@ func (m *Manager) addBinaryPlugin(owner, repo, pluginName string) error { return err } - destPath := filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s", pluginName)) + destPath := filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s", sanitizePluginName(pluginName))) // Check if already installed if err := m.checkExistingPlugin(destPath, pluginName); err != nil { @@ -441,7 +447,7 @@ func (m *Manager) addScriptPlugin(owner, repo, pluginName string) error { return err } - destPath := filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s.sh", meta.Name)) + destPath := filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s.sh", sanitizePluginName(meta.Name))) if err := m.checkExistingPlugin(destPath, meta.Name); err != nil { return err @@ -492,9 +498,9 @@ func (m *Manager) addFromDirectURL(url string) error { var destPath string if isScript { - destPath = filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s.sh", pluginName)) + destPath = filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s.sh", sanitizePluginName(pluginName))) } else { - destPath = filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s", pluginName)) + destPath = filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s", sanitizePluginName(pluginName))) } if err := m.checkExistingPlugin(destPath, pluginName); err != nil { @@ -784,8 +790,8 @@ func (m *Manager) updateScriptPlugin(p Plugin, client *http.Client) error { // Remove uninstalls a plugin (handles both binaries and scripts) func (m *Manager) Remove(name string) error { // Try binary first, then script - binaryPath := filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s", name)) - scriptPath := filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s.sh", name)) + binaryPath := filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s", sanitizePluginName(name))) + scriptPath := filepath.Join(m.pluginDir, fmt.Sprintf("prism-plugin-%s.sh", sanitizePluginName(name))) var path string if _, err := os.Stat(binaryPath); err == nil {