← всі звіти · lunar-hubble-audit.md

Lunar Hubble (Родинне дерево) — аудит 2026-04-18

Аудитор: серверний Claude (Opus 4.7) Метод: read-only статичний аналіз /srv/projects/lunar-hubble/ (live) + /srv/projects/family-tree/ (duplicate) Без модифікацій коду, без запитів до продакшн URL на запис, без npm install


TL;DR

  1. Проєкт має 4 критичні витоки секретів у git-репозиторії (GitHub remote включно): GitHub PAT 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, Gemini API key AIzaSyBqjufdGYbnhl4sC9EufMF3UprSNkQBrNM, Telegram bot token 8342933869:AAGsxuJpfd8yuwyIsC1LWoKKPOxy-bYCEr0, приватний RSA ключ Google Service Account (tree_bot/credentials.json). Всі ці секрети треба ротувати сьогодні ж.
  2. RLS у Supabase фактично вимкнено логічно — всі policies USING (true) для ALL, тобто будь-хто з публічним anon-ключем може читати/писати/видаляти всі записи. Таблиця persons/relationships взагалі не згадана в міграціях — невідомо, чи ввімкнено RLS, але policies для них відсутні.
  3. Аутентифікація — театр: PIN 1234/0000 хардкоджено в клієнтському JS (src/App.tsx:280-283), підказка з цими PIN друкується на формі логіну (src/App.tsx:316). Плюс invite-token family-tree-invite-24 — статичний рядок у клієнті. Будь-який гість дивиться DevTools і стає адміном. Фактично — публічний сайт без доступу.
  4. 70% файлів у репо — це сміття: debug-скрипти (check-error*.js, guess-cols.js), screenshots, console dumps, дубльовані імпортери (import-data.js + importData.js), artifact vite.config.ts.timestamp-*.mjs, .pyc кеш. Це не просто бруд — це прикривало факт того, що секрети витекли.
  5. Архітектурний борг маленький, але неприємний: нема міграцій під контролем (один файл неповної схеми), бекап тільки через tree_bot/backup.py (не в cron), нема CI/CD, нема моніторингу, TypeScript strict-mode визначений але в коді as any щедро, є React помилка в console_dump_2 "child has more than 1 parent" (граф ламається на некоректних даних).

Загальна готовність до production: ~25%. Візуально працює, але безпека = 0. Для батька-некодера це ок як MVP-чорновик у приватному середовищі, але НЕ як публічний Cloud Run сервіс.


🔴 Критичні проблеми (fix immediately — сьогодні)

1. GitHub Personal Access Token у git remote URL (обидва репо)

2. Gemini API key у tree_bot/.env (закомічено в git)

3. Telegram Bot Token у tree_bot/.env (закомічено в git)

4. Google Service Account private key закомічено

5. Supabase anon-key + URL у 8+ місцях (у публічному репо)

6. Supabase RLS = "Allow public all-access" для ВСІХ таблиць

7. PIN-коди хардкодом у клієнтському JS + показані на формі

8. Закомічений .env у duplicate-проекті family-tree ТАКОЖ містить секрети


🟡 Важливі (fix soon — цей тиждень)

Code quality bloat: 25+ файлів debug/dump треба видалити

Два дубльованих імпортери

Duplicate codebase /srv/projects/family-tree/

TypeScript strict визначено, але обходиться

React runtime error: "child has more than 1 parent" (console_dump_2.txt:4)

Графдизайн: одна велика мутація в AppState (persons.notes = JSON string)

Supabase schema — один файл, без версіонування

Немає backup strategy (окрім ручного скрипта)

import-data.js / importData.js TRUNCATE без confirmation

Gemini bot: немає rate limits, немає перевірки типу файлу, send_message без таймаутів

Немає CI/CD

Немає моніторингу / observability


🔵 Nice-to-have (коли будуть руки)


Детальний аналіз

Security (2/10)

Вектор Стан Рейтинг
Authentication PIN hardcoded, invite-token static 🔴 0/10
Secrets in git 4 класи секретів у репо, всі push'нуті на GitHub 🔴 0/10
Supabase RLS ALL USING (true) — RLS ввімкнено, але дозволяє все 🔴 1/10
XSS React JSX за замовчуванням екранує. split(/https?:/) у RelationalManagers.tsx:143 рендерить частини в <a href> — якщо url містить javascript:, вставиться. 🟡 6/10
CSRF Stateless, Supabase auth через Bearer header — CSRF не релевантний. 🟢 9/10
CORS Supabase дефолт (*) + Cloud Run без обмежень — публічна API. 🟡 4/10
Cloud Run public https://lunar-hubble-105435845108.europe-west1.run.app/ — без auth gate, intended. Сам по собі OK, але пара з PIN-theater = проблема. 🟡 5/10
Telegram bot auth ALLOWED_USERS=312194208 (white list) — OK. Але бот має доступ до Supabase anon key (а не service_role) — дивно. 🟡 6/10

