Skip to content

Comments

Added RCE validation to both devserver.py and dev_server_manager.py#152

Closed
syphonetic wants to merge 2 commits intoAutoForgeAI:masterfrom
syphonetic:master
Closed

Added RCE validation to both devserver.py and dev_server_manager.py#152
syphonetic wants to merge 2 commits intoAutoForgeAI:masterfrom
syphonetic:master

Conversation

@syphonetic
Copy link
Contributor

@syphonetic syphonetic commented Feb 3, 2026

Added a validation function (validate_custom_command_strict) in devserver.py
If raw commands were retrieved from request body to request.command, raise error code 400.
Called validate_custom_command_strict before set_dev_command

Replaced start function in dev_server_manager.py to filter for any command injection attempts.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added dev server lifecycle management endpoints (status monitoring, start/stop control)
    • Added dev server configuration endpoints for dynamic settings updates
  • Improvements

    • Enhanced command validation for improved safety and robustness
    • Improved error messages for missing configurations or invalid commands
    • Better process management and cleanup handling

Refactor dev server management to improve command execution and security checks. Introduce lock file handling and command validation enhancements.
Refactor dev server API to enhance security and command validation. Added logging and improved command handling.
@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Introduces comprehensive security validation for dev server control endpoints. Adds command validation layer blocking shell injection and dangerous interpreters, replaces shell-based execution with direct process spawning, and implements project-scoped dev server management with HTTP error handling for missing projects or directories.

Changes

Cohort / File(s) Summary
Router & API Layer
server/routers/devserver.py
Added 5 lifecycle endpoints (get/start/stop dev server, get/update config) with project validation, command allowlisting, and strict command validation (validate_custom_command_strict) blocking shells and dangerous interpreters. Introduced get_project_dir and get_project_devserver_manager helpers, ALLOWED_RUNNERS constant, and HTTP 404 flows for missing projects/directories.
Execution & Process Management
server/services/dev_server_manager.py
Replaced shell-based execution with shlex argument parsing and direct Popen calls. Added pre-start validations blocking sh/bash/zsh/cmd/powershell. Enhanced lock management, process tree termination, orphan lock cleanup, and asynchronous output streaming with UTC timestamp recording.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Router as Dev Server Router
    participant Validator as Command Validator
    participant Manager as Dev Server Manager
    participant Process as OS Process

    Client->>Router: start_devserver(project_name, request)
    Router->>Router: validate project exists
    Router->>Router: get_project_dir(project_name)
    Router->>Validator: validate_dev_command(command, project_dir)
    alt Command Valid
        Validator-->>Router: ✓ Valid
        Router->>Manager: start(project_name, validated_command)
        Manager->>Manager: strip & parse command (shlex)
        Manager->>Manager: block shell runners (sh/bash/zsh/cmd/powershell)
        Manager->>Manager: construct argv (direct executable)
        Manager->>Process: spawn via Popen (no shell)
        Manager->>Manager: create lock file + record PID
        Manager-->>Router: DevServerActionResponse (started)
        Router-->>Client: 200 OK
    else Command Invalid
        Validator-->>Router: ✗ Blocked (injection/disallowed)
        Router-->>Client: 400 Bad Request
    else Project Missing
        Router-->>Client: 404 Not Found
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Securing dev servers, line by line,
No shells sneaking in—our guards align!
Process spawning safe, no injection's way,
These changes make sure the bad guys won't play! 🛡️✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main security-focused changes across both files: RCE (Remote Code Execution) validation mechanisms were added to devserver.py and dev_server_manager.py.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


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

@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.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
server/routers/devserver.py (1)

690-742: ⚠️ Potential issue | 🔴 Critical

Duplicate update_devserver_config function definition.

This function is already defined at lines 627-679. FastAPI will use the second definition, but this duplication is clearly unintentional and results from the file corruption.

