For InfoSec teams and app reviewers

Security at Peel

The short version: board content is encrypted in the browser with a key that lives in the link fragment, so our servers store and relay ciphertext they cannot read. Here is exactly how, and where the limits are.

Summary

  • Card text, names, board titles, votes and stickers are encrypted with AES-256-GCM under a board key generated in the browser. The key travels in the URL fragment (#k=) and is never sent to a server.
  • Facilitator actions, the board header and snapshots are signed with the facilitator’s Ed25519 key. The board id is derived from that key, so clients detect key substitution.
  • Hidden cards are sealed to the facilitator (X25519 sealed box) until the reveal, and bound by salted commitments so revealed text can’t be swapped.
  • Anonymous by default: operations carry no author id; votes and reactions use a per-board random id.
  • Participants never authenticate. No analytics SDKs, no ads, no third-party scripts except Paddle.js on the checkout page.
  • Cloudflare only. Unclaimed boards are deleted 60 days after the last visit.

How data flows

The fragment of a URL (everything after #) is handled by the browser and is not part of the HTTP request. Peel puts the board key there.

Data flow: the key is read locally from the link; only ciphertext and minimal metadata cross the network.

Threat model

We design so that a fully compromised or curious server, including us, learns as little as possible about what a team says.

What we protect

  • Card text, GIF choices and, in named mode, the author’s name.
  • Participant names and avatars, the board name and custom template titles.
  • Who voted or reacted on what, and who wrote which card.
  • The integrity of the retro: phases, reveals and facilitator decisions can’t be forged.

Adversaries we consider

  • Our own servers, a malicious insider, or someone who breaches our storage: they get ciphertext and metadata.
  • A network attacker: TLS everywhere; the key never crosses the network.
  • A server that swaps keys: the board id is derived from the facilitator’s public key, and clients verify the signed header.
  • A participant who wants to peek or forge: hidden cards are sealed to the facilitator; facilitator operations need the facilitator’s signature; card edits need the card’s key.
  • A facilitator republishing for an offline author: commitments stop them changing the text.

Out of scope, honestly

  • Anyone holding the participant link can read the board. The link is the access control. Share it like the board itself.
  • A compromised device, a malicious browser extension, or someone looking over your shoulder.
  • Traffic analysis: the server sees timing, sizes and counts (see the data map).
  • Denial of service by the server: it can drop or delay messages, but not read or forge them.

Cryptography

All primitives come from the browser’s WebCrypto API. No custom cryptography, no third-party crypto libraries.

KeyAlgorithmUsed for
K_boardAES-256-GCM, 96-bit random IVEncrypts every operation, snapshot and presence update. The additional data binds board id, operation type and operation id, so ciphertext can’t be replayed into another board or slot. Lives only in the link fragment #k=.
K_ownerEd25519The facilitator’s authority. Signs facilitator operations (phase, timer, spotlight, template, settings, opening votes, hiding a card), the board header and snapshots. A strictly increasing counter blocks replay. In the facilitator link only (&o=).
Board idSHA-256, first 80 bits, base32Derived from the facilitator’s public key, so the id itself proves which key owns the board. Clients reject a header whose key doesn’t hash to the id.
K_facX25519 sealed box: ephemeral X25519 + HKDF-SHA256 + AES-256-GCMHidden cards are sealed to the facilitator’s public key while people write. Only the facilitator link (&f=) can open them.
Card keysEd25519, one key per cardHeld only by the author. Edits and the reveal are signed with it: authenticity without identity.
CommitmentsHMAC-SHA256 with a 128-bit random saltA hidden card publishes a commitment to its content. At the reveal the content and salt are published and every client checks them, so nobody can swap the text.
PresenceAES-256-GCM under K_boardNames, avatars and typing indicators are encrypted like everything else.
Key bundleHKDF-SHA256 from passkey PRF or recovery code, AES-256-GCMSigned-in facilitators can sync board keys across devices. The bundle is encrypted on the device; the server stores only ciphertext and wrapped keys.

Signing messages are domain-separated (for example peel/sig/v1, peel/header/v1, peel/snap/v1) so a signature for one purpose can’t be reused for another.

Hidden cards and the reveal

  1. While writing, the author’s browser seals the card to the facilitator’s X25519 key and publishes a salted commitment. The plaintext stays on the author’s device.
  2. Other participants see a hidden card: they can’t decrypt it, and neither can the server.
  3. The facilitator’s browser can open sealed cards (the “👁 only you” preview).
  4. At the reveal, each author republishes the card encrypted with K_board, signed with the card key, together with the salt.
  5. If an author is offline, the facilitator republishes for them. Every client checks the commitment, so the text must match what the author wrote.

Because the facilitator can technically preview, every board shows a “facilitator can preview” label to everyone while cards are hidden. It is not a setting that can be hidden.

Anonymity

  • Operations carry no author id. The server can’t link a card to a person, and by default neither can the facilitator.
  • Votes and reactions are keyed by a random per-board id that isn’t linked to a name.
  • Typing indicators show that someone is writing, never what.
  • Named mode is an explicit board setting; the author’s name then travels inside the encrypted card.
  • Participants never sign in, so there is no account to correlate.

Data map

Everything we hold, where it lives, who can read it and how long it stays.

DataWhereReadable byKept
Cards, votes, reactions, stickers, actions, board name, custom template titlesDurable Object storage, as ciphertextPeople holding the board linkUntil the board is deleted
Operation envelope: type, server sequence and time, size, facilitator counterDurable Object storagePeelUntil the board is deleted
Participant names, avatars, typingRelayed as ciphertext; held in memory while connectedPeople holding the board linkWhile connected
Board status: phase, participant and card counts, catalogue template id, lock flag, created and expiry timesDurable Object, D1; Slack/Teams messages if installedPeel; your Slack/Teams channelBoard lifetime
Facilitator account: provider id, name, email if shared, linked providers, created date, sessionD1PeelUntil you delete the account
Key bundle and wrapped keysD1, as ciphertextOnly you (passkey or recovery code)Until you delete the account
Plan, subscription status, Paddle customer and subscription idsD1; PaddlePeel, PaddleAs tax law requires
Slack/Teams workspace, channel and message ids; bot token (AES-GCM encrypted)D1PeelUntil uninstalled
IP addressCloudflare edge, transiently (delivery, rate limits)Cloudflare, Peel’s rate limiterNot stored in our databases
Daily counters (boards created, phases reached)D1PeelAggregate only, no identifiers

What our server can and can’t do

It can

  • See operation types, sizes, timing, the phase, participant and card counts, the catalogue template id and the lock flag.
  • Drop, delay or refuse to deliver operations (denial of service).
  • See IP addresses at the network edge.
  • Count boards and phases reached, in aggregate.
  • Delete a board (expiry, abuse reports).

It can’t

  • Read card text, names, board titles, custom template titles, votes or stickers.
  • Read hidden cards before the reveal (not even after it).
  • Forge facilitator actions: they need the facilitator’s signature.
  • Swap in its own keys: the board id gives it away.
  • Change a card’s revealed text: commitments and card signatures give it away.
  • Tell who wrote which card.

As with any web app, you trust the code we serve. We keep that surface small: a strict Content Security Policy, script-src limited to our own origin, no third-party scripts, and the protocol is open to review on request.

Slack and Microsoft Teams

  • Messages carry the board link and metadata (title, phase, counts). Card text is sent only when the facilitator posts a summary and confirms it.
  • The Join card contains the participant link, which includes the key. Anyone who can read that channel can join, same as pasting a link there.
  • Link unfurls show phase and counts only, never content.
  • Slack scopes: commands, chat:write, links:read, links:write; users:read only when a team turns on mentions.
  • Bot tokens are encrypted at rest (AES-GCM, key held as a Worker secret). Slack request signatures and Bot Framework tokens are verified on every request.
  • Uninstalling the app stops all posting and deletes its tokens.

Web and infrastructure

  • Cloudflare only: Pages, Workers, Durable Objects, D1, R2 and KV on the 1801 Labs account. No other servers.
  • TLS everywhere, HSTS, and a strict Content Security Policy.
  • No analytics SDKs, no ads, no third-party scripts. The only exception is Paddle.js, loaded on the checkout page only.
  • One cookie, for signed-in facilitators only: peel_session, httpOnly, Secure, SameSite=Lax.
  • GIF search and media go through our API, so participants’ IP addresses never reach GIPHY.
  • Fonts and assets are self-hosted. No CDN fonts, no tracking pixels.
  • Rate limits per IP and per board; abuse reports and a facilitator lock.

Accounts and retention

  • Participants never authenticate. Facilitators sign in with Google (or Entra ID through Teams) only to keep boards or use paid features.
  • An account stores an id, the name and email from the provider, and an encrypted key bundle unlocked by a passkey (WebAuthn PRF) or a recovery code. Signing in gives ownership, not access to content.
  • Claiming a board proves ownership with a facilitator signature. Board keys are never sent.
  • Boards without an account are deleted 60 days after the last visit; each visit resets the clock. Durable Object alarms delete the storage.
  • Deleting your account deletes sessions, the key bundle and your library, and gives your boards the 60-day expiry back.

Responsible disclosure

Found something? Tell us first and we’ll work with you. We acknowledge reports within three working days.

[email protected]

  • Please give us reasonable time (we aim for 90 days) to fix before disclosing.
  • Test with your own boards and accounts. Don’t access other people’s data or degrade the service.
  • We won’t pursue legal action for good-faith research that follows these rules.
  • Machine-readable contact: /.well-known/security.txt.

Company plan customers get a security review pack (questionnaire answers, sub-processors, data flows). DPA · Privacy