Spaces:
Sleeping
Sleeping
| """``/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) | |
| 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) | |
| 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": "❌ تعذّر الحفظ"} | |
| 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} | |