Skip to content

Commit e96966f

Browse files
committed
Suggestion fix
1 parent ef7f8cc commit e96966f

File tree

7 files changed

+63
-91
lines changed

7 files changed

+63
-91
lines changed

.github/workflows/codeql.yml

Lines changed: 0 additions & 43 deletions
This file was deleted.

cortex/cli.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,9 @@ def parallel_log_callback(message: str, level: str = "info"):
213213
# Calculate total duration from tasks
214214
total_duration = 0
215215
if parallel_tasks:
216-
max_end = max((t.end_time or 0) for t in parallel_tasks)
217-
min_start = min((t.start_time or time.time()) for t in parallel_tasks)
218-
if max_end and min_start:
216+
max_end = max((t.end_time for t in parallel_tasks if t.end_time is not None), default=None)
217+
min_start = min((t.start_time for t in parallel_tasks if t.start_time is not None), default=None)
218+
if max_end is not None and min_start is not None:
219219
total_duration = max_end - min_start
220220

221221
if success:

cortex/coordinator.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,9 @@
99
from datetime import datetime
1010
import logging
1111

12-
logger = logging.getLogger(__name__)
12+
from cortex.validators import DANGEROUS_PATTERNS
1313

14-
# Dangerous patterns that should never be executed
15-
DANGEROUS_PATTERNS = [
16-
r'rm\s+-rf\s+[/\*]',
17-
r'rm\s+--no-preserve-root',
18-
r'dd\s+if=.*of=/dev/',
19-
r'curl\s+.*\|\s*sh',
20-
r'curl\s+.*\|\s*bash',
21-
r'wget\s+.*\|\s*sh',
22-
r'wget\s+.*\|\s*bash',
23-
r'\beval\s+',
24-
r'base64\s+-d\s+.*\|',
25-
r'>\s*/etc/',
26-
r'chmod\s+777',
27-
r'chmod\s+\+s',
28-
]
14+
logger = logging.getLogger(__name__)
2915

3016

3117
class StepStatus(Enum):

cortex/install_parallel.py

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from dataclasses import dataclass, field
88
from enum import Enum
99

10+
from cortex.validators import DANGEROUS_PATTERNS
11+
1012

