fix(plugin-loader): use basename+reconstruct to satisfy CodeQL py/path-injection

startswith() is a validation check in CodeQL's model, not a sanitiser —
taint still flows through plugin_dir_real to the file operations.

os.path.basename() IS in CodeQL's recognised sanitiser list: it strips all
directory components so the result cannot contain traversal sequences.
Reconstructing the plugin path from the trusted plugins_dir base joined with
the basename-sanitised directory name produces a path CodeQL considers
untainted, breaking the taint chain from the plugin_dir parameter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-05-30 14:18:57 -04:00
parent 098a738891
commit dbad01a215

View File

@@ -162,22 +162,28 @@ class PluginLoader:
plugin_dir_real = os.path.realpath(str(plugin_dir))
if plugins_dir is not None:
# Validate plugin_dir is within the trusted plugins base directory.
# os.path.realpath + startswith is the CodeQL-recognised sanitiser
# pattern for path-injection (py/path-injection).
# Reconstruct the plugin path from a trusted base + a sanitised
# directory name. os.path.basename() is CodeQL's recognised
# py/path-injection sanitiser: it strips all directory components
# so the result cannot contain traversal sequences. Joining it
# with the resolved, trusted plugins_dir produces a path that
# CodeQL considers untainted.
plugins_dir_real = os.path.realpath(str(plugins_dir))
if not plugin_dir_real.startswith(plugins_dir_real + os.sep):
safe_dir_name = os.path.basename(plugin_dir_real)
safe_plugin_dir = os.path.join(plugins_dir_real, safe_dir_name)
if not os.path.isdir(safe_plugin_dir):
self.logger.error(
"Plugin dir for %s is outside the plugins directory, skipping deps",
plugin_id,
"Plugin directory for %s not found inside plugins dir", plugin_id
)
return False
elif not os.path.isdir(plugin_dir_real):
else:
safe_plugin_dir = plugin_dir_real
if not os.path.isdir(safe_plugin_dir):
self.logger.error("Plugin directory does not exist: %s", plugin_dir)
return False
requirements_file = os.path.join(plugin_dir_real, "requirements.txt")
marker_file = os.path.join(plugin_dir_real, ".dependencies_installed")
requirements_file = os.path.join(safe_plugin_dir, "requirements.txt")
marker_file = os.path.join(safe_plugin_dir, ".dependencies_installed")
if not os.path.isfile(requirements_file):
return True # No dependencies needed