🤖 Fix all issues with AI agents
In `@server/routers/devserver.py`:
- Around line 682-687: There is a duplicate, unreachable return that constructs
DevServerConfigResponse; locate the function that returns a
DevServerConfigResponse (look for the earlier return that already returns the
response) and remove the second/duplicate return DevServerConfigResponse(...)
block so only the single, intended return remains (or consolidate any differing
fields into the first return if needed).
- Around line 78-133: The uvicorn flag parsing and mode checks in
validate_custom_command_strict are case-sensitive and don't accept --flag=value
forms; update the function to use the already-created lowered list for all
comparisons (e.g., compare lowered[1:3] to ["-m","uvicorn"] and use lowered for
base checks) and normalize flags when iterating by splitting on '=' and
lowercasing the flag name before membership tests (e.g., flag_name =
a.split("=",1)[0].lower()) so that allowed_flags (make it a lowercased set)
accepts both "--port" and "--port=8000" and comparisons like "-m"/"uvicorn" are
case-insensitive.
- Around line 309-351: The file contains a duplicated second copy of the module
(starting at the malformed line "config = get_project_config(project_dir)""" and
repeating the module docstring, imports, constants and route handlers) which
causes a syntax error and multiple duplicate definitions; remove the entire
duplicated block after that malformed line so only one module-level docstring,
one set of imports, one ALLOWED_RUNNERS, and single definitions of
get_project_dir, validate_custom_command_strict, get_devserver_config,
update_devserver_config, and route registrations remain, and ensure the
truncated get_devserver_config statement is completed/removed as needed and
imports/helpers (extract_commands, get_effective_commands, is_command_allowed,
get_devserver_manager, project_config functions, _get_project_path,
validate_project_name) are only declared once.
🧹 Nitpick comments (5)
server/services/dev_server_manager.py (4)

28-28: Unused imports from security module.

The imports extract_commands, get_effective_commands, and is_command_allowed are not used anywhere in this file. The command validation using these functions happens in server/routers/devserver.py instead.

🧹 Remove unused imports
-from security import extract_commands, get_effective_commands, is_command_allowed
+# Security validation is handled in the router layer (server/routers/devserver.py)

312-315: Consider expanding the dangerous operators list.

The current blocklist catches common shell chaining operators, but file redirection operators (>, <, >>, 2>) could also be used maliciously to overwrite files or exfiltrate data.

🛡️ Add file redirection operators
         # SECURITY: block shell operators/metacharacters (defense-in-depth)
-        dangerous_ops = ["&&", "||", ";", "|", "`", "$("]
+        dangerous_ops = ["&&", "||", ";", "|", "`", "$(", ">", "<", ">>", "2>"]
         if any(op in command for op in dangerous_ops):
             return False, "Shell operators are not allowed in dev server command"

352-352: datetime.utcnow() is deprecated in Python 3.12+.

Per the coding guidelines requiring Python 3.11+, this deprecation warning will appear in newer Python versions. Use timezone-aware datetime instead.

♻️ Use timezone-aware datetime
+from datetime import datetime, timezone
-from datetime import datetime
 ...
-            self.started_at = datetime.utcnow()
+            self.started_at = datetime.now(timezone.utc)

354-360: Lock creation timing could leave orphaned process on failure.

The lock file is created after the process starts. If _create_lock() fails (e.g., due to a disk error), the process will be running but untracked. Consider wrapping this in try/except to terminate the process if lock creation fails.

🛡️ Handle lock creation failure
             # Create lock once we have a PID
-            self._create_lock()
+            try:
+                self._create_lock()
+            except OSError as e:
+                logger.error(f"Failed to create lock file, terminating process: {e}")
+                self.process.terminate()
+                self.process = None
+                return False, f"Failed to create lock file: {e}"

             # Start output streaming
             self.status = "running"
server/routers/devserver.py (1)

33-36: sys.path manipulation is fragile for importing the security module.

Modifying sys.path at runtime can cause import issues in different execution contexts. Consider making security a proper package import or using relative imports if the module structure allows.

Also applies to: 341-344

Comment on lines +78 to +133
def validate_custom_command_strict(cmd: str) -> None:
"""
Strict allowlist validation for dev server commands.
Prevents arbitrary command execution (no sh -c, no cmd /c, no python -c, etc.)
"""
if not isinstance(cmd, str) or not cmd.strip():
raise ValueError("custom_command cannot be empty")

argv = shlex.split(cmd, posix=(sys.platform != "win32"))
if not argv:
raise ValueError("custom_command could not be parsed")

base = Path(argv[0]).name.lower()

# Block direct shells / interpreters commonly used for command injection
if base in {"sh", "bash", "zsh", "cmd", "powershell", "pwsh"}:
raise ValueError(f"custom_command runner not allowed: {base}")

if base not in ALLOWED_RUNNERS:
raise ValueError(f"custom_command runner not allowed: {base}")

# Block one-liner execution
lowered = [a.lower() for a in argv]
if base in {"python", "python3"}:
if "-c" in lowered:
raise ValueError("python -c is not allowed")
# Only allow: python -m uvicorn ...
if len(argv) < 3 or argv[1:3] != ["-m", "uvicorn"]:
raise ValueError("Only 'python -m uvicorn ...' is allowed")

