За екипи по информационна сигурност и рецензенти на приложения
Сигурността в Peel
Накратко: съдържанието на таблото се криптира в браузъра с ключ, който живее във фрагмента на линка, така че сървърите ни съхраняват и препращат шифрован текст, който не могат да прочетат. Ето как точно става това и къде са границите.
Резюме
- Текстът на картите, имената, заглавията на таблата, гласовете и стикерите се криптират с AES-256-GCM с ключ на таблото, генериран в браузъра. Ключът пътува във фрагмента на URL адреса (
#k=) и никога не се изпраща до сървър. - Действията на фасилитатора, заглавната част на таблото и моментните снимки се подписват с Ed25519 ключа на фасилитатора. Идентификаторът на таблото се извлича от този ключ, така че клиентите засичат подмяна на ключа.
- Скритите карти са запечатани за фасилитатора (X25519 sealed box) до разкриването и обвързани с ангажименти със сол, така че разкритият текст не може да бъде подменен.
- Анонимно по подразбиране: операциите не носят идентификатор на автора; гласовете и реакциите използват случаен идентификатор за всяко табло.
- Участниците никога не се удостоверяват. Без SDK за анализи, без реклами, без скриптове на трети страни освен Paddle.js на страницата за плащане.
- Само Cloudflare. Неприсвоените табла се изтриват 60 дни след последното посещение.
Как текат данните
Фрагментът на URL адреса (всичко след #) се обработва от браузъра и не е част от HTTP заявката. Peel слага ключа на таблото там.
Модел на заплахите
Проектираме така, че напълно компрометиран или любопитен сървър, включително нашият, да научава възможно най-малко за това какво казва един екип.
Какво защитаваме
- Текстът на картите, избраните GIF-ове и, в режим с имена, името на автора.
- Имената и аватарите на участниците, името на таблото и заглавията на собствени шаблони.
- Кой за какво е гласувал или реагирал и кой коя карта е написал.
- Целостта на ретрото: стъпките, разкриванията и решенията на фасилитатора не могат да бъдат фалшифицирани.
Противници, които отчитаме
- Собствените ни сървъри, злонамерен служител или някой, който пробие хранилището ни: получават шифрован текст и метаданни.
- Нападател в мрежата: TLS навсякъде; ключът никога не минава през мрежата.
- Сървър, който подменя ключове: идентификаторът на таблото се извлича от публичния ключ на фасилитатора, а клиентите проверяват подписаната заглавна част.
- Участник, който иска да надникне или да фалшифицира: скритите карти са запечатани за фасилитатора; операциите на фасилитатора изискват неговия подпис; редакциите на карта изискват ключа на картата.
- Фасилитатор, който публикува повторно вместо офлайн автор: ангажиментите не му позволяват да промени текста.
Извън обхвата, честно казано
- Всеки, който има линка за участници, може да чете таблото. Линкът е контролът на достъпа. Пази го и го споделяй така, както споделяш самото табло.
- Компрометирано устройство, злонамерено разширение на браузъра или някой, който наднича през рамото ти.
- Анализ на трафика: сървърът вижда времето, размерите и бройките (виж картата на данните).
- Отказ на услуга от страна на сървъра: може да изпуска или забавя съобщения, но не и да ги чете или фалшифицира.
Криптография
Всички примитиви идват от WebCrypto API на браузъра. Без собствена криптография, без криптографски библиотеки на трети страни.
| Ключ | Алгоритъм | Използва се за |
|---|---|---|
| K_board | AES-256-GCM, 96-битов случаен IV | Криптира всяка операция, моментна снимка и обновяване на присъствието. Допълнителните данни свързват идентификатора на таблото, типа и идентификатора на операцията, така че шифрованият текст не може да бъде повторно използван в друго табло или позиция. Живее само във фрагмента на линка #k=. |
| K_owner | Ed25519 | Правомощията на фасилитатора. Подписва операциите на фасилитатора (стъпка, таймер, прожектор, шаблон, настройки, отваряне на гласуването, скриване на карта), заглавната част на таблото и моментните снимки. Строго нарастващ брояч блокира повторното възпроизвеждане. Само в линка на фасилитатора (&o=). |
| Идентификатор на таблото | SHA-256, първите 80 бита, base32 | Извлича се от публичния ключ на фасилитатора, така че самият идентификатор доказва кой ключ притежава таблото. Клиентите отхвърлят заглавна част, чийто ключ не съответства на хеша на идентификатора. |
| K_fac | X25519 sealed box: ефимерен X25519 + HKDF-SHA256 + AES-256-GCM | Докато хората пишат, скритите карти се запечатват с публичния ключ на фасилитатора. Само линкът на фасилитатора (&f=) може да ги отвори. |
| Ключове на картите | Ed25519, по един ключ за карта | Държи ги само авторът. С тях се подписват редакциите и разкриването: автентичност без идентичност. |
| Ангажименти (commitments) | HMAC-SHA256 със 128-битова случайна сол | Скритата карта публикува ангажимент към съдържанието си. При разкриването съдържанието и солта се публикуват и всеки клиент ги проверява, така че никой не може да подмени текста. |
| Присъствие | AES-256-GCM с K_board | Имената, аватарите и индикаторите за писане се криптират като всичко останало. |
| Архив с ключове | HKDF-SHA256 от passkey PRF или код за възстановяване, AES-256-GCM | Влезлите фасилитатори могат да синхронизират ключовете на таблата между устройствата. Архивът се криптира на устройството; сървърът съхранява само шифрован текст и обвити ключове. |
Подписваните съобщения са с разделени домейни (например peel/sig/v1, peel/header/v1, peel/snap/v1), така че подпис за една цел не може да се използва повторно за друга.
Анонимност
- Операциите не носят идентификатор на автора. Сървърът не може да свърже карта с човек, а по подразбиране не може и фасилитаторът.
- Гласовете и реакциите се водят под случаен идентификатор за всяко табло, който не е свързан с име.
- Индикаторите за писане показват, че някой пише, но никога какво.
- Режимът с имена е изрична настройка на таблото; тогава името на автора пътува вътре в криптираната карта.
- Участниците никога не влизат в профил, така че няма профил, с който да бъдат свързани.
Карта на данните
Всичко, което съхраняваме, къде се намира, кой може да го чете и колко време остава.
| Данни | Къде | Кой може да чете | Срок |
|---|---|---|---|
| Карти, гласове, реакции, стикери, действия, име на таблото, заглавия на собствени шаблони | Хранилище на Durable Object, като шифрован текст | Хората, които имат линка към таблото | Докато таблото не бъде изтрито |
| Плик на операцията: тип, пореден номер и време от сървъра, размер, брояч на фасилитатора | Хранилище на Durable Object | Peel | Докато таблото не бъде изтрито |
| Имена на участниците, аватари, индикатор за писане | Препраща се като шифрован текст; държи се в паметта, докато има връзка | Хората, които имат линка към таблото | Докато има връзка |
| Статус на таблото: стъпка, брой участници и карти, идентификатор на шаблона от каталога, флаг за заключване, време на създаване и изтичане | Durable Object, D1; съобщения в Slack/Teams, ако е инсталирано | Peel; твоят канал в Slack/Teams | Докато таблото съществува |
| Профил на фасилитатор: идентификатор от доставчика, име, имейл (ако е споделен), свързани доставчици, дата на създаване, сесия | D1 | Peel | Докато не изтриеш профила |
| Архив с ключове и обвити ключове | D1, като шифрован текст | Само ти (passkey или код за възстановяване) | Докато не изтриеш профила |
| План, статус на абонамента, идентификатори на клиента и абонамента в Paddle | D1; Paddle | Peel, Paddle | Според изискванията на данъчното законодателство |
| Идентификатори на работното пространство, канала и съобщенията в Slack/Teams; токен на бота (криптиран с AES-GCM) | D1 | Peel | До деинсталиране |
| IP адрес | Мрежата на Cloudflare, временно (доставка, ограничения на заявките) | Cloudflare, ограничителят на заявки на Peel | Не се съхранява в нашите бази данни |
| Дневни броячи (създадени табла, достигнати стъпки) | D1 | Peel | Само обобщено, без идентификатори |
Какво може и какво не може сървърът ни
Може
- Да вижда типовете операции, размерите, времето, стъпката, броя участници и карти, идентификатора на шаблона от каталога и флага за заключване.
- Да изпусне, забави или откаже да достави операции (отказ на услуга).
- Да вижда IP адресите в периферията на мрежата.
- Да брои таблата и достигнатите стъпки, обобщено.
- Да изтрие табло (изтичане на срока, сигнали за злоупотреба).
Не може
- Да чете текста на картите, имената, заглавията на таблата, заглавията на собствени шаблони, гласовете или стикерите.
- Да чете скритите карти преди разкриването (нито след него).
- Да фалшифицира действия на фасилитатора: за тях е нужен подписът на фасилитатора.
- Да подмени ключовете със свои: идентификаторът на таблото ще го издаде.
- Да промени разкрития текст на карта: ангажиментите и подписите на картите ще го издадат.
- Да разбере кой коя карта е написал.
Както при всяко уеб приложение, ти се доверяваш на кода, който сервираме. Държим тази повърхност малка: строга Content Security Policy, script-src ограничен до нашия собствен произход, без скриптове на трети страни, а протоколът е отворен за преглед при поискване.
Slack и Microsoft Teams
- Съобщенията носят линка към таблото и метаданни (заглавие, стъпка, бройки). Текстът на картите се изпраща само когато фасилитаторът публикува обобщение и го потвърди.
- Картата за влизане съдържа линка за участници, който включва ключа. Всеки, който чете този канал, може да влезе – все едно линкът е поставен там.
- Прегледите на линкове показват само стъпката и бройките, никога съдържание.
- Права в Slack:
commands,chat:write,links:read,links:write;users:readсамо когато екипът включи споменаванията. - Токените на бота се съхраняват криптирани (AES-GCM, ключът се пази като Worker secret). Подписите на заявките от Slack и токените на Bot Framework се проверяват при всяка заявка.
- Деинсталирането на приложението спира всички публикации и изтрива токените му.
Уеб и инфраструктура
- Само Cloudflare: Pages, Workers, Durable Objects, D1, R2 и KV в акаунта на 1801 Labs. Никакви други сървъри.
- TLS навсякъде, HSTS и строга Content Security Policy.
- Без SDK за анализи, без реклами, без скриптове на трети страни. Единственото изключение е Paddle.js, който се зарежда само на страницата за плащане.
- Една бисквитка, само за влезли фасилитатори:
peel_session, httpOnly, Secure, SameSite=Lax. - Търсенето на GIF-ове и медията минават през нашия API, така че IP адресите на участниците никога не стигат до GIPHY.
- Шрифтовете и ресурсите се хостват при нас. Без шрифтове от CDN, без проследяващи пиксели.
- Ограничения на заявките по IP и по табло; сигнали за злоупотреба и заключване от фасилитатора.
Профили и съхранение
- Участниците никога не се удостоверяват. Фасилитаторите влизат с Google (или с Entra ID през Teams) само за да пазят табла или да използват платени функции.
- Профилът съхранява идентификатор, името и имейла от доставчика и криптиран архив с ключове, който се отключва с passkey (WebAuthn PRF) или с код за възстановяване. Влизането дава собственост, а не достъп до съдържанието.
- При присвояване на табло собствеността се доказва с подпис на фасилитатора. Ключовете на таблото никога не се изпращат.
- Таблата без профил се изтриват 60 дни след последното посещение; всяко посещение нулира брояча. Хранилището се изтрива от аларми на Durable Object.
- Изтриването на профила изтрива сесиите, архива с ключове и библиотеката ти и връща 60-дневния срок на таблата ти.
Отговорно разкриване на уязвимости
Откри нещо? Първо ни кажи и ще работим заедно. Потвърждаваме получаването на доклади в рамките на три работни дни.
- Моля, дай ни разумно време (целим 90 дни) да поправим проблема, преди да го оповестиш.
- Тествай със собствени табла и профили. Не достъпвай чужди данни и не влошавай услугата.
- Няма да предприемаме правни действия срещу добросъвестни проучвания, които спазват тези правила.
- Машинночетим контакт:
/.well-known/security.txt.
Клиентите с план „Компания“ получават пакет за преглед на сигурността (отговори на въпросници, подизпълнители по обработката, потоци на данни). DPA · Поверителност