mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 08:48:05 +00:00
fix(dev-server): derive plugin dir from trusted directory listings
CodeQL's barrier-guard recognition doesn't see a startswith check inside an any() comprehension, so the normalize-and-prefix approach still flagged. Break the taint outright instead: after lookup, re-derive the directory by enumerating the search dirs (iterdir) and matching by path equality — the Path used for all downstream file access is built solely from trusted listings, never from request input. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FqzC1nzTWL4kaqgMaQZFam
This commit is contained in:
+22
-10
@@ -256,24 +256,36 @@ def _render_once(plugin_id, plugin_dir, manifest, config, mock_data, width, heig
|
||||
}
|
||||
|
||||
|
||||
def _trusted_plugin_dir(plugin_dir: Path) -> Optional[Path]:
|
||||
"""Re-derive a plugin directory from the search dirs' own listings.
|
||||
|
||||
Path-injection barrier: the returned Path is constructed purely from
|
||||
trusted directory enumeration (``iterdir``) — request-derived strings
|
||||
never enter its construction — so a crafted plugin id can never make
|
||||
downstream file access leave the plugin search dirs. Comparison is by
|
||||
path equality, deliberately without symlink resolution (dev plugins
|
||||
are commonly symlinked into plugins/).
|
||||
"""
|
||||
wanted = Path(os.path.normpath(str(plugin_dir)))
|
||||
for search_dir in get_search_dirs():
|
||||
if not search_dir.is_dir():
|
||||
continue
|
||||
for entry in search_dir.iterdir():
|
||||
if entry.is_dir() and entry == wanted:
|
||||
return entry
|
||||
return None
|
||||
|
||||
|
||||
def _parse_render_request(data):
|
||||
"""Shared /api/render* request prep. Returns (plugin_dir, manifest, config,
|
||||
mock_data, skip_update) or raises ValueError with a client message."""
|
||||
plugin_id = data['plugin_id']
|
||||
plugin_dir = find_plugin_dir(plugin_id)
|
||||
if plugin_dir:
|
||||
plugin_dir = _trusted_plugin_dir(plugin_dir)
|
||||
if not plugin_dir:
|
||||
raise LookupError(f'Plugin not found: {plugin_id}')
|
||||
|
||||
# Path-injection barrier: normalize (no symlink resolution — dev plugins
|
||||
# are commonly symlinked into plugins/) and require the directory to sit
|
||||
# inside one of the plugin search dirs before any file access.
|
||||
normalized = os.path.normpath(str(plugin_dir))
|
||||
allowed_roots = [os.path.normpath(str(d)) for d in get_search_dirs()]
|
||||
if not any(normalized == root or normalized.startswith(root + os.sep)
|
||||
for root in allowed_roots):
|
||||
raise LookupError(f'Plugin not found: {plugin_id}')
|
||||
plugin_dir = Path(normalized)
|
||||
|
||||
manifest_path = plugin_dir / 'manifest.json'
|
||||
with open(manifest_path, 'r') as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
Reference in New Issue
Block a user