Безпека — технічні деталі
Оновлено: 7 травня 2026
Шукаєте версію простою мовою для звичайного користувача? Прочитайте Як ми захищаємо ваші дані. Ця сторінка — її імплементаційно вірний відповідник, написана для розробників, безпекових дослідників і партнерів, що роблять due diligence. Це фактичний опис примітивів, які ми маємо у продакшені.
I. Дані особи — Bank iD та Stripe Identity
Для перевірки того, що наші професіонали можуть легально працювати, ми використовуємо двох зовнішніх KYC-провайдерів. Жоден з них не зберігає чутливі дані особи постійно на наших серверах.
Bank iD (OIDC). Коли працівник обирає перевірку через Bank iD, його банк підписує JWT, що містить лише перевірені поля (ім'я, дата народження, адреса, номер документа) — ніколи не банківські дані для входу. Токен ми верифікуємо проти публічного JWKS Bank iD (env BANKID_JWKS_URL) і структурований результат персистуємо у запис worker_identity_verifications. Сирий токен після верифікації відкидаємо.
Stripe Identity. Коли працівник обирає сканування документа, його пристрій завантажує знімки безпосередньо до Stripe — фізичні зображення паспорта чи ID-картки ніколи не проходять через нашу backend-інфраструктуру. Stripe надсилає нам webhook з verified | requires_input | canceled плюс структурованими полями (ім'я, дата народження, номер документа) через наш єдиний KYC-ендпоінт у worker-identity.service.ts. Зображення залишаються у Stripe згідно з їхньою політикою зберігання.
Аудит і збереження. Кожна зміна стану записується у worker_identity_audit_events (модуль worker-identity-audit). Структуровані дані про перевірку зберігаємо протягом терміну, законодавчо передбаченого для трудових відносин; сирі webhook-payload-и й pending-verification сесії очищаємо через 60 днів.
II. Токени та сесії
Під час входу клієнт отримує два токени: короткостроковий JWT access token (15 хвилин) і повторно використовуваний refresh token (14 днів ковзним, 365 днів абсолютно).
Refresh token є opaque. 64-символьний рядок з випадкових hex-символів — не JWT. Його не можна "розшифрувати" поза нашим backend. При кожному використанні ротується: старий токен одразу позначається використаним, видається новий.
Виявлення зловживань через family_id. Кожен refresh token несе family_id, що ідентифікує батьківську авторизацію (наприклад, оригінальний вхід із певного пристрою). Якщо вже використаний токен з цієї родини буде пред'явлений знову — будь-де у світі — ми негайно відкликаємо всю родину, і всі активні сесії, що походять з цієї авторизації, виходять. Це виявляє крадіжку cookies / replay-атаки за секунди. Реалізація: apps/api/src/modules/auth/, таблиця refresh_tokens.
Активні пристрої. На /account/security/sessions (веб) і /sessions (мобільний) користувач бачить усі активні сесії, їхню останню активність та пристрій / локацію (з User-Agent + поточної IP — історичні IP не зберігаємо). Сесії можна відкликати індивідуально або групою; відкликання анулює родину, а не лише один токен.
III. Файли та документи
Договори, фото профілів, документи до замовлень — все, що користувачі завантажують до FixIt, ми зберігаємо на власному MinIO-кластері (S3-сумісному), а не у публічно доступному CDN.
База даних ніколи не зберігає URL. Персистуємо лише object key (напр. users/abc123/avatar.jpg). URL компонуємо у момент відповіді через AssetUrlService (apps/api/src/modules/storage/asset-url.service.ts) — кожен URL має короткий термін дії, а storage-провайдера можна змінити без переписування бази.
Триступеневий контроль доступу (apps/api/src/modules/storage/storage.controller.ts):
- Public assets (логотипи компаній, профільні фото публічних виконавців) — без авторизації, але видаємо signed URL з коротким терміном дії.
- Authenticated assets (договори, документи до замовлень) — перевіряємо JWT і власність перед видачею signed URL.
- Tenant-scoped assets (внутрішні документи компанії) — додатково перевіряємо
X-Active-Company-Idheader, тож адміністратор компанії А не має доступу до файлів компанії B.
IV. Шифрування у транспорті та у спокої
У транспорті. TLS 1.3 (обов'язково), HSTS з preload субдоменів, CSP з nonce. Налаштовано на nginx reverse proxy з Cloudflare як WAF попереду. Увесь трафік між мобільним/веб додатком і API зашифрований між клієнтом і нашим edge.
У спокої. PostgreSQL 16 з шифруванням на рівні диска (LUKS на хості). Резервні копії бази даних зашифровані окремим ключем і зберігаються поза database-контейнером — див. bun run db:backup:dev / db:backup:staging / db:backup:prod, які викликають on-VPS backup-скрипт.
Cross-tenant scope safety. Публічні запити до таблиці companies проходять через excludePlatformCompany() (apps/api/src/common/scopes/exclude-platform-company.scope.ts) — внутрішній FIXIT_PLATFORM shell-employer таким чином не може просочитись у публічні переліки, пошук чи Elasticsearch-індексацію.
Введення користувача. Поля з вільним текстом проходять stripHtmlTags() санітизацію (apps/api/src/common/utils/sanitize.utils.ts) до збереження — без regex, лише перевірений parser. Підозрілий HTML і нульові байти ловить глобальний SanitizeNullBytesInterceptor. URL від користувача (напр. посилання на портфоліо) проходять через SSRF guard (@fixit/url-security) перш ніж сервер їх завантажить.
V. GDPR та контакти
FixIt App s.r.o. є контролером персональних даних згідно зі статтею 4 GDPR. Наша Політика конфіденційності описує юридичні рамки та ваші права.
- Контакт із захисту даних: legal@fixit.app
- Наглядовий орган: Чеське управління із захисту персональних даних — Pplk. Sochora 27, 170 00 Прага 7
Повідомлення про вразливості. Якщо ви виявили вразливість, напишіть нам на security@fixit.app. Ми реагуємо протягом 48 годин. Будь ласка, не публікуйте вразливість, доки ми не випустимо виправлення — ми дотримуємося coordinated disclosure.
Ця сторінка — фактичний опис. Якщо ви знайдете розбіжність між тим, що тут написано, і тим, що ми робимо насправді, це баг — надішліть це на security@fixit.app, і ми виправимо.