fix(preview): harden sandbox and transformation

This commit is contained in:
2026-08-26 03:34:52 -05:00
parent 656c14caf3
commit 7262e7730e
7 changed files with 125 additions and 17 deletions
+18 -5
View File
@@ -393,7 +393,15 @@ def _critique_shared(markup: str) -> list[str]:
issues: list[str] = []
lower = markup.lower()
if re.search(r"""(?i)(?:src|href)\s*=\s*['"]https?://""", markup):
external_attr = re.search(
r"""(?i)\b(?:src|srcset|href|xlink:href|poster|action|formaction|data)\b\s*=\s*(?:['"]\s*)?https?://""",
markup,
)
external_css = re.search(
r"""(?i)(?:url\s*\(\s*['"]?\s*https?://|@import\s+(?:url\s*\(\s*)?['"]?\s*https?://)""",
markup,
)
if external_attr or external_css:
issues.append(
"Remove external http(s) URLs — the sandboxed preview blocks them. "
"Inline CSS/JS; use data: URIs for images/fonts."
@@ -1014,14 +1022,15 @@ STANDING_TOOLS = frozenset({"render_preview"})
# User-message cues that justify running the (slow, non-stream) tool loop with
# render_preview. Kept narrow so ordinary chat isn't blocked behind a tool turn.
_RENDER_HINTS = (
"visual", "visualize", "visualization", "chart", "graph", "diagram",
"canvas", "plot", "interactive", "animation", "render_preview",
"visual", "visuals", "visualize", "visualization", "chart", "charts",
"graph", "graphs", "diagram", "diagrams", "canvas", "plot", "plots",
"interactive", "animation", "animations", "render_preview",
"render preview", "svg", "draw me", "live preview",
"demonstrate", "demo", "html demo", "html snippet", "html file",
# Ways of asking for something that reacts to the pointer. "interactive"
# alone missed "mouse-over sensitive", and with it the whole feature.
"hover", "mouse", "drag", "click on", "real-time", "realtime",
"simulation", "simulate", "particle", "animate",
"simulation", "simulations", "simulate", "particle", "particles", "animate",
# Every language the render window can display. Naming one is asking for a
# preview, and this way a language added to PREVIEW_LANGS starts hinting
# for itself instead of being unreachable until someone edits this tuple -
@@ -1031,8 +1040,12 @@ _RENDER_HINTS = (
def wants_render_preview(message: str) -> bool:
"""True when this turn should advertise render_preview / enter the tool loop."""
import re
lower = (message or "").lower()
return any(h in lower for h in _RENDER_HINTS)
return any(
re.search(rf"(?<![A-Za-z0-9_]){re.escape(hint)}(?![A-Za-z0-9_])", lower)
for hint in _RENDER_HINTS
)
def is_action(name: str) -> bool: