tts-voice-creator-clone-and.../routes/admin.py
mARTin-B78 c7a1e35539 Security audit, modular refactor, and container-name field
Security fixes:
- Block /proc /sys /dev /run /boot in /api/browse-dirs (path traversal)
- Verify yt-dlp output stays inside TEMP_DIR before registration
- Remove Access-Control-Allow-Origin: * from /api/proxy-audio
- TTL-based temp file registry (default 2h) to prevent disk fill

Performance:
- Cache settings + routing rules in memory (mtime-checked); eliminates
  per-request disk reads on every TTS call

UI:
- Add container name (optional) field to Docker stack TTS/STT engine
  cards (Qwen3 Voice Clone, Voice Design, Custom Voice, Streaming,
  NVIDIA Magpie, Parakeet) — enables Stop/Start/Restart buttons on
  all engine cards, matching the existing Other Local TTS/STT cards

Refactor — backend:
- server.py: 5560 lines → 43-line entry point
- core/ package: constants, registry, validation, docker_client,
  config, routing, audio, voice, presets, tts_helpers
- routes/ package: admin, settings, library, stt, sources, docker,
  tts, conversation (FastAPI APIRouter modules)
- Dockerfile + docker-compose.yml updated to include core/ and routes/

Refactor — frontend:
- static/app.js: 8744 lines → 16 modules in static/js/
  utils, voice-inspector, voice-sources, integrations, routing,
  settings, voice-clone, voice-library, tts-preview, benchmark,
  stt, init, engines, ai-backends, generation, conversation
- static/loader.js updated to load modules sequentially

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 12:13:07 +02:00

53 lines
1.4 KiB
Python

"""Admin routes: index, favicon, browse-dirs, robots."""
from __future__ import annotations
from pathlib import Path
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse, PlainTextResponse, Response
from core.constants import STATIC_DIR
router = APIRouter()
_BROWSE_BLOCKED: frozenset[str] = frozenset({
"/proc", "/sys", "/dev", "/run", "/boot",
})
@router.get("/")
async def index():
return FileResponse(
STATIC_DIR / "index.html",
headers={"Cache-Control": "no-store, max-age=0"},
)
@router.get("/favicon.ico")
async def favicon():
return Response(status_code=204)
@router.get("/api/browse-dirs")
async def browse_dirs(path: str = "/"):
p = Path(path).resolve()
p_str = str(p)
if any(p_str == b or p_str.startswith(b + "/") for b in _BROWSE_BLOCKED):
raise HTTPException(403, "Access to this path is not permitted")
if not p.is_dir():
raise HTTPException(404, "Not a directory")
try:
entries = sorted(
[d.name for d in p.iterdir() if d.is_dir() and not d.name.startswith(".")],
key=str.lower,
)
except PermissionError:
raise HTTPException(403, "Permission denied")
parent = str(p.parent) if p.parent != p else None
return {"path": str(p), "parent": parent, "dirs": entries}
@router.get("/robots.txt", response_class=PlainTextResponse)
async def robots_txt():
return "User-agent: *\nDisallow: /"