Spaces:
Sleeping
Sleeping
File size: 4,080 Bytes
80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 3c7b4e4 80a4a65 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | """``/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}
|