Skip to content

Security model for published decks

A published deck is code: HTML and JavaScript anyone can write and publish. So a deck must never run on the same origin as your account. It doesn’t:

  • Your account — the dashboard, your API tokens, sign-in — is on deck.kadal.cc.
  • Published decks are served from decks.kadal.cc, at decks.kadal.cc/@handle/slug/.

They are different origins, so a deck’s script cannot read your account’s cookies, call the account API as you, or reach into the dashboard. Opening a deck’s old deck.kadal.cc/@handle/slug/ address redirects to decks.kadal.cc, keeping the slide you linked to.

  • The session cookie is host-only (__Host-kd_session): the browser keeps it to deck.kadal.cc and never sends it to decks.kadal.cc. A deck has no ambient account authority at all.
  • The two are the same site (kadal.cc), so a top-level navigation a deck triggers to the account — a form, a window.open — can still carry the cookie. Every state-changing account request must come from deck.kadal.cc’s own pages, as JSON; a request a deck triggers names decks.kadal.cc (or no origin) and is refused.
  • The dashboard refuses to be framed, and a window a deck opens onto it loses its handle.

Because a deck is on its own ordinary origin, everything a deck needs just works there: its own cookies and storage, YouTube and other embeds, fonts, Web Workers, WebAssembly, fullscreen, the clipboard, camera and screen recording in the presenter view. A deck is also embeddable in your own site — its iframe loads from decks.kadal.cc and runs normally.

decks.kadal.cc has no account session, so the presenter proves who they are with a ticket: a signed string for one deck, valid 12 hours, kept in the page’s memory only.

  • Present and Presenter view on the deck’s dashboard page mint a ticket and open the deck with it in the address’s #fragment, which no server or Referer sees; the deck wipes it from the address bar on arrival.
  • A presenter passcode answers the passcode form with a ticket.
  • The presenter view passes its ticket to the windows it opens (S, the share window).

The live room takes the presenter’s role only from a ticket. No deck can borrow another deck’s presenter session, because there is no presenter cookie on the shared decks origin.

A viewing passcode gates the deck: its HTML and all its files. The cookie that unlocks a deck is scoped to that one deck’s path (/@handle/slug/), so it is never sent to another deck on decks.kadal.cc. Residual risk, worth knowing: the unlock is per browser, so a deck’s script could request a passcode deck the same browser has already unlocked, by asking for that deck’s own paths. Keep anything truly private off a shared link.

Found a way around any of this? Write to the address on the privacy page before telling anyone else; we’ll answer quickly.