Skip to content

Commit fd99a70

Browse files
committed
sec: prevent directory traversal in module_path
Signed-off-by: habeck <habeck@us.ibm.com>
1 parent 14a0893 commit fd99a70

2 files changed

Lines changed: 16 additions & 7 deletions

File tree

cpex/framework/isolated/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(self, config: PluginConfig) -> None:
4646
self.plugin_path = cache_root
4747
if not cache_root.exists():
4848
raise RuntimeError("plugin script_path does not exist")
49-
self.cache_dir = cache_root / ".cpex" / "venv_cache"
49+
self.cache_dir: Path = cache_root / ".cpex" / "venv_cache"
5050
self.cache_dir.mkdir(parents=True, exist_ok=True)
5151

5252
def _compute_requirements_hash(self, requirements_file: str) -> str:

cpex/framework/isolated/worker.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,15 @@ class TaskProcessor:
3636

3737
config_hash: str
3838
module_path_hash: str
39-
hook_ref: HookRef | None
40-
executor: PluginExecutor | None
39+
hook_ref: HookRef
40+
executor: PluginExecutor
4141

4242
def __init__(self) -> None:
4343
"""Initialize defaults."""
4444
hasher = hashlib.sha256()
4545
hasher.update(b"")
4646
self.config_hash = hasher.hexdigest()
4747
self.module_path_hash = self.config_hash
48-
self.hook_ref = None
49-
self.executor = None
5048

5149
def compute_hash(self, json_config_or_module_path: str):
5250
"""Compute the hash of the supplied string"""
@@ -105,8 +103,19 @@ async def process_task(task_data, tp: TaskProcessor):
105103
json_config = task_data.get("config")
106104
config_raw = json.loads(json_config)
107105
module_path: str = task_data.get("script_path")
106+
107+
# Security: Validate module_path to prevent directory traversal
108+
if ".." in module_path or module_path.startswith("/"):
109+
raise ValueError(f"Invalid module_path: '{module_path}' - path traversal not allowed")
110+
108111
if tp.module_path_hash != tp.compute_hash(module_path) or tp.config_hash != tp.compute_hash(json_config):
109-
sys.path.append(str(Path(module_path).resolve()))
112+
# pull the resolved plugin path and only add the module path if it has the same root
113+
path = Path(module_path).resolve()
114+
resolved_module_path = str(path)
115+
if path.exists():
116+
sys.path.append(resolved_module_path)
117+
else:
118+
raise RuntimeError(f"plugin module_path '{resolved_module_path}' does not exist.")
110119
config = get_proper_config(config_raw.get("name"), module_path)
111120
hook_type = task_data.get(HOOK_TYPE)
112121
cls_name: str = task_data.get("class_name")
@@ -128,7 +137,7 @@ async def process_task(task_data, tp: TaskProcessor):
128137
state=context.get("state"), global_context=context.get("global_context"), metadata=context.get("metadata")
129138
)
130139
result = await tp.executor.execute_plugin(
131-
hookref=tp.hook_ref,
140+
hook_ref=tp.hook_ref,
132141
payload=task_data.get("payload"),
133142
local_context=plugin_context,
134143
violations_as_exceptions=False,

0 commit comments

Comments
 (0)