From 9841c2b3d238e5139dc7cf0274e98eb9468e3ee8 Mon Sep 17 00:00:00 2001 From: remsky Date: Sun, 12 Jul 2026 11:14:04 -0600 Subject: [PATCH] fix(debug-endpoints): gate /debug/* introspection routes behind ENABLE_DEBUG_ENDPOINTS, document opt-in flags --- CHANGELOG.md | 9 ++++ README.md | 12 ++--- api/src/core/config.py | 3 ++ api/src/main.py | 2 +- api/src/routers/debug.py | 74 ++++++------------------------- api/tests/test_debug_endpoints.py | 26 +++++++++++ docker/cpu/docker-compose.yml | 4 +- docker/gpu/docker-compose.yml | 2 + docker/rocm/docker-compose.yml | 2 + 9 files changed, 66 insertions(+), 68 deletions(-) create mode 100644 api/tests/test_debug_endpoints.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5114fe05..2cf6f3ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,17 @@ Notable changes to this project will be documented in this file. Per-PR attribution and contributor credits are published automatically on the corresponding GitHub release page; this file is the curated, human-readable summary. ## [v0.6.0] - Unreleased +### Breaking changes +- `POST /dev/unload` is off by default; set `ALLOW_DEV_UNLOAD=true` to enable, otherwise returns 403. Shipped open in v0.5.0, now opt-in (#483). +- `/debug/*` routes also set off by default, continuation of above; to avoid unintentional exposure of internals (stack traces, temp storage, CPU/mem/GPU); set `ENABLE_DEBUG_ENDPOINTS=true` to enable, otherwise 403's. +- Removed lingering deprecated `/debug/session_pools` + +### Changed +- Documented API stability: `/v1/*` is the stable surface; `/dev/*` and `/debug/*` are operational helpers that may change or move behind flags between minor releases. + ### Fixed - OpenAI voice aliases pointed at legacy v0.19 voicepacks that sound degraded on the v1.0 model. Added the proper v1.0 `bf_isabella` and repointed `nova` (`bf_v0isabella` -> `bf_isabella`), `alloy`, `ash`, `coral`, `echo` to their v1.0 voices. The `v0*` voices stay available by explicit name. (#479) +- `/dev/captioned_speech` returned `timestamps: null` for non-English espeak voices (es/fr/it/hi/pt). Word timestamps are now derived from the model's own phoneme durations (`pred_dur`), so they match the audio exactly; falls back to the old behavior when word counts can't be reconciled. English path unchanged, ja/zh keep previous behavior. (#484) ## [v0.5.0] - 2026-06-06 ### Added diff --git a/README.md b/README.md index f3e01790..e46a04b5 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@

# _`FastKoko`_ -[![Changelog](https://img.shields.io/badge/changelog-white)](./CHANGELOG.md) [![Tests](https://img.shields.io/badge/tests-81-darkgreen)]() -[![Coverage](https://img.shields.io/badge/coverage-52%25-tan)]() +[![Changelog](https://img.shields.io/badge/changelog-white)](./CHANGELOG.md) [![Tests](https://img.shields.io/badge/tests-100-darkgreen)]() +[![Coverage](https://img.shields.io/badge/coverage-58%25-tan)]() [![Kokoro](https://img.shields.io/badge/kokoro-0.9.4-BB5420)](https://github.com/hexgrad/kokoro) [![Misaki](https://img.shields.io/badge/misaki-0.9.4-B8860B)](https://github.com/hexgrad/misaki) [![Tested at Model Commit](https://img.shields.io/badge/model-1.0::9901c2b-blue)](https://huggingface.co/hexgrad/Kokoro-82M/commit/9901c2b79161b6e898b7ea857ae5298f47b8b0d6) -[![Try on Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Try%20on-Spaces-blue)](https://huggingface.co/spaces/Remsky/FastKoko) [![Downloads](https://img.shields.io/badge/downloads-1.4M%2B-2496ED?logo=docker&logoColor=white)](https://github.com/remsky?tab=packages&repo_name=Kokoro-FastAPI) +[![Try on Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Try%20on-Spaces-blue)](https://huggingface.co/spaces/Remsky/FastKoko) [![Downloads](https://img.shields.io/badge/downloads-1.8M%2B-2496ED?logo=docker&logoColor=white)](https://github.com/remsky?tab=packages&repo_name=Kokoro-FastAPI) @@ -582,14 +582,16 @@ The city of [Worcester](/wˈʊstər/) is easy. [pause:1s] See?
Debug Endpoints -Monitor system state and resource usage with these endpoints: +Monitor system state and resource usage with these endpoints. The `/debug/*` routes expose host and process internals, so they are off by default; set `ENABLE_DEBUG_ENDPOINTS=true` to enable. - `/debug/threads` - Get thread information and stack traces - `/debug/storage` - Monitor temp file and output directory usage - `/debug/system` - Get system information (CPU, memory, GPU) -- `POST /dev/unload` - Release model from VRAM; reloads lazily on next request +- `POST /dev/unload` - Release model from VRAM; reloads lazily on next request. Off by default; set `ALLOW_DEV_UNLOAD=true` to enable Useful for debugging resource exhaustion or performance issues. + +Stability: the `/v1/*` OpenAI-compatible routes are the stable API. `/dev/*` and `/debug/*` are operational helpers, and may change or move behind flags between minor releases.
diff --git a/api/src/core/config.py b/api/src/core/config.py index c6a896eb..6bb54c1f 100644 --- a/api/src/core/config.py +++ b/api/src/core/config.py @@ -41,6 +41,9 @@ class Settings(BaseSettings): False # Whether to allow saving combined voices locally ) allow_dev_unload: bool = False # Whether to expose the POST /dev/unload endpoint + enable_debug_endpoints: bool = ( + False # Whether to expose /debug/* host and process introspection routes + ) # Container absolute paths model_dir: str = "/app/api/src/models" # Absolute path in container diff --git a/api/src/main.py b/api/src/main.py index 11bd3e5b..89a7759b 100644 --- a/api/src/main.py +++ b/api/src/main.py @@ -135,7 +135,7 @@ async def lifespan(app: FastAPI): # Include routers app.include_router(openai_router, prefix="/v1") app.include_router(dev_router) # Development endpoints -app.include_router(debug_router) # Debug endpoints +app.include_router(debug_router) # Debug endpoints (403 unless enabled) if settings.enable_web_player: app.include_router(web_router, prefix="/web") # Web player static files diff --git a/api/src/routers/debug.py b/api/src/routers/debug.py index 8acb9fd7..c3476765 100644 --- a/api/src/routers/debug.py +++ b/api/src/routers/debug.py @@ -1,10 +1,11 @@ import threading -import time from datetime import datetime import psutil import torch -from fastapi import APIRouter +from fastapi import APIRouter, Depends, HTTPException + +from ..core.config import settings try: import GPUtil @@ -13,7 +14,16 @@ except ImportError: GPU_AVAILABLE = False -router = APIRouter(tags=["debug"]) + +async def _require_debug_enabled(): + if not settings.enable_debug_endpoints: + raise HTTPException( + status_code=403, + detail={"error": "Debug endpoints are disabled"}, + ) + + +router = APIRouter(tags=["debug"], dependencies=[Depends(_require_debug_enabled)]) @router.get("/debug/threads") @@ -149,61 +159,3 @@ async def get_system_info(): "network": network_info, "gpu": gpu_info, } - - -@router.get("/debug/session_pools") -async def get_session_pool_info(): - """Get information about ONNX session pools.""" - from ..inference.model_manager import get_manager - - manager = await get_manager() - pools = manager._session_pools - current_time = time.time() - - pool_info = {} - - # Get CPU pool info - if "onnx_cpu" in pools: - cpu_pool = pools["onnx_cpu"] - pool_info["cpu"] = { - "active_sessions": len(cpu_pool._sessions), - "max_sessions": cpu_pool._max_size, - "sessions": [ - {"model": path, "age_seconds": current_time - info.last_used} - for path, info in cpu_pool._sessions.items() - ], - } - - # Get GPU pool info - if "onnx_gpu" in pools: - gpu_pool = pools["onnx_gpu"] - pool_info["gpu"] = { - "active_sessions": len(gpu_pool._sessions), - "max_streams": gpu_pool._max_size, - "available_streams": len(gpu_pool._available_streams), - "sessions": [ - { - "model": path, - "age_seconds": current_time - info.last_used, - "stream_id": info.stream_id, - } - for path, info in gpu_pool._sessions.items() - ], - } - - # Add GPU memory info if available - if GPU_AVAILABLE: - try: - gpus = GPUtil.getGPUs() - if gpus: - gpu = gpus[0] # Assume first GPU - pool_info["gpu"]["memory"] = { - "total_mb": gpu.memoryTotal, - "used_mb": gpu.memoryUsed, - "free_mb": gpu.memoryFree, - "percent_used": (gpu.memoryUsed / gpu.memoryTotal) * 100, - } - except Exception: - pass - - return pool_info diff --git a/api/tests/test_debug_endpoints.py b/api/tests/test_debug_endpoints.py new file mode 100644 index 00000000..091017bc --- /dev/null +++ b/api/tests/test_debug_endpoints.py @@ -0,0 +1,26 @@ +"""Tests for the /debug/* opt-in gate.""" + +from unittest.mock import patch + +from fastapi.testclient import TestClient + +from api.src.core.config import settings +from api.src.main import app + +client = TestClient(app) + + +def test_debug_endpoints_403_when_disabled(): + """Disabled by default: every /debug/* route returns 403.""" + for path in ["/debug/threads", "/debug/storage", "/debug/system"]: + response = client.get(path) + assert response.status_code == 403 + assert response.json()["detail"]["error"] == "Debug endpoints are disabled" + + +def test_debug_endpoints_200_when_enabled(): + with patch.object(settings, "enable_debug_endpoints", True): + response = client.get("/debug/threads") + + assert response.status_code == 200 + assert "total_threads" in response.json() diff --git a/docker/cpu/docker-compose.yml b/docker/cpu/docker-compose.yml index a793ace9..3013ca7b 100644 --- a/docker/cpu/docker-compose.yml +++ b/docker/cpu/docker-compose.yml @@ -20,7 +20,9 @@ services: - ONNX_ARENA_EXTEND_STRATEGY=kNextPowerOfTwo - API_LOG_LEVEL=DEBUG - DOWNLOAD_MODEL=true - + # - ALLOW_DEV_UNLOAD=true + # - ENABLE_DEBUG_ENDPOINTS=true + # # Gradio UI service [Comment out everything below if you don't need it] # gradio-ui: # image: ghcr.io/remsky/kokoro-fastapi-ui:v${VERSION} diff --git a/docker/gpu/docker-compose.yml b/docker/gpu/docker-compose.yml index 94413a28..c068e732 100644 --- a/docker/gpu/docker-compose.yml +++ b/docker/gpu/docker-compose.yml @@ -21,6 +21,8 @@ services: - PYTHONUNBUFFERED=1 - API_LOG_LEVEL=DEBUG - DOWNLOAD_MODEL=true + # - ALLOW_DEV_UNLOAD=true + # - ENABLE_DEBUG_ENDPOINTS=true deploy: resources: reservations: diff --git a/docker/rocm/docker-compose.yml b/docker/rocm/docker-compose.yml index 73c94ff5..ed8f2f30 100644 --- a/docker/rocm/docker-compose.yml +++ b/docker/rocm/docker-compose.yml @@ -27,6 +27,8 @@ services: - 8880:8880 environment: - USE_GPU=true + # - ALLOW_DEV_UNLOAD=true + # - ENABLE_DEBUG_ENDPOINTS=true - TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 # MIOPEN_FIND_MODE=2 is set by default in the Dockerfile. It reuses the # on-disk find DB and skips the expensive per-process kernel search.