Добавлен безопасный shell runner для workspace
Этот коммит содержится в:
@@ -0,0 +1,57 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
DATA_DIR = Path(os.getenv('DEVCONSOLE_DATA_DIR', '/var/lib/devconsole'))
|
||||||
|
PROJECTS_DIR = DATA_DIR / 'projects'
|
||||||
|
|
||||||
|
BLOCKED_TOKENS = [
|
||||||
|
'rm -rf /',
|
||||||
|
'mkfs',
|
||||||
|
':(){',
|
||||||
|
'dd if=',
|
||||||
|
'shutdown',
|
||||||
|
'reboot',
|
||||||
|
'poweroff',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_inside_projects(path: Path) -> bool:
|
||||||
|
try:
|
||||||
|
path.resolve().relative_to(PROJECTS_DIR.resolve())
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def run_command(command: str, cwd: str | None = None, timeout: int = 900) -> dict:
|
||||||
|
if not command.strip():
|
||||||
|
raise ValueError('Command is empty')
|
||||||
|
|
||||||
|
lowered = command.lower()
|
||||||
|
for token in BLOCKED_TOKENS:
|
||||||
|
if token in lowered:
|
||||||
|
raise ValueError(f'Blocked unsafe command: {token}')
|
||||||
|
|
||||||
|
workdir = Path(cwd or PROJECTS_DIR).resolve()
|
||||||
|
PROJECTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if not _is_inside_projects(workdir):
|
||||||
|
raise ValueError('Command cwd must be inside DevConsole projects directory')
|
||||||
|
|
||||||
|
process = subprocess.run(
|
||||||
|
command,
|
||||||
|
cwd=workdir,
|
||||||
|
shell=True,
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'command': command,
|
||||||
|
'cwd': workdir.as_posix(),
|
||||||
|
'returncode': process.returncode,
|
||||||
|
'stdout': process.stdout[-20000:],
|
||||||
|
'stderr': process.stderr[-20000:],
|
||||||
|
}
|
||||||
Ссылка в новой задаче
Block a user