Skip to content

In-app HTML file viewing (sandboxed iframe)#4

Merged
angusbezzina merged 7 commits into
mainfrom
angus/html-display
May 29, 2026
Merged

In-app HTML file viewing (sandboxed iframe)#4
angusbezzina merged 7 commits into
mainfrom
angus/html-display

Conversation

@angusbezzina
Copy link
Copy Markdown
Collaborator

@angusbezzina angusbezzina commented May 28, 2026

Summary

Adds an in-app viewer for .html/.htm files, alongside the existing markdown/image/video/audio viewers — primarily to showcase AI-generated HTML (inline JS, custom web fonts, CDN animation libraries). Files render in a native <iframe sandbox="allow-scripts"> loaded via the existing attn:// custom protocol.

  • Zero new dependencies → no binary-size impact. Release binary 30.20 MiB (< 32 MiB gate).
  • Planning docs: planning/one-pager.md, planning/complete-plan.md. Work tracked as Beads epic attn-zb5 (closed).

What's included

Backend (src/)

  • FileType::Html + .html/.htm detection; is_previewable() now shared by both tree-filter sites (removes a drift hazard); mime_from_extension lowercased + font/wasm/avif types.
  • Per-session IPC capability token (getrandom) injected only into the main app frame and required on privileged IPC (ipc_message_authorized, unit-tested).
  • Subframe bridge guard injected into all frames (with_initialization_script_for_main_only(_, false)) — strips window.webkit/window.ipc inside iframes.
  • CSP on .html responses: allows remote fonts/CDN libs (https:/data:) but blocks local-file fetch() (no attn: in connect-src). Access-Control-Allow-Origin: * scoped to markdown/text only so a sandboxed iframe can't canvas-read local images.
  • Query-string strip on the file-serve branch (enables ?v=<mtime> cache-busting).

Frontend (web/)

  • 'html' in the FileType union + extension map + icon; new HtmlViewer.svelte; routed in App.svelte.
  • Live-reload: a reactive SvelteMap of file mtimes feeds a ?v= cache-bust so on-disk edits refresh the iframe.

Security model

Three independent controls let rich, styled JS pages run while staying safe:

  1. Sandboxallow-scripts without allow-same-origin (opaque origin): scripts run, but can't touch the app DOM/storage, navigate top, popups, or forms.
  2. IPC bridge fencing — subframe guard + a main-frame-only capability token on privileged IPC. E2E confirmed the native WebKit bridge IS reachable from the iframe on macOS, so the token is the load-bearing control — and it blocks file-writing IPC.
  3. CSP — remote fonts/CDN libs allowed; local-file fetch() and cross-origin image reads blocked.

Verification

  • cargo test413 passed, 0 failed (incl. the token-gate unit tests).
  • cd web && npm run check0 errors; npm test — pass.
  • Live E2E on the running app: iframe renders; an attack page's edit_save/quit are rejected by the token (file byte-identical, daemon survives); editing the file on disk live-reloads the view; the app's own navigation/search IPC works.
  • task check:sizePASS, 30.20 MiB < 32 MiB.
  • Adversarially reviewed by a multi-agent workflow; all 9 confirmed findings fixed and re-verified.

Out of scope (v1)

A strict offline-only/no-network mode (future per-file toggle), editing HTML, and comments/collab on HTML (the review layer is markdown-anchored).

🤖 Generated with Claude Code

One-pager + complete plan for displaying/presenting AI-generated HTML
files in attn via a sandboxed iframe over the existing attn:// protocol
(zero new deps). Security model: iframe sandbox + IPC-bridge fencing
(subframe guard + main-frame capability token) + a CSP that allows
external fonts/CDN animation libs while blocking local-file exfiltration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
attn Ready Ready Preview, Comment May 28, 2026 9:54pm

Request Review

