From 12986c44c97979f0060c5d654e1ea28c51d0e89b Mon Sep 17 00:00:00 2001 From: Mina_ <147425861+Minamaged18@users.noreply.github.com> Date: Mon, 24 Aug 2026 00:10:57 +0300 Subject: [PATCH] fix(live-ws, dashboard): allow 0.0.0.0 dashboard origin and stop non-square SVG image warnings (#11269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on the combined 8-PR board: live-server-allowlist suite green within the board's 88/88, typecheck:core + dashboard-typecheck clean. 0.0.0.0 as a browser Origin is loopback-equivalent (it can never name a LAN host — same treatment as headroom/detect.ts and outboundUrlGuard.ts), so the local-only posture is unchanged and the FORBIDDEN_ORIGIN reconnect flood on the dev server's printed URL stops. Thank you @Minamaged18! --- .../cli-code/components/DefaultToolCard.tsx | 47 ++++++++----------- src/server/ws/liveServerAllowList.ts | 5 ++ src/shared/components/ProviderIcon.tsx | 36 +++++++++++--- src/shared/components/cli/CliToolCard.tsx | 9 +++- .../security/live-server-allowlist.test.ts | 3 ++ 5 files changed, 64 insertions(+), 36 deletions(-) diff --git a/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx b/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx index b7877dc05e..09446dfb16 100644 --- a/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx +++ b/src/app/(dashboard)/dashboard/cli-code/components/DefaultToolCard.tsx @@ -2,7 +2,6 @@ import { useEffect, useRef, useState, useCallback } from "react"; import { Card, Button, ModelSelectModal } from "@/shared/components"; -import Image from "next/image"; import { useTranslations } from "next-intl"; import { copyToClipboard } from "@/shared/utils/clipboard"; import { buildOpenCodeConfigDocument } from "@/shared/services/opencodeConfig"; @@ -643,38 +642,32 @@ export default function DefaultToolCard({ }; const renderIcon = () => { + // Tool SVGs are non-square (e.g. opencode is 234×42, cursor is 467×532). + // next/image's dev check warns whenever the rendered aspect-ratio size + // differs from the square width/height attributes, so these render as a + // plain capped at 32px on both axes — true ratio, no dev noise. + const renderImg = (src: string) => ( + // eslint-disable-next-line @next/next/no-img-element -- local static SVG asset + {tool.name} { + (e.currentTarget as HTMLElement).style.display = "none"; + }} + /> + ); if (tool.image) { - return ( - {tool.name} { - (e.currentTarget as HTMLElement).style.display = "none"; - }} - /> - ); + return renderImg(tool.image); } if (tool.imageLight || tool.imageDark) { const themedSrc = isDark ? tool.imageDark || tool.imageLight : tool.imageLight || tool.imageDark; - return ( - {tool.name} { - (e.currentTarget as HTMLElement).style.display = "none"; - }} - /> - ); + return renderImg(themedSrc); } if (tool.icon) { return ( diff --git a/src/server/ws/liveServerAllowList.ts b/src/server/ws/liveServerAllowList.ts index 9e3a91d598..1f3101f054 100644 --- a/src/server/ws/liveServerAllowList.ts +++ b/src/server/ws/liveServerAllowList.ts @@ -20,6 +20,11 @@ export const DEFAULT_ALLOWED_ORIGINS: readonly string[] = Object.freeze([ "http://127.0.0.1:20128", "http://localhost:20128", "http://[::1]:20128", + // 0.0.0.0 is the "unspecified" address but browsers treat it as loopback + // when the user pastes it into the address bar; the dashboard is reachable + // at http://0.0.0.0:20128 and its WS Origin is exactly that string. Same + // local-only posture as the entries above — it never refers to a LAN host. + "http://0.0.0.0:20128", ]); /** diff --git a/src/shared/components/ProviderIcon.tsx b/src/shared/components/ProviderIcon.tsx index 97c4e135aa..8cb101ad4e 100644 --- a/src/shared/components/ProviderIcon.tsx +++ b/src/shared/components/ProviderIcon.tsx @@ -401,34 +401,56 @@ const ProviderIcon = memo(function ProviderIcon({ className={className} style={{ display: "inline-flex", alignItems: "center", ...style }} > - */} + {providerId} setFailedAssets((current) => ({ ...current, [themedKey]: true }))} - unoptimized /> ); } - // Tier 2: Local SVG — fastest, cached separately from the JS bundle + // Tier 2: Local SVG — fastest, cached separately from the JS bundle. + // Rendered as a plain (not next/image): provider SVGs carry their own + // intrinsic aspect ratio (e.g. opencode.svg is 234×42), and next/image's + // dev-mode check warns whenever the layout size differs from the square + // width/height attributes — a false positive for non-square logos rendered + // at fixed icon sizes. We keep `width/height` attributes for layout reserve + // but let the intrinsic ratio win on both axes (`width/height: "auto"`) so + // wide logos like opencode render at their true aspect ratio instead of + // being letterboxed into a 1:1 box. if (hasSvg && !svgFailed) { return ( - {providerId} setFailedAssets((current) => ({ ...current, [svgKey]: true }))} - unoptimized /> ); diff --git a/src/shared/components/cli/CliToolCard.tsx b/src/shared/components/cli/CliToolCard.tsx index 4b027558c1..d741bc5f13 100644 --- a/src/shared/components/cli/CliToolCard.tsx +++ b/src/shared/components/cli/CliToolCard.tsx @@ -1,7 +1,6 @@ "use client"; import Link from "next/link"; -import Image from "next/image"; import { useTranslations } from "next-intl"; import type { CliCatalogEntry } from "@/shared/schemas/cliCatalog"; import type { ToolBatchStatus } from "@/shared/types/cliBatchStatus"; @@ -38,12 +37,18 @@ export default function CliToolCard({
{/* Icon / image */} {imageSrc ? ( - (not next/image): tool SVGs are non-square (opencode + // 234×42, cursor 467×532) and next/image's dev check warns whenever the + // rendered aspect-ratio size differs from the square width/height + // attributes. object-contain + max caps keep the logo at its true ratio. + // eslint-disable-next-line @next/next/no-img-element -- local static SVG asset + {tool.name} ) : ( { assert.equal(isOriginAllowed("http://127.0.0.1:20128", EMPTY_ENV), true); assert.equal(isOriginAllowed("http://localhost:20128", EMPTY_ENV), true); assert.equal(isOriginAllowed("http://[::1]:20128", EMPTY_ENV), true); + // 0.0.0.0 is loopback-equivalent in the browser; the dashboard is often + // opened at http://0.0.0.0:20128, which sends exactly that Origin on WS. + assert.equal(isOriginAllowed("http://0.0.0.0:20128", EMPTY_ENV), true); }); it("accepts an Origin matching LIVE_WS_ALLOWED_ORIGINS", () => {