diff --git a/studio/backend/core/inference/diffusion.py b/studio/backend/core/inference/diffusion.py index 1b1985b807..185c453507 100644 --- a/studio/backend/core/inference/diffusion.py +++ b/studio/backend/core/inference/diffusion.py @@ -330,7 +330,9 @@ def _display_repo_id(value: Any) -> Any: try: candidate = Path(value).expanduser() if candidate.is_absolute() or candidate.exists(): - return candidate.name or value + # Defense-in-depth: redact any hf_... pattern that survives + # in the leaf name before returning it to the UI / log line. + return _redact_hf_tokens(candidate.name or value) except (OSError, ValueError): pass return _redact_hf_tokens(value) diff --git a/studio/backend/routes/datasets.py b/studio/backend/routes/datasets.py index 9ac2edb44d..44d033b98e 100644 --- a/studio/backend/routes/datasets.py +++ b/studio/backend/routes/datasets.py @@ -336,19 +336,20 @@ async def upload_dataset( file: UploadFile, current_subject: str = Depends(get_current_subject), ) -> UploadDatasetResponse: - filename = _sanitize_filename(file.filename or "dataset_upload") - # Round 34 P1: mirror the seed.py multipart filename hardening so - # /api/datasets/upload also rejects control characters and embedded - # HF tokens. The reflected filename + stored_path are echoed back - # to the client and persisted, so the validators must match the - # JSON-side hardening on SeedInspectUploadRequest.filename. + # Validate the raw multipart filename BEFORE sanitization so smuggled + # control characters and embedded HF tokens are rejected at the same + # boundary as the JSON path; sanitizing first would silently strip + # control chars and let raw inputs pass the validator. + raw_filename = file.filename or "dataset_upload" from models.inference import _no_control_chars, _reject_embedded_hf_token try: - _no_control_chars(filename, "filename") - _reject_embedded_hf_token(filename, "filename") + _no_control_chars(raw_filename, "filename") + _reject_embedded_hf_token(raw_filename, "filename") except ValueError as exc: raise HTTPException(status_code = 400, detail = str(exc)) from exc + + filename = _sanitize_filename(raw_filename) ext = Path(filename).suffix.lower() if ext not in LOCAL_UPLOAD_EXTS: allowed = ", ".join(sorted(LOCAL_UPLOAD_EXTS)) diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index 547b50f567..a8082fff13 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -2498,6 +2498,12 @@ async def diffusion_load( # Round 28 P2 #15: AI Assist running (raised by # _release_chat_backend_for_diffusion) is retryable. or "AI Assist" in detail + # Backend mid-handoff race (raised by + # _raise_if_helper_advisor_busy_for_diffusion when + # another workload's public_load_pending is set) mirrors + # the route-level 503 at routes/inference.py:415, so the + # backend-surfaced phrasing must classify the same way. + or "Another GPU workload is mid-handoff" in detail ): # Round 17 P1 #2: chat unload failures raised by the # backend helper map to 503 (retryable infra issue),