Skip to content

fix: create parent directory before moving workflow clone#1531

Open
guzalv wants to merge 1 commit intoambient-code:mainfrom
guzalv:fix/workflow-clone-mkdir
Open

fix: create parent directory before moving workflow clone#1531
guzalv wants to merge 1 commit intoambient-code:mainfrom
guzalv:fix/workflow-clone-mkdir

Conversation

@guzalv
Copy link
Copy Markdown

@guzalv guzalv commented May 8, 2026

Summary

  • mkdir -p fix: 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 already had mkdir -p but the subpath-fallback and no-subpath branches did not, causing mv / shutil.move to fail silently.
  • Path traversal fix (workflow.py): Validates that the resolved subpath stays within the clone directory, preventing ../ traversal in user-supplied subpath input.
  • Permissions fix (hydrate.sh): Adds chown/chmod for /workspace/workflows alongside the existing /workspace/repos permissions block, so the runner container (user 1001) can read cloned workflow files.

Changed files

File Change
components/runners/state-sync/hydrate.sh Add mkdir -p before mv in subpath-fallback and no-subpath branches; add chown/chmod for /workspace/workflows
components/runners/ambient-runner/ambient_runner/endpoints/workflow.py Add mkdir -p before shutil.move in subpath-fallback and no-subpath branches; add path traversal validation for subpath

Test plan

  • Start a session with a custom workflow (no subpath) — workflow files should appear in /workspace/workflows/<name>/
  • Start a session with a custom workflow + subpath — subpath extraction should work
  • Verify subpath with ../ sequences is rejected with an error log
  • Verify /workspace/workflows/ has correct ownership (1001:0) after init

Summary by CodeRabbit

  • New Features

    • Added support for dynamically updating and switching active workflows at runtime without restarting the application.
  • Bug Fixes

    • Enhanced security by preventing path traversal vulnerabilities in workflow repository handling.
    • Improved workflow directory restoration and permissions management during initialization.

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
@netlify
Copy link
Copy Markdown

netlify Bot commented May 8, 2026

Deploy Preview for cheerful-kitten-f556a0 ready!

Name Link
🔨 Latest commit af7b22d
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/69fd9cce3a2e3a00087868ec
😎 Deploy Preview https://deploy-preview-1531--cheerful-kitten-f556a0.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

📝 Walkthrough

Walkthrough

Adds path traversal prevention to runtime workflow switching by validating that subpath stays within the temporary clone directory. Consolidates file operations using shutil.move() and simplifies the shell script's workflow extraction fallback to always move the temp clone, with added post-restore permissions setup for workflow directories.

Changes

Secure Runtime Workflow Switching

Layer / File(s) Summary
Path Validation & Security
components/runners/ambient-runner/ambient_runner/endpoints/workflow.py
Resolves subpath to absolute path and rejects it if it escapes the temporary clone directory via is_relative_to() check, preventing path traversal attacks.
File Operations
components/runners/ambient-runner/ambient_runner/endpoints/workflow.py
Unifies file operations to use shutil.move(str(temp_dir), str(workflow_final)) for both subpath and non-subpath cases, with adjusted directory creation/cleanup logic.
Hydration Integration
components/runners/state-sync/hydrate.sh
Simplifies workflow extraction fallback to directly move temp clone into final destination when ACTIVE_WORKFLOW_PATH is not found. Adds conditional ownership/permissions setup for /workspace/workflows post-S3 restore.
🚥 Pre-merge checks | ✅ 8
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title follows Conventional Commits format (fix type with scope and description) and accurately describes the primary change: creating parent directories before moving workflow clones.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Performance And Algorithmic Complexity ✅ Passed No performance regressions. Path validation and mkdir ops are O(1). File operations scale linearly with file count (unavoidable). No N+1 patterns, unbounded growth, or expensive loops.
Security And Secret Handling ✅ Passed Path traversal validation added (is_relative_to check). Tokens not logged. Auth middleware enforced. No injection vulnerabilities, hardcoded secrets, or data leakage detected.
Kubernetes Resource Safety ✅ Passed PR modifies only application code (Python + shell script), no Kubernetes manifests. K8s Resource Safety check is not applicable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
components/runners/ambient-runner/ambient_runner/endpoints/workflow.py (2)

47-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact credentials before logging workflow URL

Line 47 logs git_url directly; URLs can carry embedded tokens/userinfo and leak secrets into logs. Log a redacted value.

Suggested fix
-    logger.info(f"Workflow change request: {git_url}@{branch} (path: {path})")
+    logger.info(
+        "Workflow change request: %s@%s (path: %s)",
+        redact_secrets(git_url),
+        branch,
+        path,
+    )

As per coding guidelines, components/runners/ambient-runner/**/*.py: - Check subprocess handling, timeout management, and that secrets are not logged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/runners/ambient-runner/ambient_runner/endpoints/workflow.py` at
line 47, The log call at logger.info(f"Workflow change request:
{git_url}@{branch} (path: {path})") exposes potential credentials in git_url;
before logging, sanitize git_url by parsing and removing any userinfo
(username:password@) or token fragments so only host/path remain, then log the
redacted URL along with branch and path (update the logger.info call near the
Workflow change request in ambient_runner/endpoints/workflow.py to use the
sanitized variable instead of raw git_url).

122-136: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a timeout to git clone subprocess wait

await process.communicate() (Line 135) has no timeout, so a hung clone can block this request and hold _workflow_change_lock indefinitely. Wrap it with asyncio.wait_for(...) and kill on timeout.

Suggested fix
-        stdout, stderr = await process.communicate()
+        try:
+            stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=60)
+        except asyncio.TimeoutError:
+            process.kill()
+            await process.communicate()
+            logger.error("Failed to clone workflow: git clone timed out after 60s")
+            return False, ""

As per coding guidelines, components/runners/ambient-runner/**/*.py: - Check subprocess handling, timeout management, and that secrets are not logged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/runners/ambient-runner/ambient_runner/endpoints/workflow.py`
around lines 122 - 136, The git clone subprocess uses await
process.communicate() without a timeout which can hang and hold
_workflow_change_lock; wrap the communicate call in asyncio.wait_for with a
sensible timeout constant (e.g. CLONE_TIMEOUT) and if wait_for raises
asyncio.TimeoutError terminate the subprocess (process.kill(), await
process.communicate() to drain) and raise/return a timeout error; apply this
change inside the function that spawns the subprocess (the code that creates
`process` and calls `communicate`) and ensure you do not log any secrets or the
full clone_url when reporting the timeout.
components/runners/state-sync/hydrate.sh (1)

