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.

Gegevensstroom: de sleutel wordt lokaal uit de link gelezen; alleen ciphertext en minimale metadata gaan over het netwerk.

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.

SleutelAlgoritmeGebruikt voor
K_boardAES-256-GCM, willekeurige IV van 96 bitsVersleutelt 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_ownerEd25519Het 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-idSHA-256, eerste 80 bits, base32Afgeleid 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_facX25519 sealed box: tijdelijke X25519 + HKDF-SHA256 + AES-256-GCMTerwijl mensen schrijven, worden verborgen kaartjes verzegeld met de publieke sleutel van de facilitator. Alleen de facilitatorlink (&f=) kan ze openen.
KaartsleutelsEd25519, één sleutel per kaartjeAlleen in handen van de auteur. Bewerkingen en de onthulling worden ermee ondertekend: authenticiteit zonder identiteit.
CommitmentsHMAC-SHA256 met een willekeurige salt van 128 bitsEen 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.
AanwezigheidAES-256-GCM onder K_boardNamen, avatars en typindicatoren worden versleuteld zoals al het andere.
SleutelbundelHKDF-SHA256 uit passkey-PRF of herstelcode, AES-256-GCMIngelogde 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.

Verborgen kaartjes en de onthulling

  1. Tijdens het schrijven verzegelt de browser van de auteur het kaartje met de X25519-sleutel van de facilitator en publiceert een gezouten commitment. De platte tekst blijft op het apparaat van de auteur.
  2. Andere deelnemers zien een verborgen kaartje: zij kunnen het niet ontsleutelen, en de server ook niet.
  3. De browser van de facilitator kan verzegelde kaartjes openen (het voorbeeld “👁 alleen jij”).
  4. Bij de onthulling publiceert elke auteur het kaartje opnieuw, versleuteld met K_board en ondertekend met de kaartsleutel, samen met de salt.
  5. Is een auteur offline, dan publiceert de facilitator het namens die auteur. Elke client controleert het commitment, dus de tekst moet overeenkomen met wat de auteur schreef.

Omdat de facilitator technisch gezien kan meekijken, toont elk bord aan iedereen het label “facilitator kan meekijken” zolang kaartjes verborgen zijn. Dat is geen instelling die je kunt verbergen.

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.

GegevensWaarLeesbaar voorBewaard
Kaartjes, stemmen, reacties, stickers, actiepunten, bordnaam, titels van eigen templatesOpslag in Durable Objects, als ciphertextWie de link van het bord heeftTot het bord wordt verwijderd
Envelop van een operatie: type, volgnummer en tijd van de server, grootte, facilitatortellerOpslag in Durable ObjectsPeelTot het bord wordt verwijderd
Namen en avatars van deelnemers, typenDoorgegeven als ciphertext; in het geheugen zolang er verbinding isWie de link van het bord heeftZolang er verbinding is
Bordstatus: fase, aantal deelnemers en kaartjes, template-id uit de catalogus, vergrendelvlag, aanmaak- en vervaltijdenDurable Object, D1; Slack/Teams-berichten indien geïnstalleerdPeel; je Slack/Teams-kanaalLevensduur van het bord
Facilitatoraccount: provider-id, naam, e-mailadres indien gedeeld, gekoppelde providers, aanmaakdatum, sessieD1PeelTot je het account verwijdert
Sleutelbundel en ingepakte sleutelsD1, als ciphertextAlleen jij (passkey of herstelcode)Tot je het account verwijdert
Abonnement, abonnementsstatus, klant- en abonnements-id’s bij PaddleD1; PaddlePeel, PaddleZolang de belastingwet dat vereist
Slack/Teams-workspace, kanaal- en bericht-id’s; bottoken (versleuteld met AES-GCM)D1PeelTot de app wordt verwijderd
IP-adresCloudflare-edge, tijdelijk (aflevering, rate limits)Cloudflare, de rate limiter van PeelNiet opgeslagen in onze databases
Dagelijkse tellers (gemaakte borden, bereikte fases)D1PeelAlleen 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:read alleen 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.

[email protected]

  • 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