Voor InfoSec-teams en app-reviewers
Beveiliging bij Peel
De korte versie: de inhoud van een bord wordt in de browser versleuteld met een sleutel die in het fragment van de link staat, dus onze servers slaan ciphertext op die ze niet kunnen lezen en geven die door. Hier lees je precies hoe, en waar de grenzen liggen.
Samenvatting
- Tekst van kaartjes, namen, bordtitels, stemmen en stickers worden versleuteld met AES-256-GCM onder een bordsleutel die in de browser wordt gemaakt. De sleutel reist mee in het URL-fragment (
#k=) en wordt nooit naar een server gestuurd. - Facilitatoracties, de bordheader en snapshots worden ondertekend met de Ed25519-sleutel van de facilitator. De bord-id is van die sleutel afgeleid, dus clients merken het als een sleutel wordt verwisseld.
- Verborgen kaartjes zijn tot de onthulling verzegeld voor de facilitator (X25519 sealed box) en vastgelegd met gezouten commitments, zodat onthulde tekst niet kan worden verwisseld.
- Standaard anoniem: operaties bevatten geen auteur-id; stemmen en reacties gebruiken een willekeurige id per bord.
- Deelnemers authenticeren nooit. Geen analytics-SDK’s, geen advertenties, geen scripts van derden behalve Paddle.js op de afrekenpagina.
- Alleen Cloudflare. Niet-geclaimde borden worden 60 dagen na het laatste bezoek verwijderd.
Hoe gegevens stromen
Het fragment van een URL (alles na #) wordt door de browser afgehandeld en maakt geen deel uit van het HTTP-verzoek. Daar zet Peel de sleutel van het bord.
Dreigingsmodel
We ontwerpen zo dat een volledig gecompromitteerde of nieuwsgierige server, wij inbegrepen, zo weinig mogelijk te weten komt over wat een team zegt.
Wat we beschermen
- Tekst van kaartjes, gekozen GIF’s en, in de modus met naam, de naam van de auteur.
- Namen en avatars van deelnemers, de bordnaam en titels van eigen templates.
- Wie op wat stemde of reageerde, en wie welk kaartje schreef.
- De integriteit van de retro: fases, onthullingen en beslissingen van de facilitator kunnen niet worden vervalst.
Tegenstanders waar we rekening mee houden
- Onze eigen servers, een kwaadwillende insider of iemand die bij onze opslag inbreekt: die krijgen ciphertext en metadata.
- Een aanvaller op het netwerk: overal TLS; de sleutel gaat nooit over het netwerk.
- Een server die sleutels verwisselt: de bord-id is afgeleid van de publieke sleutel van de facilitator, en clients controleren de ondertekende header.
- Een deelnemer die wil gluren of vervalsen: verborgen kaartjes zijn verzegeld voor de facilitator; facilitatoroperaties vereisen de handtekening van de facilitator; bewerkingen van kaartjes vereisen de sleutel van dat kaartje.
- Een facilitator die opnieuw publiceert voor een offline auteur: commitments voorkomen dat die de tekst verandert.
Buiten scope, eerlijk gezegd
- Iedereen met de deelnemerslink kan het bord lezen. De link ís de toegangscontrole. Deel hem zoals je het bord zelf zou delen.
- Een gecompromitteerd apparaat, een kwaadaardige browserextensie of iemand die over je schouder meekijkt.
- Verkeersanalyse: de server ziet timing, groottes en aantallen (zie de gegevenskaart).
- Denial of service door de server: die kan berichten laten vallen of vertragen, maar niet lezen of vervalsen.
Cryptografie
Alle primitieven komen uit de WebCrypto API van de browser. Geen zelfgemaakte cryptografie, geen cryptobibliotheken van derden.
| Sleutel | Algoritme | Gebruikt voor |
|---|---|---|
| K_board | AES-256-GCM, willekeurige IV van 96 bits | Versleutelt elke operatie, snapshot en aanwezigheidsupdate. De additional data bindt bord-id, operatietype en operatie-id, zodat ciphertext niet in een ander bord of slot opnieuw kan worden afgespeeld. Staat alleen in het fragment van de link #k=. |
| K_owner | Ed25519 | Het gezag van de facilitator. Ondertekent facilitatoroperaties (fase, timer, spotlight, template, instellingen, stemmen openbaar maken, een kaartje verbergen), de bordheader en snapshots. Een strikt oplopende teller voorkomt replay. Alleen in de facilitatorlink (&o=). |
| Bord-id | SHA-256, eerste 80 bits, base32 | Afgeleid van de publieke sleutel van de facilitator, zodat de id zelf bewijst welke sleutel eigenaar is van het bord. Clients weigeren een header waarvan de sleutel niet naar de id hasht. |
| K_fac | X25519 sealed box: tijdelijke X25519 + HKDF-SHA256 + AES-256-GCM | Terwijl mensen schrijven, worden verborgen kaartjes verzegeld met de publieke sleutel van de facilitator. Alleen de facilitatorlink (&f=) kan ze openen. |
| Kaartsleutels | Ed25519, één sleutel per kaartje | Alleen in handen van de auteur. Bewerkingen en de onthulling worden ermee ondertekend: authenticiteit zonder identiteit. |
| Commitments | HMAC-SHA256 met een willekeurige salt van 128 bits | Een verborgen kaartje publiceert een commitment op de inhoud. Bij de onthulling worden de inhoud en de salt gepubliceerd en controleert elke client ze, zodat niemand de tekst kan verwisselen. |
| Aanwezigheid | AES-256-GCM onder K_board | Namen, avatars en typindicatoren worden versleuteld zoals al het andere. |
| Sleutelbundel | HKDF-SHA256 uit passkey-PRF of herstelcode, AES-256-GCM | Ingelogde facilitators kunnen bordsleutels tussen apparaten synchroniseren. De bundel wordt op het apparaat versleuteld; de server slaat alleen ciphertext en ingepakte sleutels op. |
Te ondertekenen berichten zijn per domein gescheiden (bijvoorbeeld peel/sig/v1, peel/header/v1, peel/snap/v1), zodat een handtekening voor het ene doel niet voor een ander doel kan worden hergebruikt.
Anonimiteit
- Operaties bevatten geen auteur-id. De server kan een kaartje niet aan een persoon koppelen, en standaard kan de facilitator dat ook niet.
- Stemmen en reacties zijn gekoppeld aan een willekeurige id per bord die niet aan een naam is gekoppeld.
- Typindicatoren laten zien dát iemand schrijft, nooit wát.
- De modus met naam is een expliciete bordinstelling; de naam van de auteur reist dan mee in het versleutelde kaartje.
- Deelnemers loggen nooit in, dus er is geen account om aan te koppelen.
Gegevenskaart
Alles wat we bewaren, waar het staat, wie het kan lezen en hoe lang het blijft.
| Gegevens | Waar | Leesbaar voor | Bewaard |
|---|---|---|---|
| Kaartjes, stemmen, reacties, stickers, actiepunten, bordnaam, titels van eigen templates | Opslag in Durable Objects, als ciphertext | Wie de link van het bord heeft | Tot het bord wordt verwijderd |
| Envelop van een operatie: type, volgnummer en tijd van de server, grootte, facilitatorteller | Opslag in Durable Objects | Peel | Tot het bord wordt verwijderd |
| Namen en avatars van deelnemers, typen | Doorgegeven als ciphertext; in het geheugen zolang er verbinding is | Wie de link van het bord heeft | Zolang er verbinding is |
| Bordstatus: fase, aantal deelnemers en kaartjes, template-id uit de catalogus, vergrendelvlag, aanmaak- en vervaltijden | Durable Object, D1; Slack/Teams-berichten indien geïnstalleerd | Peel; je Slack/Teams-kanaal | Levensduur van het bord |
| Facilitatoraccount: provider-id, naam, e-mailadres indien gedeeld, gekoppelde providers, aanmaakdatum, sessie | D1 | Peel | Tot je het account verwijdert |
| Sleutelbundel en ingepakte sleutels | D1, als ciphertext | Alleen jij (passkey of herstelcode) | Tot je het account verwijdert |
| Abonnement, abonnementsstatus, klant- en abonnements-id’s bij Paddle | D1; Paddle | Peel, Paddle | Zolang de belastingwet dat vereist |
| Slack/Teams-workspace, kanaal- en bericht-id’s; bottoken (versleuteld met AES-GCM) | D1 | Peel | Tot de app wordt verwijderd |
| IP-adres | Cloudflare-edge, tijdelijk (aflevering, rate limits) | Cloudflare, de rate limiter van Peel | Niet opgeslagen in onze databases |
| Dagelijkse tellers (gemaakte borden, bereikte fases) | D1 | Peel | Alleen geaggregeerd, zonder identifiers |
Wat onze server wel en niet kan
Hij kan
- Operatietypes, groottes, timing, de fase, het aantal deelnemers en kaartjes, de template-id uit de catalogus en de vergrendelvlag zien.
- Operaties laten vallen, vertragen of weigeren af te leveren (denial of service).
- IP-adressen zien aan de rand van het netwerk.
- Borden en bereikte fases tellen, geaggregeerd.
- Een bord verwijderen (verlopen, misbruikmeldingen).
Hij kan niet
- Tekst van kaartjes, namen, bordtitels, titels van eigen templates, stemmen of stickers lezen.
- Verborgen kaartjes lezen vóór de onthulling (en ook niet erna).
- Facilitatoracties vervalsen: daarvoor is de handtekening van de facilitator nodig.
- Eigen sleutels ertussen schuiven: de bord-id verraadt dat.
- De onthulde tekst van een kaartje wijzigen: commitments en kaarthandtekeningen verraden dat.
- Zien wie welk kaartje schreef.
Zoals bij elke webapp vertrouw je de code die wij serveren. Dat oppervlak houden we klein: een strikte Content Security Policy, script-src beperkt tot onze eigen origin, geen scripts van derden, en het protocol is op aanvraag te reviewen.
Slack en Microsoft Teams
- Berichten bevatten de link van het bord en metadata (titel, fase, aantallen). Tekst van kaartjes wordt alleen verstuurd als de facilitator een samenvatting plaatst en dat bevestigt.
- De Meedoen-kaart bevat de deelnemerslink, inclusief sleutel. Iedereen die dat kanaal kan lezen, kan meedoen, net alsof je daar een link plakt.
- Linkvoorbeelden tonen alleen fase en aantallen, nooit inhoud.
- Slack-scopes:
commands,chat:write,links:read,links:write;users:readalleen als een team vermeldingen aanzet. - Bottokens zijn versleuteld opgeslagen (AES-GCM, sleutel bewaard als Worker-secret). Slack-verzoekhandtekeningen en Bot Framework-tokens worden bij elk verzoek gecontroleerd.
- Als je de app verwijdert, stopt alle posting en worden de tokens verwijderd.
Web en infrastructuur
- Alleen Cloudflare: Pages, Workers, Durable Objects, D1, R2 en KV op het account van 1801 Labs. Geen andere servers.
- Overal TLS, HSTS en een strikte Content Security Policy.
- Geen analytics-SDK’s, geen advertenties, geen scripts van derden. De enige uitzondering is Paddle.js, en die wordt alleen op de afrekenpagina geladen.
- Eén cookie, alleen voor ingelogde facilitators:
peel_session, httpOnly, Secure, SameSite=Lax. - GIF-zoekopdrachten en media lopen via onze API, zodat IP-adressen van deelnemers GIPHY nooit bereiken.
- Lettertypes en assets hosten we zelf. Geen lettertypes via een CDN, geen trackingpixels.
- Rate limits per IP-adres en per bord; misbruikmeldingen en een vergrendeling door de facilitator.
Accounts en bewaartermijnen
- Deelnemers authenticeren nooit. Facilitators loggen in met Google (of Entra ID via Teams), alleen om borden te bewaren of betaalde functies te gebruiken.
- Een account bevat een id, de naam en het e-mailadres van de provider, en een versleutelde sleutelbundel die wordt ontgrendeld met een passkey (WebAuthn PRF) of een herstelcode. Inloggen geeft eigendom, geen toegang tot inhoud.
- Bij het claimen van een bord wordt eigendom bewezen met een handtekening van de facilitator. Bordsleutels worden nooit verstuurd.
- Borden zonder account worden 60 dagen na het laatste bezoek verwijderd; elk bezoek zet de klok terug op nul. Alarms van Durable Objects verwijderen de opslag.
- Als je je account verwijdert, worden sessies, de sleutelbundel en je bibliotheek verwijderd en krijgen je borden weer de vervaltermijn van 60 dagen.
Responsible disclosure
Iets gevonden? Vertel het eerst aan ons, dan werken we samen. We bevestigen meldingen binnen drie werkdagen.
- Geef ons redelijk de tijd (we streven naar 90 dagen) om het te verhelpen voordat je het openbaar maakt.
- Test met je eigen borden en accounts. Benader geen gegevens van anderen en verstoor de dienst niet.
- We ondernemen geen juridische stappen tegen onderzoek te goeder trouw dat deze regels volgt.
- Machineleesbaar contact:
/.well-known/security.txt.
Klanten met het Company-abonnement krijgen een security-reviewpakket (antwoorden op vragenlijsten, subverwerkers, gegevensstromen). Verwerkersovereenkomst · Privacy