Files
pad/web
xarmian bf37b59e71 fix(share): a shared item body is sanitized with the markdown allowlist, and the SPA CSP restricts form-action (BUG-3669) (#2145)
* fix(share): a shared item body is sanitized with the markdown allowlist, and the SPA CSP restricts form-action (BUG-3669)

The share page ran item bodies through DOMPurify's DEFAULT policy, on both
render paths (the marked pipeline and its fallback). That policy keeps
<form>, password inputs, action/method and inline style, so any editor of a
shared item could put a "Sign in to Pad" form on Pad's origin that posts to
another site. The collection inline-expand reads the same function, so it
had the same hole.

Both paths now use sanitizeMarkdownHtml, the allowlist every other rendered
body uses. Everything the share pipeline emits (attachment spans, images,
links, the unavailable-attachment placeholder) is within it. With no window
the page now renders '' instead of the raw HTML; the SPA has no SSR, so
that branch never ran.

The SPA CSP adds form-action 'self': default-src does not govern form
submission. Every SPA form submits through fetch (preventDefault), and the
OAuth consent and unsubscribe pages set their own policies.

The allowlist keeps <input> for task-list checkboxes, as in-app. With no
form, form attribute or script, an input cannot submit, so the test asserts
no form owner rather than no input.

Counterfactuals: main's share page fails both new tests ("a <form> reached
the page"). Reverting only the main path fails only its test, and reverting
only the fallback fails only its test. The main-path mutant has to keep the
DOMPurify import; without it the ReferenceError falls into the fixed fallback
and the test passes. Removing form-action fails the CSP test.

Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr

* test(share): the share-page guard asserts the markdown allowlist, not DOMPurify's default policy (BUG-3669)

#2145's first CI run failed TestSharePageDoesNotImportCollab: it required
the share route to import dompurify and call DOMPurify.sanitize(, the very
pipeline BUG-3669 replaces. Its message asks for exactly this update. It
now requires sanitizeMarkdownHtml (import and call), and fails if the
route calls DOMPurify.sanitize( at all, so a revert to the default policy
on either path fails the guard as well as the vitest leg. On main's
+page.svelte it fails on all three.

The comments that described the share pipeline as marked()+DOMPurify
(PublicCollectionView, PublicItemCard, PublicItemExpansion, shareRender,
the route itself) now name the allowlist. shareRender's own test
sanitizes with sanitizeMarkdownHtml, as the page does, so it tests the
inert pass on the markup the page actually feeds it.

Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr
2026-10-14 02:04:58 -04:00
..
2026-03-26 01:52:36 +00:00
2026-03-26 01:52:36 +00:00

Pad Web UI

SvelteKit 2 + Svelte 5 frontend for Pad, compiled to static files and embedded into the Go binary.

Development

npm install
npm run dev          # Dev server at localhost:5173 (proxies API to localhost:7777)
npm run build        # Production build to build/
npm run check        # Type checking with svelte-check

When developing, run the Go backend separately with make dev from the project root.

Building for Production

Do not build in isolation. Always use make build from the project root — this builds the web frontend, then compiles the Go binary with the build output embedded via //go:embed.

Stack

  • Svelte 5 with runes ($state, $derived, $effect)
  • SvelteKit 2 with adapter-static (SPA mode)
  • Tiptap block editor with markdown round-trip
  • svelte-dnd-action for drag-and-drop in board/list views
  • SSE for real-time updates
  • TypeScript throughout

Structure

src/
  routes/                    SvelteKit pages
    +layout.svelte           App shell (sidebar + main)
    +page.svelte             Landing/redirect
    [workspace]/
      +page.svelte           Dashboard (collections, phases, activity)
      +layout.svelte         SSE connection per workspace
      [collection]/
        +page.svelte         Collection view (board/list)
      [collection]/[item]/
        +page.svelte         Item detail + editor
      conventions/            Purpose-built conventions page
      playbooks/              Purpose-built playbooks page
      settings/               Workspace settings
  lib/
    api/client.ts            HTTP API client
    components/
      layout/                Sidebar, navigation
      editor/                Tiptap editor, raw markdown editor
      fields/                FieldEditor, relation picker
      items/                 ItemCard, ItemDetail
      collections/           BoardView, ListView
      common/                StatusBadge, badges, modals
      search/                CommandPalette
    stores/                  Svelte 5 reactive stores
      workspace.svelte.ts    Workspace state
      collections.svelte.ts  Collection + item state
      ui.svelte.ts           Sidebar, mobile state
    types/index.ts           TypeScript types and constants
  app.css                    Global styles and design tokens