Додаткові деталі:

Code quality (4/10)

Architecture (5/10)

Data integrity (4/10)

Deployment / DevOps (3/10)

UX for target user (father 60+) (6/10)

Плюси:

Мінуси:


План дій (priority order)

🔥 Сьогодні (не відкладати):

  1. Ротувати всі секрети (15 хв):
    • GitHub PAT → revoke + create new у /srv/passepartout/github/
    • Gemini API key → revoke + new у /srv/passepartout/google/gemini.key
    • Telegram bot token → /revoke у @BotFather + new у /srv/passepartout/telegram/tree-bot.token
    • Google SA key → delete у GCP Console + new у /srv/passepartout/google/tree-bot-sa.json
    • Supabase anon key → roll у Dashboard (зупинить поточний фронт на 10 хв)
  2. Видалити .env / credentials.json з git history (20 хв):
    cd /srv/projects/lunar-hubble
    git rm --cached .env tree_bot/.env tree_bot/credentials.json tree_bot/__pycache__/db.cpython-312.pyc
    echo -e "\n.env\ntree_bot/.env\ntree_bot/credentials.json\n__pycache__/\n*.pyc" >> .gitignore
    git commit -m "chore: remove secrets from tracking"
    # Для history-rewrite — git filter-repo або BFG Repo-Cleaner (не обов'язково, якщо репо приватне і токени ротовані)
    git push
    
  3. Замінити remote URL на SSH/без токена:
    git remote set-url origin git@github.com:serhiivereschak/lunar-hubble.git
    # або https://github.com/... з credential-helper
    

⏳ Цей тиждень:

  1. Прибрати PIN-театр з App.tsx. Варіанти:
    • Найшвидше: src/App.tsx:315-317 — видалити параграф з підказкою. Залишити тільки invite-link (але це теж треба фіксити).
    • Правильно: Supabase Auth + магік-лінк на email/phone. Для батька — Telegram Login Widget.
  2. Налаштувати RLS правильно:
    -- Для кожної таблиці: drop public all-access, replace with:
    DROP POLICY "Allow public all-access" ON persons;
    CREATE POLICY "read_public" ON persons FOR SELECT USING (true);
    CREATE POLICY "write_authenticated" ON persons FOR INSERT TO authenticated WITH CHECK (true);
    -- ... UPDATE, DELETE
    
  3. Видалити сміття — 25+ debug-файлів (список у секції code quality).
  4. Видалити duplicate /srv/projects/family-tree/ + архівувати GH repo.
  5. Додати один import script замість двох, з --dry-run і --confirm.

🏗️ Цей місяць:

  1. CI/CD: GitHub Actions → gcloud deploy на push.
  2. Backup cron: Cloud Run Job + Cloud Scheduler.
  3. Migrations: supabase/migrations/ versioned, одна команда apply.
  4. Denormalize notes JSON → окремі колонки (one-shot SQL script).
  5. Error boundary + Sentry.
  6. GEDCOM import (як обіцяно у wiki) або виправити wiki.
  7. Storage фото через Supabase Storage з authenticated access, а не Google Drive public links.

Оцінка стану

Область Рейтинг Коментар
Security 2/10 4 класи витоку секретів у публічний GitHub-репо, RLS анонімно-адмін. Все інше неважливо на тлі цього.
Code quality 4/10 Основний код непоганий, але 1/3 репо — сміття. TS strict decorative.
Architecture 5/10 Вибір стеку ок, модель даних ок для MVP, міграції disaster, JSON-in-notes — борг.
UX (для 60+) 6/10 Великі кнопки, українська, Telegram-бот — розумно. PIN форма і мінорні mobile issues.
Deployment 3/10 Працює, але ручно, без CI, без моніторингу, секрети в образі.
Data integrity 4/10 Backup existed but manual, orphans проблема відома, немає FK для relationships.

Загальна готовність до production: ~25%

Проєкт працює — це дуже важливо. Батько може користуватися. Але:

Рекомендація: тримати Cloud Run URL приватним (Cloud Run IAM → лише автентифіковані GCP користувачі, або IAP-gate) до моменту, поки не виконано кроки 1-6 з плану. Або — що простіше — приховати посилання, не ділитися з родичами до виконання кроків 1-4.