1113
class TaskStatus(Enum):
1214
PENDING = "pending"
@@ -55,36 +57,23 @@ async def run_single_task(task: ParallelTask, executor, timeout: int, log_callba
5557
log_callback(f"Starting {task.name}…", "info")
5658

5759
# Validate command for dangerous patterns
58-
DANGEROUS_PATTERNS = [
59-
r'rm\s+-rf\s+[/\*]',
60-
r'rm\s+--no-preserve-root',
61-
r'dd\s+if=.*of=/dev/',
62-
r'curl\s+.*\|\s*sh',
63-
r'curl\s+.*\|\s*bash',
64-
r'wget\s+.*\|\s*sh',
65-
r'wget\s+.*\|\s*bash',
66-
r'\beval\s+',
67-
r'base64\s+-d\s+.*\|',
68-
r'>\s*/etc/',
69-
r'chmod\s+777',
70-
r'chmod\s+\+s',
71-
]
72-
7360
for pattern in DANGEROUS_PATTERNS:
7461
if re.search(pattern, task.command, re.IGNORECASE):
7562
task.status = TaskStatus.FAILED
76-
task.error = f"Command blocked: matches dangerous pattern"
63+
task.error = "Command blocked: matches dangerous pattern"
7764
task.end_time = time.time()
7865
if log_callback:
7966
log_callback(f"Finished {task.name} (failed)", "error")
8067
return False
8168

8269
try:
8370
# Run command in executor (thread pool) to avoid blocking the event loop
84-
loop = asyncio.get_event_loop()
71+
loop = asyncio.get_running_loop()
8572
result = await asyncio.wait_for(
8673
loop.run_in_executor(
8774
executor,
75+
# Use shell=True carefully - commands are validated against dangerous patterns above.
76+
# shell=True is required to support complex shell commands (e.g., pipes, redirects).
8877
lambda: subprocess.run(
8978
task.command,
9079
shell=True,
@@ -148,7 +137,7 @@ async def run_parallel_install(
148137
log_callback: Optional callback for logging (called with message and level)
149138
150139
Returns:
151-
Tuple of (success: bool, tasks: List[ParallelTask])
140+
tuple[bool, List[ParallelTask]]: Success status and list of all tasks
152141
"""
153142
if not commands:
154143
return True, []
@@ -190,14 +179,31 @@ async def run_parallel_install(
190179
while pending or running:
191180
# Start tasks whose dependencies are met
192181
ready_to_start = []
193-
for task_name in list(pending):
182+
for task_name in pending.copy():
194183
task = tasks[task_name]
195-
deps_met = all(dep in completed for dep in task.dependencies)
184+
# When stop_on_error=False, accept both completed and failed dependencies
185+
if stop_on_error:
186+
deps_met = all(dep in completed for dep in task.dependencies)
187+
else:
188+
deps_met = all(dep in completed or dep in failed for dep in task.dependencies)
196189

197190
if deps_met:
198191
ready_to_start.append(task_name)
199192
pending.remove(task_name)
200193

194+
# If no tasks can be started and none are running, we're stuck (deadlock/cycle detection)
195+
if not ready_to_start and not running and pending:
196+
# Mark remaining tasks as skipped - they have unresolvable dependencies
197+
for task_name in pending:
198+
task = tasks[task_name]
199+
if task.status == TaskStatus.PENDING:
200+
task.status = TaskStatus.SKIPPED
201+
task.error = "Task could not run because dependencies never completed"
202+
if log_callback:
203+
log_callback(f"{task_name} skipped due to unresolved dependencies", "error")
204+
failed.update(pending)
205+
break
206+
201207
# Create tasks for ready items
202208
for task_name in ready_to_start:
203209
coro = run_single_task(tasks[task_name], executor, timeout, log_callback)
@@ -217,10 +223,20 @@ async def run_parallel_install(
217223
# Process completed tasks
218224
for task_coro in done:
219225
# Find which task this is
220-
for task_name, running_coro in list(running.items()):
226+
for task_name, running_coro in running.items():
221227
if running_coro is task_coro:
222228
task = tasks[task_name]
223-
success = task_coro.result()
229+
230+
# Handle cancelled tasks
231+
try:
232+
success = task_coro.result()
233+
except asyncio.CancelledError:
234+
# Task was cancelled due to stop_on_error
235+
task.status = TaskStatus.SKIPPED
236+
task.error = "Task cancelled due to dependency failure"
237+
failed.add(task_name)
238+
del running[task_name]
239+
break
224240

225241
if success:
226242
completed.add(task_name)

cortex/validators.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@
99
from typing import Optional, Tuple
1010

1111

12+
# Dangerous command patterns to block for security
13+
DANGEROUS_PATTERNS = [
14+
r'rm\s+-rf\s+[/\*]',
15+
r'rm\s+--no-preserve-root',
16+
r'dd\s+if=.*of=/dev/',
17+
r'curl\s+.*\|\s*sh',
18+
r'curl\s+.*\|\s*bash',
19+
r'wget\s+.*\|\s*sh',
20+
r'wget\s+.*\|\s*bash',
21+
r'\beval\s+',
22+
r'base64\s+-d\s+.*\|',
23+
r'>\s*/etc/',
24+
r'chmod\s+777',
25+
r'chmod\s+\+s',
26+
]
27+
28+
1229
class ValidationError(Exception):
1330
"""Custom exception for validation errors with user-friendly messages"""
1431

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ rich>=13.0.0
99

1010
# Type hints for older Python versions
1111
typing-extensions>=4.0.0
12+
PyYAML>=6.0.0

tests/installer/test_parallel_install.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import time
66
from cortex.install_parallel import (
77
run_parallel_install,
8-
ParallelTask,
98
TaskStatus
109
)
1110

@@ -64,10 +63,6 @@ async def run_test():
6463

6564
assert success
6665
assert all(t.status == TaskStatus.SUCCESS for t in tasks)
67-
68-
# Verify all tasks completed (they were sequential)
69-
assert len(tasks) == 3
70-
assert all(t.status == TaskStatus.SUCCESS for t in tasks)
7166

7267
asyncio.run(run_test())
7368

@@ -178,7 +173,7 @@ async def run_test():
178173

179174
assert not success
180175
assert tasks[0].status == TaskStatus.FAILED
181-
assert "timed out" in tasks[0].error.lower() or tasks[0].error != ""
176+
assert "timed out" in tasks[0].error.lower() or "timeout" in tasks[0].error.lower()
182177

183178
asyncio.run(run_test())
184179

@@ -242,7 +237,7 @@ async def run_test():
242237
def log_callback(message: str, level: str = "info"):
243238
log_messages.append((message, level))
244239

245-
success, tasks = await run_parallel_install(
240+
success, _tasks = await run_parallel_install(
246241
commands,
247242
timeout=10,
248243
log_callback=log_callback

0 commit comments

Comments
 (0)