if base == "uvicorn":
if len(argv) < 2 or ":" not in argv[1]:
raise ValueError("uvicorn must specify an app like module:app")

allowed_flags = {"--host", "--port", "--reload", "--log-level", "--workers"}
i = 2
while i < len(argv):
a = argv[i]
if a.startswith("-") and a not in allowed_flags:
raise ValueError(f"uvicorn flag not allowed: {a}")
i += 1

if base in {"npm", "pnpm", "yarn"}:
# Allow only dev/start scripts (no arbitrary exec)
if base == "npm":
if len(argv) < 3 or argv[1] != "run" or argv[2] not in {"dev", "start"}:
raise ValueError("npm custom_command must be 'npm run dev' or 'npm run start'")
elif base == "pnpm":
ok = (len(argv) >= 2 and argv[1] in {"dev", "start"}) or (len(argv) >= 3 and argv[1] == "run" and argv[2] in {"dev", "start"})
if not ok:
raise ValueError("pnpm custom_command must be 'pnpm dev/start' or 'pnpm run dev/start'")
elif base == "yarn":
ok = (len(argv) >= 2 and argv[1] in {"dev", "start"}) or (len(argv) >= 3 and argv[1] == "run" and argv[2] in {"dev", "start"})
if not ok:
raise ValueError("yarn custom_command must be 'yarn dev/start' or 'yarn run dev/start'")

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Review of validate_custom_command_strict logic (appears twice due to duplication).

The validation logic has a potential gap in the uvicorn flag parsing:

  1. Flags with = syntax not handled: Flags like --port=8000 will be rejected because --port=8000 is not in allowed_flags, even though --port is allowed.

  2. Inconsistent case handling: The -c check uses lowered list (line 102/410), but the argv[1:3] != ["-m", "uvicorn"] check (line 105/413) is case-sensitive. A user could bypass with -M uvicorn.

🔧 Fix flag validation and casing consistency
     if base == "uvicorn":
         if len(argv) < 2 or ":" not in argv[1]:
             raise ValueError("uvicorn must specify an app like module:app")
 
         allowed_flags = {"--host", "--port", "--reload", "--log-level", "--workers"}
         i = 2
         while i < len(argv):
             a = argv[i]
-            if a.startswith("-") and a not in allowed_flags:
-                raise ValueError(f"uvicorn flag not allowed: {a}")
+            if a.startswith("-"):
+                # Handle --flag=value syntax
+                flag_name = a.split("=")[0] if "=" in a else a
+                if flag_name not in allowed_flags:
+                    raise ValueError(f"uvicorn flag not allowed: {flag_name}")
             i += 1

     if base in {"python", "python3"}:
         if "-c" in lowered:
             raise ValueError("python -c is not allowed")
         # Only allow: python -m uvicorn ...
-        if len(argv) < 3 or argv[1:3] != ["-m", "uvicorn"]:
+        if len(argv) < 3 or [x.lower() for x in argv[1:3]] != ["-m", "uvicorn"]:
             raise ValueError("Only 'python -m uvicorn ...' is allowed")

Also applies to: 386-440

🤖 Prompt for AI Agents
In `@server/routers/devserver.py` around lines 78 - 133, The uvicorn flag parsing
and mode checks in validate_custom_command_strict are case-sensitive and don't
accept --flag=value forms; update the function to use the already-created
lowered list for all comparisons (e.g., compare lowered[1:3] to ["-m","uvicorn"]
and use lowered for base checks) and normalize flags when iterating by splitting
on '=' and lowercasing the flag name before membership tests (e.g., flag_name =
a.split("=",1)[0].lower()) so that allowed_flags (make it a lowercased set)
accepts both "--port" and "--port=8000" and comparisons like "-m"/"uvicorn" are
case-insensitive.

Comment on lines +309 to +351
config = get_project_config(project_dir)"""
Dev Server Router
=================

API endpoints for dev server control (start/stop) and configuration.
Uses project registry for path lookups and project_config for command detection.
"""

import logging
import sys
import shlex
from pathlib import Path

from fastapi import APIRouter, HTTPException

from ..schemas import (
DevServerActionResponse,
DevServerConfigResponse,
DevServerConfigUpdate,
DevServerStartRequest,
DevServerStatus,
)
from ..services.dev_server_manager import get_devserver_manager
from ..services.project_config import (
clear_dev_command,
get_dev_command,
get_project_config,
set_dev_command,
)
from ..utils.project_helpers import get_project_path as _get_project_path
from ..utils.validation import validate_project_name

