From 256d17e2e12188b7a31f8e004a1320bca6c878d0 Mon Sep 17 00:00:00 2001 From: Darshan Poudel Date: Wed, 10 Jun 2026 13:17:31 +0545 Subject: [PATCH 01/93] fix(studio): block arbitrary external image URLs in markdown renderer (#5602) * fix(studio): block arbitrary external image URLs in markdown renderer Model-emitted tags were causing the browser to issue HTTP requests to arbitrary origins, leaking the user's IP address, User-Agent, and Referer header to any domain a prompt-injected model could emit (tracking-pixel vector, issue #5596). Add a urlTransform function passed to that only allows: - data: URIs (inline images, mermaid SVG, user attachments) - blob: URIs (locally generated object URLs) - relative paths without a scheme (same-origin assets) All other schemes (http:, https:, ftp:, etc.) return null, causing Streamdown to omit the element entirely. Existing iframes are already stripped by Streamdown's default sanitizer; event-handler attributes (onerror, onload, etc.) are also stripped by the default schema. * fix(studio): strip control chars and block backslash URL variants Two bypass vectors found after review: 1. Backslash-normalised URLs: \\attacker.com\pixel has no colon and does not start with // so the earlier guards allowed it as a relative path. Browsers normalise leading backslash pairs to // before resolving, so the request still reaches the external origin. 2. Embedded control characters: /\n/attacker.com passes trim() unchanged, startsWith("//") is false, and no-colon check passes it as relative. Browsers strip ASCII controls (U+0000-U+001F, U+007F) before URL resolution, so the value resolves to the attacker origin. Fix: strip all ASCII control characters from the raw URL before any guard, then block any URL whose normalized form starts with two chars from [/\\] to cover //, \\, /\, and \/ in one regex. * fix(studio): delegate non-image URLs to defaultUrlTransform Returning the raw URL for non-img nodes bypassed Streamdown's built-in link sanitization, allowing model-emitted javascript: hrefs to reach the DOM unfiltered. Pass non-image URLs through defaultUrlTransform so the library's own javascript:/data: sanitization stays active for links. * fix(studio): use scheme regex instead of includes() for colon check A colon anywhere in the URL (e.g. /api/image?id=model:v2 or /snapshots/2026-06-04T12:00:00Z.png) was incorrectly treated as an explicit scheme and the URL was dropped. Replace the includes(':') check with a proper scheme regex that only matches when a valid scheme token appears before any path separator. * Studio: shorten safeImageUrl comments in markdown renderer --------- Co-authored-by: Daniel Han --- .../components/assistant-ui/markdown-text.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/studio/frontend/src/components/assistant-ui/markdown-text.tsx b/studio/frontend/src/components/assistant-ui/markdown-text.tsx index fc547da0b1..ef9b33fced 100644 --- a/studio/frontend/src/components/assistant-ui/markdown-text.tsx +++ b/studio/frontend/src/components/assistant-ui/markdown-text.tsx @@ -13,7 +13,7 @@ import { HugeiconsIcon } from "@hugeicons/react"; import { createMathPlugin } from "@streamdown/math"; import { mermaid } from "@streamdown/mermaid"; import { useEffect, useMemo, useRef, useState } from "react"; -import { Block, type BlockProps, Streamdown } from "streamdown"; +import { Block, type BlockProps, Streamdown, defaultUrlTransform, type UrlTransform } from "streamdown"; import { createCodePlugin } from "./code-plugin"; import "katex/dist/katex.min.css"; import { AudioPlayer } from "./audio-player"; @@ -424,6 +424,22 @@ function useRafCoalescedText(text: string, isStreaming: boolean): string { return text; } +const safeImageUrl: UrlTransform = (url, _key, node) => { + // Only images are restricted; links/other nodes use the default transform. + if (node.tagName !== "img") return defaultUrlTransform(url, _key, node); + + // Strip ASCII controls first: browsers drop them mid-parse, so a value like + // "\t//attacker.com" would otherwise slip past the guards below. + // eslint-disable-next-line no-control-regex + const normalized = url.replace(/[\x00-\x1f\x7f]/g, "").trim(); + const lower = normalized.toLowerCase(); + + if (lower.startsWith("data:") || lower.startsWith("blob:")) return normalized; + if (/^[/\\]{2}/.test(normalized)) return null; // protocol-relative: // \\ /\ \/ + if (/^[a-zA-Z][a-zA-Z0-9+\-.]*:/.test(normalized)) return null; // scheme prefix (colon later in path is fine) + return normalized; // relative -> same-origin +}; + const MarkdownTextImpl = () => { const { text, status } = useMessagePartText(); const displayText = useRafCoalescedText(text, status.type === "running"); @@ -444,6 +460,7 @@ const MarkdownTextImpl = () => { isAnimating={status.type === "running"} plugins={{ code, math, mermaid }} components={STREAMDOWN_COMPONENTS} + urlTransform={safeImageUrl} controls={{ code: false, mermaid: { From 4d2f29ff2af0e67341246fff898c1414c57e9ef0 Mon Sep 17 00:00:00 2001 From: Michael Han <107991372+shimmyshimmer@users.noreply.github.com> Date: Wed, 10 Jun 2026 01:57:16 -0700 Subject: [PATCH 02/93] Studio: center account avatar vertically in sidebar footer pill (#6026) Co-authored-by: shimmyshimmer --- studio/frontend/src/components/app-sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/frontend/src/components/app-sidebar.tsx b/studio/frontend/src/components/app-sidebar.tsx index 9cb28a198b..2a633aeaf9 100644 --- a/studio/frontend/src/components/app-sidebar.tsx +++ b/studio/frontend/src/components/app-sidebar.tsx @@ -1156,7 +1156,7 @@ export function AppSidebar() { aria-label={t("shell.accountMenu", { name: displayTitle })} className="sidebar-nav-btn !h-[44px] gap-[9px] px-2 py-[3px] rounded-[14px]" > -
+
Date: Wed, 10 Jun 2026 02:17:21 -0700 Subject: [PATCH 03/93] fix: clearer Studio setup error when GPU driver is too old for the installed CUDA toolkit (#5993) * fix: clearer Studio setup error when GPU driver is too old for the installed CUDA toolkit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Honor optional color arg in setup.sh substep so driver/toolkit warnings render in C_WARN * Add regression test that setup.sh _cuda_version_gt compares numerically for PR #5993 * Update Resolve-CudaToolkit test for the new driver-too-old messaging setup.ps1 now routes the too-new-toolkit case through Write-CudaDriverToolkitMismatch instead of the old 'is installed but INCOMPATIBLE' banner. Extract that helper alongside Resolve-CudaToolkit so the child pwsh can run it, and assert the new driver-too-old guidance (and the one-line source-build error) instead of the removed INCOMPATIBLE text. * Address review nits: document Windows hard-exit asymmetry and add toolkit/driver edge tests setup.ps1: note that only a forced source build reaches the hard-exit branch (the prebuilt path returned above), unlike setup.sh which degrades to CPU. test_selection_logic.py: cover the CUDA UMD Version variant, the empty nvcc version guard, and the too_old (< 12.4) short-circuit. * fix(studio): allow CUDA minor-version compat and try installed toolkits before CPU fallback The driver check now compares CUDA major versions only, per NVIDIA minor-version compatibility, and when the selected nvcc is still too new the setup iterates other installed toolkits and uses the newest driver-compatible one before falling back to a CPU llama.cpp build. Same rule mirrored in setup.ps1. * style: apply ruff kwarg-spacing format after rebase * Accept a same-major CUDA toolkit found only on PATH in the Windows fallback The major-only compatibility fix updated the side-by-side scan (Find-Nvcc -MaxVersion) and the CUDA_PATH check, but the fallback that runs when Find-Nvcc -MaxVersion returns null still recorded any plain Find-Nvcc result as an incompatible toolkit without re-checking the major. A same-major toolkit discoverable only via PATH, process CUDA_PATH, or a custom location (e.g. toolkit 13.3 with a driver supporting CUDA 13.2) was therefore rejected even though it is compatible. Re-apply the same major-only rule in the fallback: use the toolkit when its major is within the driver's, otherwise record it as too-new. Adds a regression test covering the PATH-only same-major case. * Add CUDA driver/toolkit selection edge-case tests for Studio setup * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Tighten comments in Studio CUDA driver/toolkit setup Collapse multi-line comments, drop obvious ones, keep the load-bearing intent (the major-compat invariant, the Windows hard-exit vs setup.sh-CPU asymmetry, the PATH-only fallback rationale). Comment-only; no code change. * Clarify the Windows source-build hard-exit comment The path is reached by any committed source build (forced, or after a prebuilt-install failure), not only a forced one. Comment-only. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Drop two obvious comments in setup.ps1 CUDA detection --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Daniel Han --- studio/setup.ps1 | 77 ++-- studio/setup.sh | 182 ++++++-- tests/studio/install/test_selection_logic.py | 443 +++++++++++++++++++ tests/studio/test_resolve_cuda_toolkit.ps1 | 69 ++- 4 files changed, 685 insertions(+), 86 deletions(-) diff --git a/studio/setup.ps1 b/studio/setup.ps1 index 4d47010136..920d1af13a 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -220,14 +220,10 @@ function Get-InstalledLlamaPrebuiltRelease { function Find-Nvcc { param([string]$MaxVersion = "") - # If MaxVersion is set, we need to find a toolkit <= that version. - # CUDA toolkits install side-by-side under C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\vX.Y\ - $toolkitBase = 'C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA' if ($MaxVersion -and (Test-Path $toolkitBase)) { $drMajor = [int]$MaxVersion.Split('.')[0] - $drMinor = [int]$MaxVersion.Split('.')[1] # Get all installed CUDA dirs, sorted descending (highest first) $cudaDirs = Get-ChildItem -Directory $toolkitBase | Where-Object { @@ -236,8 +232,8 @@ function Find-Nvcc { foreach ($dir in $cudaDirs) { if ($dir.Name -match '^v(\d+)\.(\d+)') { - $tkMajor = [int]$Matches[1]; $tkMinor = [int]$Matches[2] - $compatible = ($tkMajor -lt $drMajor) -or ($tkMajor -eq $drMajor -and $tkMinor -le $drMinor) + $tkMajor = [int]$Matches[1] + $compatible = ($tkMajor -le $drMajor) if ($compatible) { $nvcc = Join-Path $dir.FullName 'bin\nvcc.exe' if (Test-Path $nvcc) { @@ -278,6 +274,19 @@ function Find-Nvcc { return $null } +function Write-CudaDriverToolkitMismatch { + param( + [Parameter(Mandatory = $true)][string]$ToolkitVersion, + [Parameter(Mandatory = $true)][string]$DriverMaxCuda, + [string]$Color = "Yellow" + ) + $toolkitMajor = $ToolkitVersion.Split('.')[0] + $driverMajor = $DriverMaxCuda.Split('.')[0] + substep "CUDA Toolkit $ToolkitVersion is a major-version mismatch: toolkit major $toolkitMajor exceeds driver CUDA major $driverMajor ($DriverMaxCuda)." $Color + substep "Update the NVIDIA GPU driver to run CUDA Toolkit $ToolkitVersion, or install a CUDA $driverMajor.x toolkit." $Color + substep "Or let Studio use the prebuilt CUDA bundle; it does not need the local toolkit." $Color +} + # Detect CUDA Compute Capability via nvidia-smi. # Returns e.g. "80" for A100 (8.0), "89" for RTX 4090 (8.9), etc. # Returns $null if detection fails. @@ -1062,17 +1071,13 @@ if ($vsResult) { # or installed. Without it, detection is best-effort and only sets the flag. function Resolve-CudaToolkit { param([switch]$RequireOrExit) -# IMPORTANT: The CUDA Toolkit version must be <= the max CUDA version the -# NVIDIA driver supports. nvidia-smi reports this as "CUDA Version: X.Y". -# If we install a toolkit newer than the driver supports, llama-server will -# fail at runtime with "ggml_cuda_init: failed to initialize CUDA: (null)". +# Toolkit major must be <= the driver's max CUDA major (nvidia-smi "CUDA Version: X.Y"); +# a newer-major toolkit fails at runtime ("ggml_cuda_init: failed to initialize CUDA"). -# -- Detect max CUDA version the driver supports -- $DriverMaxCuda = $null try { $smiOut = & $NvidiaSmiExe 2>&1 | Out-String - # Newer NVIDIA drivers (e.g. 610.x) report the driver max CUDA as - # "CUDA UMD Version: X.Y" rather than "CUDA Version: X.Y"; accept both. + # Newer drivers report "CUDA UMD Version: X.Y" instead of "CUDA Version: X.Y"; accept both. if ($smiOut -match "CUDA(?: UMD)? Version:\s+([\d]+)\.([\d]+)") { $DriverMaxCuda = "$($Matches[1]).$($Matches[2])" substep "driver supports up to CUDA $DriverMaxCuda" @@ -1096,7 +1101,6 @@ $NvccPath = $null if ($DriverMaxCuda) { $drMajorCuda = [int]$DriverMaxCuda.Split('.')[0] - $drMinorCuda = [int]$DriverMaxCuda.Split('.')[1] # --- Step 1: Check existing CUDA_PATH first --- $existingCudaPath = [Environment]::GetEnvironmentVariable('CUDA_PATH', 'Machine') @@ -1108,7 +1112,7 @@ if ($DriverMaxCuda) { $verOut = & $candidateNvcc --version 2>&1 | Out-String if ($verOut -match 'release\s+(\d+)\.(\d+)') { $tkMaj = [int]$Matches[1]; $tkMin = [int]$Matches[2] - $isCompat = ($tkMaj -lt $drMajorCuda) -or ($tkMaj -eq $drMajorCuda -and $tkMin -le $drMinorCuda) + $isCompat = ($tkMaj -le $drMajorCuda) if ($isCompat) { # Also verify the toolkit supports our GPU architecture $archOk = $true @@ -1124,7 +1128,7 @@ if ($DriverMaxCuda) { substep "using existing CUDA Toolkit at CUDA_PATH (nvcc: $NvccPath)" } } else { - substep "CUDA_PATH ($existingCudaPath) has CUDA $tkMaj.$tkMin which exceeds driver max $DriverMaxCuda" "Yellow" + substep "CUDA_PATH ($existingCudaPath) has CUDA $tkMaj.$tkMin with major $tkMaj, which exceeds driver CUDA major $drMajorCuda ($DriverMaxCuda)" "Yellow" } } } @@ -1141,12 +1145,19 @@ if ($DriverMaxCuda) { } } } else { - # Check if there's an incompatible (too new) toolkit installed + # No side-by-side match: a major-compatible toolkit may still be on + # PATH/CUDA_PATH/a custom dir; use it, else record it as too-new. $AnyNvcc = Find-Nvcc if ($AnyNvcc) { $NvccOut = & $AnyNvcc --version 2>&1 | Out-String - if ($NvccOut -match "release\s+([\d]+\.[\d]+)") { - $IncompatibleToolkit = $Matches[1] + if ($NvccOut -match "release\s+(\d+)\.(\d+)") { + $tkMaj = [int]$Matches[1]; $tkMin = [int]$Matches[2] + if ($tkMaj -le $drMajorCuda) { + $NvccPath = $AnyNvcc + substep "found compatible CUDA Toolkit (nvcc: $NvccPath)" + } else { + $IncompatibleToolkit = "$tkMaj.$tkMin" + } } } } @@ -1155,26 +1166,18 @@ if ($DriverMaxCuda) { $NvccPath = Find-Nvcc } -# -- If incompatible toolkit is blocking, tell user to uninstall it -- +# A newer-major toolkit blocked by the driver: explain the mismatch. if (-not $NvccPath -and $IncompatibleToolkit) { + Write-CudaDriverToolkitMismatch -ToolkitVersion $IncompatibleToolkit -DriverMaxCuda $DriverMaxCuda if (-not $RequireOrExit) { - substep "CUDA Toolkit $IncompatibleToolkit exceeds driver max $DriverMaxCuda -- skipping; prebuilt llama.cpp needs no local toolkit" "Yellow" $script:CudaToolkitReady = $false return } + # Reached only by a source build (forced, or after a prebuilt-install failure); + # with no compatible toolkit it must fail (setup.sh degrades to CPU instead). Write-Host "" -ForegroundColor Red Write-Host "========================================================================" -ForegroundColor Red - Write-Host "[ERROR] CUDA Toolkit $IncompatibleToolkit is installed but INCOMPATIBLE" -ForegroundColor Red - Write-Host " with your NVIDIA driver (which supports up to CUDA $DriverMaxCuda)." -ForegroundColor Red - Write-Host "" -ForegroundColor Red - Write-Host " This will cause 'failed to initialize CUDA' errors at runtime." -ForegroundColor Red - Write-Host "" -ForegroundColor Red - Write-Host " To fix:" -ForegroundColor Yellow - Write-Host " 1. Open Control Panel -> Programs -> Uninstall a program" -ForegroundColor Yellow - Write-Host " 2. Uninstall 'NVIDIA CUDA Toolkit $IncompatibleToolkit'" -ForegroundColor Yellow - Write-Host " 3. Re-run setup.bat (it will install CUDA $DriverMaxCuda automatically)" -ForegroundColor Yellow - Write-Host "" -ForegroundColor Yellow - Write-Host " Alternatively, update your NVIDIA driver to one that supports CUDA $IncompatibleToolkit." -ForegroundColor Gray + Write-Host "[ERROR] CUDA source build cannot use the installed toolkit with this driver." -ForegroundColor Red Write-Host "========================================================================" -ForegroundColor Red exit 1 } @@ -1187,7 +1190,6 @@ if (-not $NvccPath -and $RequireOrExit) { if ($DriverMaxCuda) { # Query winget for available CUDA Toolkit versions $drMajor = [int]$DriverMaxCuda.Split('.')[0] - $drMinor = [int]$DriverMaxCuda.Split('.')[1] $AvailableVersions = @() try { $rawOutput = winget show Nvidia.CUDA --versions --accept-source-agreements 2>&1 | Out-String @@ -1200,13 +1202,12 @@ if (-not $NvccPath -and $RequireOrExit) { } } catch {} - # Filter to compatible versions (<= driver max) and pick the highest + # Filter to compatible major versions and pick the highest $BestVersion = $null foreach ($ver in $AvailableVersions) { $parts = $ver.Split('.') $vMajor = [int]$parts[0] - $vMinor = [int]$parts[1] - if ($vMajor -lt $drMajor -or ($vMajor -eq $drMajor -and $vMinor -le $drMinor)) { + if ($vMajor -le $drMajor) { $BestVersion = $ver break # list is descending, first match is highest compatible } @@ -1224,7 +1225,7 @@ if (-not $NvccPath -and $RequireOrExit) { substep "CUDA Toolkit $BestVersion installed (nvcc: $NvccPath)" } } else { - substep "no compatible CUDA Toolkit version found in winget (need <= $DriverMaxCuda)" "Yellow" + substep "no compatible CUDA Toolkit version found in winget (need CUDA major <= $drMajor)" "Yellow" } } else { substep "Installing CUDA Toolkit (latest) via winget..." @@ -1246,7 +1247,7 @@ if (-not $NvccPath) { } Write-Host "[ERROR] CUDA Toolkit (nvcc) is required but could not be found or installed." -ForegroundColor Red if ($DriverMaxCuda) { - Write-Host " Install CUDA Toolkit $DriverMaxCuda from https://developer.nvidia.com/cuda-toolkit-archive" -ForegroundColor Yellow + Write-Host " Install a CUDA Toolkit with major version $($DriverMaxCuda.Split('.')[0]) from https://developer.nvidia.com/cuda-toolkit-archive" -ForegroundColor Yellow } else { Write-Host " Install CUDA Toolkit from https://developer.nvidia.com/cuda-downloads" -ForegroundColor Yellow } diff --git a/studio/setup.sh b/studio/setup.sh index 9b29def859..0db9a612c8 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -59,8 +59,9 @@ fi # ── Output helpers ── # Consistent column layout: 2-space indent, 15-char label (fits llama-quantize), then value. # Usage: step