Skip to content

Conversation

@Spherrrical
Copy link
Collaborator

This demo is a template Chat SDK provided by Vercel, and repurposed to support Plano as the model gateway

@Spherrrical Spherrrical marked this pull request as draft January 8, 2026 23:25
<iframe
className={cn("size-full", className)}
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
src={(src ?? url) || undefined}

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 2 days ago

In general, the fix is to ensure that untrusted text taken from the DOM is not directly used in a dangerous context without validation or sanitization. For URLs, this typically means: (1) validating that the value is a well‑formed URL, and (2) restricting allowed schemes to safe ones such as http: and https: before using it in attributes like src, or else rejecting/clearing it.

For this component, the safest minimal change without altering the public API is:

  1. Add a small helper to validate and normalize URLs, allowing only http and https schemes.
  2. Use this helper in handleUrlChange before storing the URL in state; if the URL is invalid or uses a disallowed scheme, we can set url to an empty string (or leave the previous URL). This prevents dangerous values from propagating to the iframe src.
  3. Additionally, tighten the sandbox attribute on the iframe by removing allow-scripts and allow-same-origin so that even if a URL slips through, it runs with much lower privileges. This change is self-contained and doesn’t change how the component is used.

Concretely, in demos/use_cases/vercel-ai-sdk/components/elements/web-preview.tsx:

  • Introduce a sanitizeUrl function near the top of the file (after imports) that:
    • Attempts to construct a new URL(value, window.location.origin) (inside a try/catch).
    • Checks url.protocol and only returns the input when it starts with http: or https:.
    • Returns an empty string (or undefined) otherwise.
  • Update handleUrlChange (line 53 onwards) so that it calls sanitizeUrl and only updates url (and calls onUrlChange) with the sanitized value.
  • Optionally, also sanitize in WebPreviewBody when using src ?? url to be defensive, but the primary sanitization at the setter is usually enough.
  • Update the sandbox attribute on the iframe to a stricter set, e.g. sandbox="allow-forms allow-popups allow-presentation" (or even stricter depending on needs).

No new external dependencies are required; we can rely on the standard URL API in the browser.

Suggested changeset 1
demos/use_cases/vercel-ai-sdk/components/elements/web-preview.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/demos/use_cases/vercel-ai-sdk/components/elements/web-preview.tsx b/demos/use_cases/vercel-ai-sdk/components/elements/web-preview.tsx
--- a/demos/use_cases/vercel-ai-sdk/components/elements/web-preview.tsx
+++ b/demos/use_cases/vercel-ai-sdk/components/elements/web-preview.tsx
@@ -18,6 +18,20 @@
 } from "@/components/ui/tooltip";
 import { cn } from "@/lib/utils";
 
+const sanitizeUrl = (value: string): string => {
+  if (!value) return "";
+  try {
+    const url = new URL(value, window.location.origin);
+    const protocol = url.protocol.toLowerCase();
+    if (protocol === "http:" || protocol === "https:") {
+      return url.toString();
+    }
+  } catch {
+    // Invalid URL, fall through and return empty string
+  }
+  return "";
+};
+
 export type WebPreviewContextValue = {
   url: string;
   setUrl: (url: string) => void;
@@ -51,8 +65,9 @@
   const [consoleOpen, setConsoleOpen] = useState(false);
 
   const handleUrlChange = (newUrl: string) => {
-    setUrl(newUrl);
-    onUrlChange?.(newUrl);
+    const safeUrl = sanitizeUrl(newUrl);
+    setUrl(safeUrl);
+    onUrlChange?.(safeUrl);
   };
 
   const contextValue: WebPreviewContextValue = {
@@ -170,7 +185,7 @@
     <div className="flex-1">
       <iframe
         className={cn("size-full", className)}
-        sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
+        sandbox="allow-forms allow-popups allow-presentation"
         src={(src ?? url) || undefined}
         title="Preview"
         {...props}
EOF
@@ -18,6 +18,20 @@
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";

const sanitizeUrl = (value: string): string => {
if (!value) return "";
try {
const url = new URL(value, window.location.origin);
const protocol = url.protocol.toLowerCase();
if (protocol === "http:" || protocol === "https:") {
return url.toString();
}
} catch {
// Invalid URL, fall through and return empty string
}
return "";
};

export type WebPreviewContextValue = {
url: string;
setUrl: (url: string) => void;
@@ -51,8 +65,9 @@
const [consoleOpen, setConsoleOpen] = useState(false);

const handleUrlChange = (newUrl: string) => {
setUrl(newUrl);
onUrlChange?.(newUrl);
const safeUrl = sanitizeUrl(newUrl);
setUrl(safeUrl);
onUrlChange?.(safeUrl);
};

const contextValue: WebPreviewContextValue = {
@@ -170,7 +185,7 @@
<div className="flex-1">
<iframe
className={cn("size-full", className)}
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
sandbox="allow-forms allow-popups allow-presentation"
src={(src ?? url) || undefined}
title="Preview"
{...props}
Copilot is powered by AI and may make mistakes. Always verify output.
Spherrrical and others added 3 commits January 8, 2026 15:31
…as HTML

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants