FIX: 11 bugs bảo mật/ổn định (static mount chặn dotfile, delete traversal, cleanup giữ clips serverFileId, upload whitelist, password strength, auth audio endpoints, pedalboard==0.9.19, vendor CDN local) + FEATURE: Carla bridge preview/export MIDI notes âm VSTi (POST /midi-render, /carla-play-notes, nút Preview VSTi/Export MIDI->Audio; pedalboard 0.9.19 raw MIDI bytes; SONICFORGE_STORAGE_DIR cô lập test storage)
This commit is contained in:
+21
-3
@@ -74,6 +74,8 @@ async def _resolve_host_ips(hostname: str):
|
||||
|
||||
|
||||
async def _validate_target_url(url: str, user_id: str):
|
||||
"""Validate target. Trả IP đã validate (str) để proxy connect thẳng vào đó
|
||||
chống DNS rebinding TOCTOU; None khi giữ hostname (loopback / https)."""
|
||||
parsed = urlparse(url)
|
||||
if parsed.scheme not in ("http", "https"):
|
||||
raise HTTPException(status_code=400, detail="URL chỉ hỗ trợ giao thức http/https")
|
||||
@@ -88,7 +90,7 @@ async def _validate_target_url(url: str, user_id: str):
|
||||
# Hostname-level fast path for loopback hosts
|
||||
if hostname in _LOOPBACK_HOSTS:
|
||||
if hostname in allowed_hosts:
|
||||
return
|
||||
return None
|
||||
raise HTTPException(status_code=403, detail="Target nội bộ không nằm trong danh sách AI provider đã cấu hình")
|
||||
|
||||
# Try direct IP parse (hostname may itself be an IP)
|
||||
@@ -109,18 +111,34 @@ async def _validate_target_url(url: str, user_id: str):
|
||||
continue
|
||||
raise HTTPException(status_code=403, detail="Target IP nội bộ không nằm trong danh sách AI provider đã cấu hình")
|
||||
|
||||
# Trả IP đầu tiên đã validate — http sẽ connect thẳng vào IP này (bind),
|
||||
# không cho httpx re-resolve hostname (fix DNS rebinding TOCTOU).
|
||||
return str(ips[0])
|
||||
|
||||
|
||||
@router.post("/proxy")
|
||||
async def proxy_llm(req: ProxyRequest, current_user: dict = Depends(get_current_user)):
|
||||
await _validate_target_url(req.url, current_user["user_id"])
|
||||
target_ip = await _validate_target_url(req.url, current_user["user_id"])
|
||||
# Never forward the app's own auth token upstream.
|
||||
headers = {
|
||||
k: v for k, v in req.headers.items()
|
||||
if k.lower() not in ("host", "origin", "referer", "x-auth-token")
|
||||
}
|
||||
url = req.url
|
||||
# Bug #6: http + hostname (không phải IP literal) → connect thẳng IP đã
|
||||
# validate, giữ Host gốc. https giữ hostname (SNI + cert validation chống
|
||||
# rebinding sẵn). IPv6 skip (netloc bracket phức tạp, hiếm gặp).
|
||||
if target_ip and ":" not in target_ip:
|
||||
parsed = urlparse(req.url)
|
||||
if parsed.scheme == "http":
|
||||
host_header = parsed.netloc
|
||||
new_netloc = parsed.netloc.replace(parsed.hostname, target_ip)
|
||||
from urllib.parse import urlunsplit
|
||||
url = urlunsplit((parsed.scheme, new_netloc, parsed.path, parsed.query, parsed.fragment))
|
||||
headers["Host"] = host_header
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=180.0, follow_redirects=False) as client:
|
||||
resp = await client.post(req.url, headers=headers, json=req.body)
|
||||
resp = await client.post(url, headers=headers, json=req.body)
|
||||
raw = resp.text
|
||||
try:
|
||||
return resp.json()
|
||||
|
||||
Reference in New Issue
Block a user