Hardens issues found in a security review of the current tree (separate from
the cookbook SSH PR):
- Email thread rendering (static/js/emailLibrary.js): the flat read path runs
inbound HTML through the allowlist sanitizer, but the two threaded paths
(_renderTurnsAsBubbles / _renderTurnsFromServer — the default view) injected
server-parsed `body_html` raw into the DOM. A crafted inbound email could
inject arbitrary markup (phishing/form/credential-capture/tracking; full XSS
if a deployment relaxes the script CSP). Now sanitized on all paths.
- Attachment extraction (routes/email_routes.py, routes/email_helpers.py): the
on-disk extraction dir was `ATTACHMENTS_DIR / f"{folder}_{uid}"` with
user-controlled folder/uid and no containment, so a folder like `../../tmp`
could escape ATTACHMENTS_DIR. New attachment_extract_dir() flattens both to a
single safe segment and asserts containment.
- Diagnostics routes (routes/diagnostics_routes.py): /api/db/stats,
/api/rag/stats, /api/test/youtube, /api/test-research relied only on the
global session check (any logged-in user). Now require_admin-gated.
- Defense-in-depth HTML escaping: session HTML export escapes the session name
(routes/session_routes.py); the MCP OAuth page escapes the reflected Host
header / server_id (routes/mcp_routes.py).
- Internal-tool token now compared with secrets.compare_digest (constant time)
in core/middleware.py and app.py.
Adds regression tests in tests/test_security_regressions.py.
77 lines
2.9 KiB
Python
77 lines
2.9 KiB
Python
"""Diagnostics routes — /api/db/stats, /api/rag/stats, /api/test/youtube, /api/test-research."""
|
|
|
|
import logging
|
|
from typing import Dict, Any
|
|
|
|
from fastapi import APIRouter, HTTPException, Form, Request
|
|
|
|
from services.youtube.youtube_handler import extract_youtube_id, extract_transcript_async
|
|
from core.constants import DEFAULT_HOST
|
|
from core.middleware import require_admin
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def setup_diagnostics_routes(
|
|
rag_manager,
|
|
rag_available: bool,
|
|
research_handler,
|
|
) -> APIRouter:
|
|
router = APIRouter(tags=["diagnostics"])
|
|
|
|
@router.get("/api/db/stats")
|
|
async def get_database_stats(request: Request) -> Dict[str, Any]:
|
|
require_admin(request)
|
|
try:
|
|
from core.database import get_detailed_stats
|
|
return get_detailed_stats()
|
|
except Exception as e:
|
|
logger.error(f"DB stats error: {e}")
|
|
raise HTTPException(500, "Failed to retrieve database statistics")
|
|
|
|
@router.get("/api/rag/stats")
|
|
async def get_rag_stats(request: Request) -> Dict[str, Any]:
|
|
require_admin(request)
|
|
if rag_available and rag_manager:
|
|
return rag_manager.get_stats()
|
|
return {"error": "RAG system not available"}
|
|
|
|
@router.get("/api/test/youtube")
|
|
async def test_youtube(request: Request, url: str) -> Dict[str, Any]:
|
|
require_admin(request)
|
|
try:
|
|
video_id = extract_youtube_id(url)
|
|
if not video_id:
|
|
return {"error": "Invalid YouTube URL"}
|
|
|
|
data = await extract_transcript_async(url, video_id)
|
|
return {
|
|
"video_id": video_id,
|
|
"transcript_success": data.get("success", False),
|
|
"transcript_length": len(data.get("transcript", "")) if data.get("success") else 0,
|
|
"transcript_preview": (data.get("transcript", "")[:500] + "...")
|
|
if data.get("success") and len(data.get("transcript", "")) > 500
|
|
else data.get("transcript", ""),
|
|
"error": data.get("error") if not data.get("success") else None,
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
@router.post("/api/test-research")
|
|
async def test_research(request: Request, query: str = Form("What is machine learning?")) -> Dict[str, Any]:
|
|
require_admin(request)
|
|
try:
|
|
endpoint = f"http://{DEFAULT_HOST}:8000/v1/chat/completions"
|
|
model = "gpt-oss-120b"
|
|
result = await research_handler.call_research_service(query, endpoint, model)
|
|
return {
|
|
"status": "success",
|
|
"query": query,
|
|
"result_preview": result[:200] + "..." if len(result) > 200 else result,
|
|
"result_length": len(result),
|
|
}
|
|
except Exception as e:
|
|
return {"status": "error", "error": str(e), "query": query}
|
|
|
|
return router
|