# Add root to path for security module import
_root = Path(__file__).parent.parent.parent
if str(_root) not in sys.path:
sys.path.insert(0, str(_root))

from security import extract_commands, get_effective_commands, is_command_allowed

logger = logging.getLogger(__name__)


router = APIRouter(prefix="/api/projects/{project_name}/devserver", tags=["devserver"])
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the duplicate content in the file
echo "=== Checking for duplicate function definitions ==="
rg -n "^def get_project_dir|^def validate_custom_command_strict|^async def update_devserver_config" server/routers/devserver.py

echo ""
echo "=== Checking for duplicate module docstring ==="
rg -n "Dev Server Router" server/routers/devserver.py

echo ""
echo "=== Checking for duplicate ALLOWED_RUNNERS ==="
rg -n "ALLOWED_RUNNERS" server/routers/devserver.py

Repository: leonvanzyl/autocoder

Length of output: 784


🏁 Script executed:

#!/bin/bash
# Check the actual content around line 309
echo "=== Lines 305-315 around the suspected corruption ==="
sed -n '305,315p' server/routers/devserver.py | cat -n

echo ""
echo "=== Total lines in file ==="
wc -l server/routers/devserver.py

echo ""
echo "=== Check for syntax errors (first occurrence of malformed string) ==="
sed -n '309p' server/routers/devserver.py | cat -A

Repository: leonvanzyl/autocoder

Length of output: 743


Critical: File contains duplicate code causing syntax/runtime errors.

Starting at line 309, the file content is duplicated. The get_devserver_config function is cut off mid-statement with a malformed string (config = get_project_config(project_dir)""") followed by the entire module docstring, imports, and helper functions repeating.

This causes:

  1. Syntax error from the unclosed/malformed triple-quoted string at line 309
  2. Duplicate function definitions: get_project_dir (lines 46, 354), validate_custom_command_strict (lines 78, 386), update_devserver_config (lines 628, 691)
  3. Duplicate module docstring (lines 2, 310) and ALLOWED_RUNNERS constant (lines 76, 384)
  4. Duplicate route registrations

Please review the merge/commit history and remove the duplicate content starting from line 309 onward.

🤖 Prompt for AI Agents
In `@server/routers/devserver.py` around lines 309 - 351, The file contains a
duplicated second copy of the module (starting at the malformed line "config =
get_project_config(project_dir)""" and repeating the module docstring, imports,
constants and route handlers) which causes a syntax error and multiple duplicate
definitions; remove the entire duplicated block after that malformed line so
only one module-level docstring, one set of imports, one ALLOWED_RUNNERS, and
single definitions of get_project_dir, validate_custom_command_strict,
get_devserver_config, update_devserver_config, and route registrations remain,
and ensure the truncated get_devserver_config statement is completed/removed as
needed and imports/helpers (extract_commands, get_effective_commands,
is_command_allowed, get_devserver_manager, project_config functions,
_get_project_path, validate_project_name) are only declared once.

Comment on lines +682 to +687
return DevServerConfigResponse(
detected_type=config["detected_type"],
detected_command=config["detected_command"],
custom_command=config["custom_command"],
effective_command=config["effective_command"],
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Unreachable duplicate return statement.

Lines 682-687 contain a duplicate return DevServerConfigResponse(...) statement that appears after the function has already returned at line 679. This is dead code resulting from the file duplication issue.

🤖 Prompt for AI Agents
In `@server/routers/devserver.py` around lines 682 - 687, There is a duplicate,
unreachable return that constructs DevServerConfigResponse; locate the function
that returns a DevServerConfigResponse (look for the earlier return that already
returns the response) and remove the second/duplicate return
DevServerConfigResponse(...) block so only the single, intended return remains
(or consolidate any differing fields into the first return if needed).

@syphonetic syphonetic closed this Feb 3, 2026
CoreAspectStu added a commit to CoreAspectStu/autocoder-custom that referenced this pull request Feb 9, 2026
…ensive programming

- Created 24 unit tests using Vitest + React Testing Library
- Tests verify ChatTab handles undefined/null data gracefully
- Coverage includes:
  * Undefined conversations array
  * Null messages handling
  * Missing conversation properties
  * Malformed API responses (404, 500, 503)
  * Network error handling
  * Type safety (wrong data types)
  * Edge cases (no project, empty name, long content)
  * Error Boundary functionality
  * 100% coverage of error paths

All test code written and verified with automated script.
Tests will run when system load decreases.

Related: Feature AutoForgeAI#147 (error handling), Feature AutoForgeAI#146 (404 handling)
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