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.
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.
| Key | Algorithm | Used for |
|---|---|---|
| K_board | AES-256-GCM, 96-bit random IV | Encrypts 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_owner | Ed25519 | The 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 id | SHA-256, first 80 bits, base32 | Derived 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_fac | X25519 sealed box: ephemeral X25519 + HKDF-SHA256 + AES-256-GCM | Hidden cards are sealed to the facilitator’s public key while people write. Only the facilitator link (&f=) can open them. |
| Card keys | Ed25519, one key per card | Held only by the author. Edits and the reveal are signed with it: authenticity without identity. |
| Commitments | HMAC-SHA256 with a 128-bit random salt | A 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. |
| Presence | AES-256-GCM under K_board | Names, avatars and typing indicators are encrypted like everything else. |
| Key bundle | HKDF-SHA256 from passkey PRF or recovery code, AES-256-GCM | Signed-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.
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.
| Data | Where | Readable by | Kept |
|---|---|---|---|
| Cards, votes, reactions, stickers, actions, board name, custom template titles | Durable Object storage, as ciphertext | People holding the board link | Until the board is deleted |
| Operation envelope: type, server sequence and time, size, facilitator counter | Durable Object storage | Peel | Until the board is deleted |
| Participant names, avatars, typing | Relayed as ciphertext; held in memory while connected | People holding the board link | While connected |
| Board status: phase, participant and card counts, catalogue template id, lock flag, created and expiry times | Durable Object, D1; Slack/Teams messages if installed | Peel; your Slack/Teams channel | Board lifetime |
| Facilitator account: provider id, name, email if shared, linked providers, created date, session | D1 | Peel | Until you delete the account |
| Key bundle and wrapped keys | D1, as ciphertext | Only you (passkey or recovery code) | Until you delete the account |
| Plan, subscription status, Paddle customer and subscription ids | D1; Paddle | Peel, Paddle | As tax law requires |
| Slack/Teams workspace, channel and message ids; bot token (AES-GCM encrypted) | D1 | Peel | Until uninstalled |
| IP address | Cloudflare edge, transiently (delivery, rate limits) | Cloudflare, Peel’s rate limiter | Not stored in our databases |
| Daily counters (boards created, phases reached) | D1 | Peel | Aggregate 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:readonly 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.
- 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