CyberArena / app /api /terminal.py
Hussien Haider
H
3c7b4e4
Raw
History Blame Contribute Delete
4.08 kB
"""``/api/training/terminal*`` — the in-process OS simulator endpoints."""
import os
import re
from fastapi import APIRouter, Depends, Request
from slowapi import Limiter
from slowapi.util import get_remote_address
from app.core.auth import get_current_user
from app.sandbox.terminal import run_terminal_command
from app.sandbox.workdir import get_or_create_workdir, safe_join
from app.types import (
TerminalRequest,
TerminalWriteRequest,
TerminalListRequest,
)
router = APIRouter()
# Rate limiter: 30 terminal commands per minute per IP
limiter = Limiter(key_func=get_remote_address)
@router.post("/api/training/terminal")
@limiter.limit("30/minute")
async def post_terminal(request: Request, req: TerminalRequest, user: dict = Depends(get_current_user)):
"""Execute a terminal command in the challenge's sandbox for real."""
# IDOR FIX: Use authenticated user's team role from JWT
team_role = user.get("team_role", "red")[:10]
challenge_id = re.sub(r"[^a-f0-9\-]", "", req.challengeId)[:128]
command = (req.command or "")[:500] # Limit command length
if not challenge_id or not command:
return {"stdout": "", "stderr": "❌ مدخلات غير صالحة", "exitCode": 1}
return await run_terminal_command(team_role, challenge_id, command)
@router.post("/api/training/terminal/write")
@limiter.limit("20/minute")
async def post_terminal_write(request: Request, req: TerminalWriteRequest, user: dict = Depends(get_current_user)):
"""Write/edit a file inside the sandbox workdir (path-traversal safe)."""
# IDOR FIX: Use authenticated user's team role from JWT
team_role = user.get("team_role", "red")[:10]
challenge_id = re.sub(r"[^a-f0-9\-]", "", req.challengeId)[:128]
if not challenge_id:
return {"ok": False, "error": "❌ challengeId مفقود"}
if not req.filename:
return {"ok": False, "error": "❌ اسم الملف مفقود"}
# Sanitize filename
filename = re.sub(r"[^a-zA-Z0-9\.\-_\/]", "", req.filename)[:200]
if len(req.content) > 200_000:
return {"ok": False, "error": "❌ الملف كبير جداً (الحد 200KB)"}
workdir, _row = await get_or_create_workdir(team_role, challenge_id)
try:
target = safe_join(workdir, filename)
except ValueError as e:
return {"ok": False, "error": "❌ مسار غير مسموح"}
try:
os.makedirs(os.path.dirname(target) or workdir, exist_ok=True)
with open(target, "w", encoding="utf-8", newline="\n") as f:
f.write(req.content)
return {"ok": True, "path": os.path.relpath(target, workdir).replace("\\", "/")}
except Exception as e:
return {"ok": False, "error": "❌ تعذّر الحفظ"}
@router.post("/api/training/terminal/list")
@limiter.limit("60/minute")
async def post_terminal_list(request: Request, req: TerminalListRequest, user: dict = Depends(get_current_user)):
"""List files currently in the sandbox workdir (for the file explorer)."""
# IDOR FIX: Use authenticated user's team role from JWT
team_role = user.get("team_role", "red")[:10]
challenge_id = re.sub(r"[^a-f0-9\-]", "", req.challengeId)[:128]
if not challenge_id:
return {"files": []}
workdir, _row = await get_or_create_workdir(team_role, challenge_id)
files = []
try:
for name in sorted(os.listdir(workdir)):
if name.startswith("."):
continue
p = os.path.join(workdir, name)
if os.path.isfile(p):
try:
with open(p, "r", encoding="utf-8", errors="replace") as f:
content = f.read()
if len(content) > 50_000:
content = content[:50_000] + "\n... (truncated)"
files.append({"name": name, "content": content})
except Exception:
files.append({"name": name, "content": "(binary file)"})
except Exception:
pass
return {"files": files}