327-336: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Block path traversal in workflow subpath extraction

Line 328 concatenates WORKFLOW_PATH directly, and Line 332 only checks -d; ../ segments can escape "$WORKFLOW_TEMP" and copy host/container directories (e.g., /etc) into the workspace. Add canonical-path confinement before copying.

Suggested fix
         if [ -n "$WORKFLOW_PATH" ]; then
-            SUBPATH_FULL="$WORKFLOW_TEMP/$WORKFLOW_PATH"
-            echo "  Checking for subpath: $SUBPATH_FULL"
-            ls -la "$SUBPATH_FULL" 2>&1 || echo "  Subpath does not exist"
-
-            if [ -d "$SUBPATH_FULL" ]; then
+            WORKFLOW_TEMP_REAL="$(cd "$WORKFLOW_TEMP" && pwd -P)"
+            SUBPATH_FULL="$(cd "$WORKFLOW_TEMP" 2>/dev/null && cd "$WORKFLOW_PATH" 2>/dev/null && pwd -P)"
+            echo "  Checking for subpath: $WORKFLOW_PATH"
+
+            if [ -n "$SUBPATH_FULL" ] && [[ "$SUBPATH_FULL" == "$WORKFLOW_TEMP_REAL"/* ]] && [ -d "$SUBPATH_FULL" ]; then
                 echo "  Extracting subpath: $WORKFLOW_PATH"
                 mkdir -p "$(dirname "$WORKFLOW_FINAL")"
                 cp -r "$SUBPATH_FULL" "$WORKFLOW_FINAL"
                 rm -rf "$WORKFLOW_TEMP"
                 echo "  ✓ Workflow extracted from subpath to /workspace/workflows/${WORKFLOW_NAME}"
             else
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/runners/state-sync/hydrate.sh` around lines 327 - 336, The code
currently builds SUBPATH_FULL by concatenating WORKFLOW_PATH into WORKFLOW_TEMP
and copies it without ensuring it stays inside the temp dir, allowing path
traversal; before using SUBPATH_FULL in the -d check and cp, resolve a canonical
path (via realpath or readlink -f) for both WORKFLOW_TEMP and SUBPATH_FULL
(e.g., compute WORKFLOW_TEMP_REAL and SUBPATH_REAL), verify SUBPATH_REAL starts
with WORKFLOW_TEMP_REAL (string-prefix check) and only proceed to mkdir/cp/rm
when that containment check passes; otherwise log an error and abort so
WORKFLOW_PATH cannot escape the temp directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@components/runners/ambient-runner/ambient_runner/endpoints/workflow.py`:
- Line 47: The log call at logger.info(f"Workflow change request:
{git_url}@{branch} (path: {path})") exposes potential credentials in git_url;
before logging, sanitize git_url by parsing and removing any userinfo
(username:password@) or token fragments so only host/path remain, then log the
redacted URL along with branch and path (update the logger.info call near the
Workflow change request in ambient_runner/endpoints/workflow.py to use the
sanitized variable instead of raw git_url).
- Around line 122-136: The git clone subprocess uses await process.communicate()
without a timeout which can hang and hold _workflow_change_lock; wrap the
communicate call in asyncio.wait_for with a sensible timeout constant (e.g.
CLONE_TIMEOUT) and if wait_for raises asyncio.TimeoutError terminate the
subprocess (process.kill(), await process.communicate() to drain) and
raise/return a timeout error; apply this change inside the function that spawns
the subprocess (the code that creates `process` and calls `communicate`) and
ensure you do not log any secrets or the full clone_url when reporting the
timeout.

In `@components/runners/state-sync/hydrate.sh`:
- Around line 327-336: The code currently builds SUBPATH_FULL by concatenating
WORKFLOW_PATH into WORKFLOW_TEMP and copies it without ensuring it stays inside
the temp dir, allowing path traversal; before using SUBPATH_FULL in the -d check
and cp, resolve a canonical path (via realpath or readlink -f) for both
WORKFLOW_TEMP and SUBPATH_FULL (e.g., compute WORKFLOW_TEMP_REAL and
SUBPATH_REAL), verify SUBPATH_REAL starts with WORKFLOW_TEMP_REAL (string-prefix
check) and only proceed to mkdir/cp/rm when that containment check passes;
otherwise log an error and abort so WORKFLOW_PATH cannot escape the temp
directory.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a82faba0-f4e5-4c42-9a5a-ccc6e23926c8

📥 Commits

Reviewing files that changed from the base of the PR and between 070520c and af7b22d.

📒 Files selected for processing (2)
  • components/runners/ambient-runner/ambient_runner/endpoints/workflow.py
  • components/runners/state-sync/hydrate.sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant