Pentru echipele InfoSec și evaluatorii de aplicații
Securitatea în Peel
Pe scurt: conținutul board-ului e criptat în browser cu o cheie care stă în fragmentul linkului, așa că serverele noastre stochează și transmit text cifrat pe care nu-l pot citi. Iată exact cum funcționează și unde sunt limitele.
Rezumat
- Textul cardurilor, numele, titlurile board-urilor, voturile și stickerele sunt criptate cu AES-256-GCM sub o cheie de board generată în browser. Cheia călătorește în fragmentul URL-ului (
#k=) și nu e trimisă niciodată la un server. - Acțiunile facilitatorului, antetul board-ului și snapshoturile sunt semnate cu cheia Ed25519 a facilitatorului. Id-ul board-ului e derivat din acea cheie, așa că clienții detectează înlocuirea cheii.
- Cardurile ascunse sunt sigilate pentru facilitator (X25519 sealed box) până la dezvăluire și legate prin angajamente cu salt, așa că textul dezvăluit nu poate fi înlocuit.
- Anonim din start: operațiile nu conțin id de autor; voturile și reacțiile folosesc un id aleatoriu per board.
- Participanții nu se autentifică niciodată. Fără SDK-uri de analytics, fără reclame, fără scripturi terțe, cu excepția Paddle.js pe pagina de plată.
- Doar Cloudflare. Board-urile nerevendicate se șterg la 60 de zile după ultima vizită.
Cum circulă datele
Fragmentul unui URL (tot ce urmează după #) e gestionat de browser și nu face parte din cererea HTTP. Peel pune cheia board-ului acolo.
Modelul de amenințări
Proiectăm totul astfel încât un server complet compromis sau curios, inclusiv noi, să afle cât mai puțin posibil despre ce spune o echipă.
Ce protejăm
- Textul cardurilor, GIF-urile alese și, în modul cu nume, numele autorului.
- Numele și avatarurile participanților, numele board-ului și titlurile șabloanelor personalizate.
- Cine a votat sau a reacționat la ce și cine a scris fiecare card.
- Integritatea retro-ului: pașii, dezvăluirile și deciziile facilitatorului nu pot fi falsificate.
Adversarii pe care îi luăm în calcul
- Propriile noastre servere, un angajat rău-intenționat sau cineva care ne compromite stocarea: obțin doar text cifrat și metadate.
- Un atacator în rețea: TLS peste tot; cheia nu trece niciodată prin rețea.
- Un server care înlocuiește cheile: id-ul board-ului e derivat din cheia publică a facilitatorului, iar clienții verifică antetul semnat.
- Un participant care vrea să tragă cu ochiul sau să falsifice: cardurile ascunse sunt sigilate pentru facilitator; operațiile facilitatorului necesită semnătura lui; editarea unui card necesită cheia cardului.
- Un facilitator care republică pentru un autor offline: angajamentele îl împiedică să schimbe textul.
Ce nu acoperim, sincer
- Oricine are linkul de participant poate citi board-ul. Linkul este controlul de acces. Partajează-l cu aceeași grijă ca board-ul însuși.
- Un dispozitiv compromis, o extensie de browser rău-intenționată sau cineva care se uită peste umărul tău.
- Analiza traficului: serverul vede momentele, dimensiunile și numerele (vezi harta datelor).
- Blocarea serviciului de către server: poate pierde sau întârzia mesaje, dar nu le poate citi sau falsifica.
Criptografie
Toate primitivele vin din API-ul WebCrypto al browserului. Fără criptografie proprie, fără biblioteci criptografice terțe.
| Cheie | Algoritm | Folosită pentru |
|---|---|---|
| K_board | AES-256-GCM, IV aleatoriu de 96 de biți | Criptează fiecare operație, snapshot și actualizare de prezență. Datele adiționale leagă id-ul board-ului, tipul operației și id-ul operației, astfel încât textul cifrat nu poate fi reluat într-un alt board sau slot. Există doar în fragmentul linkului #k=. |
| K_owner | Ed25519 | Autoritatea facilitatorului. Semnează operațiile facilitatorului (pas, timer, reflector, șablon, setări, deschiderea votului, ascunderea unui card), antetul board-ului și snapshoturile. Un contor strict crescător blochează reluarea (replay). Doar în linkul de facilitator (&o=). |
| Id-ul board-ului | SHA-256, primii 80 de biți, base32 | Derivat din cheia publică a facilitatorului, așa că id-ul însuși dovedește ce cheie deține board-ul. Clienții resping un antet dacă hash-ul cheii din el nu corespunde id-ului. |
| K_fac | X25519 sealed box: X25519 efemer + HKDF-SHA256 + AES-256-GCM | Cardurile ascunse sunt sigilate cu cheia publică a facilitatorului cât timp oamenii scriu. Doar linkul de facilitator (&f=) le poate deschide. |
| Cheile cardurilor | Ed25519, o cheie per card | Fiecare cheie e deținută doar de autor. Editările și dezvăluirea sunt semnate cu ea: autenticitate fără identitate. |
| Angajamente (commitments) | HMAC-SHA256 cu un salt aleatoriu de 128 de biți | Un card ascuns publică un angajament față de conținutul său. La dezvăluire se publică conținutul și saltul, iar fiecare client le verifică, astfel încât nimeni nu poate înlocui textul. |
| Prezență | AES-256-GCM cu K_board | Numele, avatarurile și indicatorii de scriere sunt criptați ca orice altceva. |
| Pachetul de chei | HKDF-SHA256 din PRF-ul passkey-ului sau din codul de recuperare, AES-256-GCM | Facilitatorii conectați pot sincroniza cheile board-urilor între dispozitive. Pachetul e criptat pe dispozitiv; serverul stochează doar text cifrat și chei împachetate. |
Mesajele semnate sunt separate pe domenii (de exemplu peel/sig/v1, peel/header/v1, peel/snap/v1), așa că o semnătură făcută pentru un scop nu poate fi refolosită pentru altul.
Anonimat
- Operațiile nu conțin niciun id de autor. Serverul nu poate lega un card de o persoană și, implicit, nici facilitatorul.
- Voturile și reacțiile sunt asociate unui id aleatoriu per board, care nu e legat de un nume.
- Indicatorii de scriere arată că cineva scrie, niciodată ce scrie.
- Modul cu nume e o setare explicită a board-ului; în acest caz, numele autorului circulă în interiorul cardului criptat.
- Participanții nu se conectează niciodată, deci nu există niciun cont care să poată fi corelat.
Harta datelor
Tot ce deținem, unde se află, cine le poate citi și cât timp le păstrăm.
| Date | Unde | Cine le poate citi | Păstrare |
|---|---|---|---|
| Carduri, voturi, reacții, stickere, acțiuni, numele board-ului, titlurile șabloanelor personalizate | Stocarea Durable Object, ca text cifrat | Cei care au linkul board-ului | Până la ștergerea board-ului |
| Plicul operației: tip, secvența și ora de pe server, dimensiune, contorul facilitatorului | Stocarea Durable Object | Peel | Până la ștergerea board-ului |
| Numele participanților, avatarurile, indicatorul de scriere | Transmise ca text cifrat; ținute în memorie cât timp sunt conectați | Cei care au linkul board-ului | Cât timp sunt conectați |
| Starea board-ului: pasul, numărul de participanți și de carduri, id-ul șablonului din catalog, indicatorul de blocare, momentul creării și al expirării | Durable Object, D1; mesaje Slack/Teams, dacă aplicația e instalată | Peel; canalul tău de Slack/Teams | Pe durata de viață a board-ului |
| Cont de facilitator: id-ul de la furnizor, nume, e-mail dacă e partajat, furnizori asociați, data creării, sesiune | D1 | Peel | Până îți ștergi contul |
| Pachetul de chei și cheile împachetate | D1, ca text cifrat | Doar tu (passkey sau cod de recuperare) | Până îți ștergi contul |
| Plan, starea abonamentului, id-urile de client și de abonament din Paddle | D1; Paddle | Peel, Paddle | Cât cere legislația fiscală |
| Id-urile de workspace, canal și mesaj din Slack/Teams; tokenul botului (criptat cu AES-GCM) | D1 | Peel | Până la dezinstalare |
| Adresa IP | Edge-ul Cloudflare, temporar (livrare, limitarea ratei) | Cloudflare, limitatorul de rată al Peel | Nu se stochează în bazele noastre de date |
| Contoare zilnice (board-uri create, pași atinși) | D1 | Peel | Doar agregat, fără identificatori |
Ce poate și ce nu poate face serverul nostru
Poate
- Să vadă tipurile operațiilor, dimensiunile, momentele, pasul, numărul de participanți și de carduri, id-ul șablonului din catalog și indicatorul de blocare.
- Să piardă, să întârzie sau să refuze livrarea operațiilor (denial of service).
- Să vadă adresele IP la marginea rețelei.
- Să numere board-urile și pașii atinși, agregat.
- Să șteargă un board (expirare, rapoarte de abuz).
Nu poate
- Să citească textul cardurilor, numele, titlurile board-urilor, titlurile șabloanelor personalizate, voturile sau stickerele.
- Să citească cardurile ascunse înainte de dezvăluire (și nici după).
- Să falsifice acțiunile facilitatorului: acestea necesită semnătura facilitatorului.
- Să strecoare propriile chei: id-ul board-ului l-ar da de gol.
- Să schimbe textul dezvăluit al unui card: angajamentele și semnăturile cardurilor l-ar da de gol.
- Să afle cine a scris fiecare card.
Ca la orice aplicație web, ai încredere în codul pe care îl servim. Păstrăm această suprafață mică: o Content Security Policy strictă, script-src limitat la propria noastră origine, fără scripturi terțe, iar protocolul poate fi analizat la cerere.
Slack și Microsoft Teams
- Mesajele conțin linkul board-ului și metadate (titlu, pas, numere). Textul cardurilor e trimis doar când facilitatorul postează un rezumat și îl confirmă.
- Cardul „Intră” conține linkul de participant, care include cheia. Oricine poate citi canalul respectiv poate intra, la fel ca atunci când lipești linkul acolo.
- Previzualizările linkurilor arată doar pasul și numerele, niciodată conținutul.
- Permisiuni Slack (scopes):
commands,chat:write,links:read,links:write;users:readdoar când o echipă activează mențiunile. - Tokenurile botului sunt criptate în repaus (AES-GCM, cu cheia păstrată ca secret de Worker). Semnăturile cererilor Slack și tokenurile Bot Framework sunt verificate la fiecare cerere.
- Dezinstalarea aplicației oprește orice postare și îi șterge tokenurile.
Web și infrastructură
- Doar Cloudflare: Pages, Workers, Durable Objects, D1, R2 și KV în contul 1801 Labs. Niciun alt server.
- TLS peste tot, HSTS și o Content Security Policy strictă.
- Fără SDK-uri de analytics, fără reclame, fără scripturi terțe. Singura excepție e Paddle.js, încărcat doar pe pagina de plată.
- Un singur cookie, doar pentru facilitatorii conectați:
peel_session, httpOnly, Secure, SameSite=Lax. - Căutarea de GIF-uri și fișierele media trec prin API-ul nostru, așa că adresele IP ale participanților nu ajung niciodată la GIPHY.
- Fonturile și celelalte resurse sunt găzduite de noi. Fără fonturi de pe CDN, fără pixeli de urmărire.
- Limite de rată per IP și per board; rapoarte de abuz și blocarea board-ului de către facilitator.
Conturi și păstrarea datelor
- Participanții nu se autentifică niciodată. Facilitatorii se conectează cu Google (ori cu Entra ID prin Teams) doar ca să păstreze board-uri sau să folosească funcții plătite.
- Un cont stochează un id, numele și e-mailul primite de la furnizor și un pachet de chei criptat, deblocat cu un passkey (WebAuthn PRF) sau cu un cod de recuperare. Conectarea oferă calitatea de proprietar, nu acces la conținut.
- Revendicarea unui board dovedește proprietatea printr-o semnătură de facilitator. Cheile board-ului nu sunt trimise niciodată.
- Board-urile fără cont se șterg la 60 de zile după ultima vizită; fiecare vizită repornește termenul. Alarmele Durable Object șterg stocarea.
- Ștergerea contului șterge sesiunile, pachetul de chei și biblioteca ta și readuce board-urile tale la expirarea după 60 de zile.
Divulgare responsabilă
Ai găsit ceva? Spune-ne întâi nouă și lucrăm împreună. Confirmăm primirea rapoartelor în cel mult trei zile lucrătoare.
- Te rugăm să ne lași un timp rezonabil (ținta noastră e de 90 de zile) pentru remediere înainte de a face publică problema.
- Testează cu propriile board-uri și conturi. Nu accesa datele altor persoane și nu degrada serviciul.
- Nu vom iniția acțiuni în justiție pentru cercetarea de bună-credință care respectă aceste reguli.
- Contact în format citibil automat:
/.well-known/security.txt.
Clienții planului Companie primesc un pachet pentru evaluarea de securitate (răspunsuri la chestionare, subprocesatori, fluxuri de date). DPA · Confidențialitate