unsloth/.github
Daniel Han 42ff23de73 Studio: fix HTML/SVG preview sanitizer, sandbox, and streaming gaps
Bundle of follow-ups to the HTML/SVG fence renderer landed earlier in
this PR. Each item came out of either the parallel reviewer pass or a
manual Playwright probe against the live Studio with an Anthropic
provider attached.

Sanitizer:
  - filter, mask, and clip-path are now in FORBID_ATTR. They accept
    url(https://...) values and the CSS engine still fetches that URL
    when the SVG renders, which previously slipped past the FORBID
    list.
  - href and xlink:href are no longer blanket-forbidden; they survive
    only when the value is a same-document fragment (href="#id"),
    which is what textPath, gradient, and use refs need. External
    schemes are dropped via a uponSanitizeAttribute hook so a beacon
    href cannot make it through.
  - The hook approach replaces DOMPurify's ALLOWED_URI_REGEXP, which
    also filtered presentation attrs (cx, cy, r, fill, width, height)
    and rendered circles with r=0.

SVG preview:
  - Inner stylesheet caps both max-width AND max-height so a square
    viewBox (200x200) scaled to the container width no longer
    overflows the fixed-height iframe and clips at the bottom.

HTML preview:
  - srcdoc carries a defense-in-depth meta-CSP (default-src 'none',
    connect-src 'none', frame-src 'none', img-src data: blob:,
    script-src 'self' 'unsafe-inline', style-src 'self' 'unsafe-inline').
    The host CSP already blocks inline scripts; this layer also blocks
    network egress, nested iframes, and form submission so a future
    host-CSP relaxation does not silently turn the preview into an
    exfiltration channel.
  - Sandbox grows allow-modals so alert/confirm/prompt are not
    silently no-oped if the host CSP ever permits inline scripts.
  - Pop-out spacer now uses the live HTML iframe height instead of
    hardcoded DEFAULT_PREVIEW_HEIGHT, so popping out a short preview
    does not leave a 500px hole in the chat bubble.
  - autoHeight resets on source change so a long-running session that
    swaps from a tall demo to a short one no longer keeps the previous
    iframe size during the gap before the new doc posts its height.

Streaming and a11y:
  - parseIncompleteCodeFence parses an in-flight open fence (no closing
    backticks yet). markdown-text falls back to it when streaming is
    incomplete, so the advertised isIncomplete -> Code-tab-lock path
    actually runs.
  - Tab buttons gain aria-controls / aria-labelledby wiring and a
    roving tabindex so the WAI-ARIA tab pattern is complete.
  - Pop-out modal gets role="dialog" and aria-modal.

Tooling:
  - vitest now runs in the Studio Frontend CI workflow so sanitizer or
    renderer regressions block the gate.
  - test-setup shims URL.createObjectURL / revokeObjectURL for jsdom in
    case future iframe work needs it.
  - frame-src in the host CSP is now declared explicitly as 'self' so
    a future change that loosens it leaves a visible diff for review.

Tests added: ARIA wiring, SVG height fit, srcdoc meta-CSP shape,
incomplete-fence helper, filter/mask/clip-path attr stripping, safe
fragment-href survival, external-href rejection. Vitest passes 21/21,
tsc -b and vite build are clean.
2026-05-24 16:05:11 +00:00
..
ISSUE_TEMPLATE Update issue template 2026-03-23 10:10:15 +05:30
scripts ci: route every hf download through xet-tuned stall-retry wrapper (#5476) 2026-05-15 21:11:56 -07:00
workflows Studio: fix HTML/SVG preview sanitizer, sandbox, and streaming gaps 2026-05-24 16:05:11 +00:00
CODEOWNERS security: NOT affected by Mini Shai-Hulud (May-12 wave) -- forward-looking hardening only (#5397) 2026-05-13 04:58:12 -07:00
dependabot.yml security: NOT affected by Mini Shai-Hulud (May-12 wave) -- forward-looking hardening only (#5397) 2026-05-13 04:58:12 -07:00
FUNDING.yml Update FUNDING.yml (#3792) 2025-12-28 19:57:43 -08:00