From af7b22d342a47997c26ff54130f877409aec9aa4 Mon Sep 17 00:00:00 2001 From: Guzman Alvarez Date: Fri, 8 May 2026 10:20:04 +0200 Subject: [PATCH] fix: create parent directory before moving workflow clone The workflow clone code in both hydrate.sh (init container) and workflow.py (runtime endpoint) failed to create /workspace/workflows/ before moving the cloned repo into it. The subpath-found branch had mkdir -p but the subpath-fallback and no-subpath branches did not. Also fixes: - Path traversal vulnerability in workflow.py subpath handling - Missing chown/chmod for /workspace/workflows in hydrate.sh --- .../ambient-runner/ambient_runner/endpoints/workflow.py | 7 ++++++- components/runners/state-sync/hydrate.sh | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) mode change 100644 => 100755 components/runners/state-sync/hydrate.sh diff --git a/components/runners/ambient-runner/ambient_runner/endpoints/workflow.py b/components/runners/ambient-runner/ambient_runner/endpoints/workflow.py index 133807fa0..6c90ace0a 100644 --- a/components/runners/ambient-runner/ambient_runner/endpoints/workflow.py +++ b/components/runners/ambient-runner/ambient_runner/endpoints/workflow.py @@ -139,7 +139,10 @@ async def clone_workflow_at_runtime( return False, "" if subpath: - subpath_full = temp_dir / subpath + subpath_full = (temp_dir / subpath).resolve() + if not subpath_full.is_relative_to(temp_dir): + logger.error(f"Subpath '{subpath}' escapes clone directory") + return False, "" if subpath_full.exists() and subpath_full.is_dir(): if workflow_final.exists(): shutil.rmtree(workflow_final) @@ -149,10 +152,12 @@ async def clone_workflow_at_runtime( logger.warning(f"Subpath '{subpath}' not found, using entire repo") if workflow_final.exists(): shutil.rmtree(workflow_final) + workflow_final.parent.mkdir(parents=True, exist_ok=True) shutil.move(str(temp_dir), str(workflow_final)) else: if workflow_final.exists(): shutil.rmtree(workflow_final) + workflow_final.parent.mkdir(parents=True, exist_ok=True) shutil.move(str(temp_dir), str(workflow_final)) logger.info(f"Workflow '{workflow_name}' ready at {workflow_final}") diff --git a/components/runners/state-sync/hydrate.sh b/components/runners/state-sync/hydrate.sh old mode 100644 new mode 100755 index 12bf33334..c568a1289 --- a/components/runners/state-sync/hydrate.sh +++ b/components/runners/state-sync/hydrate.sh @@ -340,11 +340,13 @@ if [ -n "$ACTIVE_WORKFLOW_GIT_URL" ] && [ "$ACTIVE_WORKFLOW_GIT_URL" != "null" ] echo " Available paths in repo:" find "$WORKFLOW_TEMP" -maxdepth 3 -type d | head -10 echo " Using entire repo instead" + mkdir -p "$(dirname "$WORKFLOW_FINAL")" mv "$WORKFLOW_TEMP" "$WORKFLOW_FINAL" echo " ✓ Workflow ready at /workspace/workflows/${WORKFLOW_NAME}" fi else # No subpath - use entire repo + mkdir -p "$(dirname "$WORKFLOW_FINAL")" mv "$WORKFLOW_TEMP" "$WORKFLOW_FINAL" echo " ✓ Workflow ready at /workspace/workflows/${WORKFLOW_NAME}" fi @@ -467,9 +469,13 @@ else echo "No git repo state backup found" fi -# Set permissions on repos after restore (repos may have been cloned or restored) +# Set permissions on repos and workflows after restore chown -R 1001:0 /workspace/repos 2>/dev/null || true chmod -R 777 /workspace/repos 2>/dev/null || true +if [ -d /workspace/workflows ]; then + chown -R 1001:0 /workspace/workflows 2>/dev/null || true + chmod -R 777 /workspace/workflows 2>/dev/null || true +fi echo "=========================================" echo "Workspace initialized successfully"