Skip to content

Commit d748c6b

Browse files
deacon-mpclaude
andauthored
fix: replace create_subprocess_shell (#3265)
* fix: replace create_subprocess_shell with safe exec in start_vue_dev_server Avoid shell injection risk by using create_subprocess_exec instead. * fix: address Copilot review feedback on subprocess PR - Capture proc from create_subprocess_exec, log PID, schedule proc.wait() to avoid zombie processes on Vue dev server exit - Rewrite test to use pytest style, ast-based function extraction, and Path-relative server.py resolution Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: guard against None return from ast.get_source_segment ast.get_source_segment() returns None when source offsets are unavailable; assert it is not None before using it in string checks. * fix: remove untracked create_task in start_vue_dev_server to avoid zombie subprocess * test: accept FunctionDef and AsyncFunctionDef in start_vue_dev_server check * test: use explicit utf-8 encoding in read_text() --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b6156b9 commit d748c6b

File tree

2 files changed

+24
-3
lines changed

2 files changed

+24
-3
lines changed

server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ async def enable_cors(request, response):
156156

157157

158158
async def start_vue_dev_server():
159-
await asyncio.create_subprocess_shell(
160-
"npm run dev", stdout=sys.stdout, stderr=sys.stderr, cwd=MAGMA_PATH
159+
proc = await asyncio.create_subprocess_exec(
160+
"npm", "run", "dev", stdout=sys.stdout, stderr=sys.stderr, cwd=MAGMA_PATH
161161
)
162-
logging.info("VueJS development server is live.")
162+
logging.info("VueJS development server started (PID %s).", proc.pid)
163163

164164

165165
def _get_parser():
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import ast
2+
from pathlib import Path
3+
4+
5+
SERVER_PY = Path(__file__).resolve().parents[2] / 'server.py'
6+
7+
8+
def test_no_create_subprocess_shell_in_start_vue():
9+
"""Verify create_subprocess_shell is not used in start_vue_dev_server."""
10+
content = SERVER_PY.read_text(encoding='utf-8')
11+
tree = ast.parse(content, filename=str(SERVER_PY))
12+
func_node = None
13+
for node in ast.walk(tree):
14+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == 'start_vue_dev_server':
15+
func_node = node
16+
break
17+
assert func_node is not None, "start_vue_dev_server function not found in server.py"
18+
func_content = ast.get_source_segment(content, func_node)
19+
assert func_content is not None, "Could not extract source for start_vue_dev_server"
20+
assert 'create_subprocess_shell' not in func_content
21+
assert 'create_subprocess_exec' in func_content

0 commit comments

Comments
 (0)