mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-02-05 16:33:08 +00:00
fix: harden dev server RCE mitigations from PR #153
Address security gaps and improve validation in the dev server command execution path introduced by PR #153: Security fixes (critical): - Add missing shell metacharacters to dangerous_ops blocklist: single & (Windows cmd.exe command separator), >, <, ^, %, \n, \r - The single & gap was a confirmed RCE bypass on Windows where .cmd files are always executed via cmd.exe even with shell=False (CPython limitation documented in issue #77696) - Apply validate_custom_command_strict at /start endpoint for defense-in-depth against config file tampering Validation improvements: - Fix uvicorn --flag=value syntax (split on = before comparing) - Expand Python support: Django (manage.py), Flask, custom .py scripts - Add runners: flask, poetry, cargo, go, npx - Expand npm script allowlist: serve, develop, server, preview - Reorder PATCH /config validation to run strict check first (fail fast) - Extract constants: ALLOWED_NPM_SCRIPTS, ALLOWED_PYTHON_MODULES, BLOCKED_SHELLS for reuse and testability Cleanup: - Remove unused security.py imports from dev_server_manager.py - Fix deprecated datetime.utcnow() -> datetime.now(timezone.utc) - Remove unnecessary _remove_lock() in exception handlers where lock was never created (Popen failure path) Tests: - Add test_devserver_security.py with 78 tests covering valid commands, blocked shells, blocked commands, injection attempts, dangerous_ops blocking, and constant verification Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,18 +14,17 @@ This is a simplified version of AgentProcessManager, tailored for dev servers:
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import shlex
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Awaitable, Callable, Literal, Set
|
||||
|
||||
import psutil
|
||||
|
||||
from registry import list_registered_projects
|
||||
from security import extract_commands, get_effective_commands, is_command_allowed
|
||||
from server.utils.process_utils import kill_process_tree
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -310,9 +309,16 @@ class DevServerProcessManager:
|
||||
return False, "Empty dev server command"
|
||||
|
||||
# SECURITY: block shell operators/metacharacters (defense-in-depth)
|
||||
dangerous_ops = ["&&", "||", ";", "|", "`", "$("]
|
||||
# NOTE: On Windows, .cmd/.bat files are executed via cmd.exe even with
|
||||
# shell=False (CPython limitation), so metacharacter blocking is critical.
|
||||
# Single & is a cmd.exe command separator, ^ is cmd escape, % enables
|
||||
# environment variable expansion, > < enable redirection.
|
||||
dangerous_ops = ["&&", "||", ";", "|", "`", "$(", "&", ">", "<", "^", "%"]
|
||||
if any(op in command for op in dangerous_ops):
|
||||
return False, "Shell operators are not allowed in dev server command"
|
||||
# Block newline injection (cmd.exe interprets newlines as command separators)
|
||||
if "\n" in command or "\r" in command:
|
||||
return False, "Newlines are not allowed in dev server command"
|
||||
|
||||
# Parse into argv and execute without shell
|
||||
argv = shlex.split(command, posix=(sys.platform != "win32"))
|
||||
@@ -349,7 +355,7 @@ class DevServerProcessManager:
|
||||
)
|
||||
|
||||
self._command = command
|
||||
self.started_at = datetime.utcnow()
|
||||
self.started_at = datetime.now(timezone.utc)
|
||||
self._detected_url = None
|
||||
|
||||
# Create lock once we have a PID
|
||||
@@ -364,12 +370,10 @@ class DevServerProcessManager:
|
||||
except FileNotFoundError:
|
||||
self.status = "stopped"
|
||||
self.process = None
|
||||
self._remove_lock()
|
||||
return False, f"Command not found: {argv[0]}"
|
||||
except Exception as e:
|
||||
self.status = "stopped"
|
||||
self.process = None
|
||||
self._remove_lock()
|
||||
return False, f"Failed to start dev server: {e}"
|
||||
|
||||
async def stop(self) -> tuple[bool, str]:
|
||||
|
||||
Reference in New Issue
Block a user