Hierarchical epic attn-zb5 (1 epic, 4 features, 15 tasks) derived from
planning/{one-pager,complete-plan}.md. Graph: backend plumbing (F1) and
sandbox hardening (F3) run in parallel; the frontend viewer (F2) depends
on both so allow-scripts never ships before the IPC bridge is fenced;
live reload (F4) depends on F2. Every impl task carries a Verification
section; every feature ends with an integration/E2E validation task.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render .html/.htm in a native <iframe sandbox="allow-scripts"> over the
existing attn:// protocol, so AI-generated HTML (inline JS, custom fonts,
CDN animation libs) displays in-app. Zero new dependencies; release binary
30.20 MiB (< 32 MiB gate).

Backend (src/): FileType::Html + detection; is_previewable() shared by both
tree filters; mime_from_extension lowercased + font/wasm types; per-session
IPC capability token (getrandom) injected into the main frame only and
required on privileged IPC (ipc_message_authorized, unit-tested); a
subframe bridge guard injected into all frames (window.webkit/ipc removed in
iframes); query-string strip on the file-serve branch for cache-busting; CSP
on .html responses allowing remote fonts/CDN libs but blocking local-file
fetch; ACAO scoped to markdown/text so a sandboxed iframe can't canvas-read
local images.

Frontend (web/): 'html' FileType + extension map + icon; new HtmlViewer.svelte;
routed in App.svelte; live-reload via a reactive SvelteMap of mtimes feeding a
?v= cache-bust; DirectoryOverview html count.

Verified: cargo test (413 ok), svelte-check (0 errors), web unit tests, and a
live E2E — iframe renders, an attack page's edit_save/quit are rejected by the
token (the native WebKit bridge is reachable on macOS, so the token is the
load-bearing control), and editing the file on disk live-reloads the view.
Adversarially reviewed by a multi-agent workflow; all 9 confirmed findings
addressed. Implements epic attn-zb5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@angusbezzina angusbezzina changed the title Planning: in-app HTML file viewing In-app HTML file viewing (sandboxed iframe) May 28, 2026
Drop HtmlViewer's bespoke header bar so HTML files share the exact same
PathBreadcrumb chrome as every other file type. Add an "Open in browser"
icon button (Lucide external-link) to the breadcrumb's top-right cluster,
shown for HTML files (where the Share button sits for markdown). Verified:
plan.html renders in the sandboxed iframe with the breadcrumb header, 1
open-in-browser button, 0 share buttons, and no leftover header bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Center the header action buttons vertically (-mt-3): the 27px button
  previously overflowed the 40px header by 2px (15px above / -2px below);
  now ~6px above and below.
- Hide the iframe's chunky native scrollbar to match the app's clean look.
  Cross-origin sandboxed content can't be scrolled by attn's ScrollArea
  (content height isn't measurable) nor styled, so the iframe is widened by
  the measured platform scrollbar width inside an overflow-hidden wrapper —
  the native bar falls in the clipped gutter (0 for overlay scrollbars).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The current .beads/.gitignore predated the Dolt sql-server runtime files, so
.beads-credential-key (a secret), dolt-server.{lock,log,pid,port}, and backup/
showed up as untracked — the credential key especially must never be committed.
Add ignore patterns for them. Also commits the tracked interactions.jsonl
(bd's shared audit log) updated this session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@angusbezzina angusbezzina marked this pull request as ready for review May 28, 2026 21:47
- web/src/lib/ipc.ts: the module-level capability-token read touched `window`
  at import time, crashing the Node test env ("window is not defined") and
  failing 6 component test files. Guard it (and send()) with `typeof window`.
- src/main.rs: use the `['?', '#']` array char pattern instead of a closure
  (clippy::manual_pattern_char_comparison under -D warnings).
- cargo fmt across src/ (main.rs/ipc.rs from this feature; daemon.rs/manager.rs/
  ws.rs were pre-existing drift the fmt --check gate also flagged).

Verified: cargo fmt --check clean, cargo clippy --all-targets -D warnings clean,
web npm test 31 files / 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@angusbezzina angusbezzina merged commit fad78ad into main May 29, 2026
6 checks passed
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.

1 participant