WellNuo/AUDIT_REPORT.md
Sergei 671374da9a Improve BLE WiFi error handling and logging
- setWiFi() now throws detailed errors instead of returning false
- Shows specific error messages: "WiFi credentials rejected", timeout etc.
- Added logging throughout BLE WiFi configuration flow
- Fixed WiFi network deduplication (keeps strongest signal)
- Ignore "Operation cancelled" error (normal cleanup behavior)
- BatchSetupProgress shows actual error in hint field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 19:10:45 -08:00

388 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WellNuo Security & Quality Audit Report
**Дата:** 2026-01-22
**Версия:** 4.0
**Последнее обновление:** 2026-01-26
---
## Резюме
| Категория | Кол-во | Статус |
|-----------|--------|--------|
| ✅ Критичные (VULN-001 — VULN-008) | 6 | **ВЫПОЛНЕНО** — задеплоено 2026-01-26 |
| 🟢 Быстрые рекомендации | 5 | Можно сделать за 2-3 часа |
| 🟡 Средние рекомендации | ~15 | 1-3 дня на каждую |
| 🔴 Большие задачи | 3 | 1-2 недели (после релиза) |
| ⏸️ Заблокированные | 4 | Ждём legacy backend |
---
# ✅ ВЫПОЛНЕНО — Критичные уязвимости
> Задеплоено 2026-01-26. Code Review Score: 10/10.
> PRD: `PRD-SECURITY.md`
| # | Уязвимость | Что сделано | Файлы |
|---|------------|-------------|-------|
| 1 | **VULN-001**: Stripe Webhook | Проверка `STRIPE_WEBHOOK_SECRET` на старте, сервер не запустится без него | `webhook.js`, `index.js` |
| 2 | **VULN-003**: JWT Secret | Проверка ≥32 символа на старте + обновлён secret до 64 символов | `index.js` |
| 3 | **VULN-004**: OTP Rate Limit | 5 попыток/15мин на verify, 3/15мин на send | `auth.js` |
| 4 | **VULN-005**: Input Validation | express-validator на всех POST/PATCH endpoints | `beneficiaries.js`, `stripe.js`, `invitations.js` |
| 5 | **VULN-007**: Doppler | Инструкция создана | `backend/DOPPLER_SETUP.md` |
| 6 | **VULN-008**: npm audit | Уязвимости в зависимостях исправлены | `package.json` |
---
# ⏸️ ЗАБЛОКИРОВАНО — Ждём legacy backend
> Эти задачи зависят от изменений на **eluxnetworks.net**.
> Контакт: команда legacy backend.
### Что ждём:
**Phase 1 (ETA: mid next week):**
- `set_deployment` endpoint возвращает `deployment_id` в ответе
**Phase 2 (post-MVP):**
- Header-based auth (`Authorization: Bearer <token>`)
- `beneficiary_password` становится optional
- `external_beneficiary_id` поле в таблице deployments
### Заблокированные задачи:
| Задача | Проблема | Когда разблокируется |
|--------|----------|---------------------|
| **FE-003**: Legacy Credentials | Hardcoded `anandk` / `anandk_8` в `api.ts:1444` | Phase 2 |
| **LEGACY-001**: Deployment ID | `set_deployment` не возвращает ID | Phase 1 |
| **LEGACY-002**: Plaintext Passwords | Отправляем пароль в открытом виде | Phase 2 |
| **LEGACY-003**: External ID Mapping | Нет `external_beneficiary_id` в БД | Phase 2 |
---
# 🟢 БЫСТРЫЕ РЕКОМЕНДАЦИИ (2-3 часа всего)
> Простые фиксы, можно сделать перед релизом.
### VULN-010: CORS Whitelist
**Effort:** 30 минут
**Файл:** `backend/src/index.js`
**Проблема:** CORS разрешает запросы с любого домена.
**Fix:**
```javascript
const corsOptions = {
origin: ['https://wellnuo.smartlaunchhub.com', 'exp://'],
credentials: true
};
app.use(cors(corsOptions));
```
---
### VULN-012: Request Size Limit
**Effort:** 15 минут
**Файл:** `backend/src/index.js`
**Проблема:** Нет лимита на размер body — можно отправить огромный JSON и положить сервер.
**Fix:**
```javascript
app.use(express.json({ limit: '1mb' }));
```
---
### FE-001: Stripe Key в Environment
**Effort:** 15 минут
**Файл:** `services/api.ts` или где используется Stripe
**Проблема:** Stripe publishable key захардкожен.
**Fix:** Вынести в `EXPO_PUBLIC_STRIPE_PUBLISHABLE_KEY`.
---
### FE-006: Убрать console.log в Production
**Effort:** 30 минут
**Файлы:** `babel.config.js`, `package.json`
**Проблема:** Console.log попадают в production build.
**Fix:**
```bash
npm install babel-plugin-transform-remove-console --save-dev
```
```javascript
// babel.config.js
module.exports = {
presets: ['babel-preset-expo'],
env: {
production: {
plugins: ['transform-remove-console']
}
}
};
```
---
### FE-007: JWT Expiration Check
**Effort:** 1 час
**Файл:** `services/api.ts`
**Проблема:** Если токен истёк — пользователь получает непонятные ошибки вместо редиректа на логин.
**Fix:**
```javascript
function isTokenExpired(token) {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp * 1000 < Date.now();
} catch {
return true;
}
}
// Перед каждым запросом
if (isTokenExpired(token)) {
// Редирект на логин
}
```
---
# 🟡 СРЕДНИЕ РЕКОМЕНДАЦИИ (1-3 дня каждая)
> Полезно, но не критично для релиза.
### VULN-006: Логирование (Winston + Sentry)
**Effort:** 1 день
**Приоритет:** Высокий после релиза
**Что делаем:**
1. Winston для structured logs
2. Sentry для алертов об ошибках
3. Audit Trail таблица для истории действий
4. Generic error messages в production (не показывать stack trace)
**Почему важно:** Сейчас если что-то падает — мы не узнаем. Sentry пришлёт алерт.
---
### FE-004: SSL Pinning
**Effort:** 1 день
**Приоритет:** Средний
**Что делаем:**
1. Удалить `app/(tabs)/bug.tsx` (если есть)
2. Добавить SSL pinning для API запросов
3. Проверка URL в WebView
**Почему важно:** Защита от man-in-the-middle атак.
---
### VULN-009: Admin 2FA
**Effort:** 2-3 дня
**Приоритет:** Низкий (мало админов)
**Что делаем:** TOTP или email code для admin операций.
---
### VULN-015: CSRF Protection
**Effort:** 1 день
**Приоритет:** Низкий (мобильное приложение)
**Что делаем:** `csurf` middleware для web версии.
---
### FE-010: SecureStore вместо AsyncStorage
**Effort:** 2-3 часа
**Приоритет:** Средний
**Что делаем:** Перенести tokens из AsyncStorage в SecureStore (зашифрованное хранилище).
---
### FE-014: Deep Links Validation
**Effort:** 2-3 часа
**Приоритет:** Средний
**Что делаем:** Валидировать параметры deep links перед обработкой.
---
### FE-019: Client-Side Rate Limiting
**Effort:** 1-2 часа
**Приоритет:** Низкий
**Что делаем:** Debounce на кнопки отправки форм.
---
### BUG-001 — BUG-005: Race Conditions & Memory Leaks
**Effort:** 1 день
**Приоритет:** Средний
| Баг | Файл | Проблема |
|-----|------|----------|
| BUG-001 | `AuthContext.tsx:44` | Race condition в useEffect |
| BUG-002 | `BeneficiaryContext.tsx:59` | Stale closure |
| BUG-003 | `api.ts:316` | Silent auth failures |
| BUG-004 | `beneficiaries/[id]/index.tsx:146` | Missing dependencies |
| BUG-005 | `beneficiaries/[id]/index.tsx:107` | Interval not cleaned |
---
### Code Quality (SMELL-001 — SMELL-006)
**Effort:** 2-3 дня
**Приоритет:** Низкий
- Duplicate navigation logic
- Magic numbers → constants
- Mixed auth token types
- Excessive `any` types (40+)
- Callback hell → async/await
- Silent fallbacks → add logging
---
# 🔴 БОЛЬШИЕ ЗАДАЧИ (1-2 недели, после релиза)
### VULN-017: Refresh Tokens
**Effort:** 1 неделя (backend + frontend)
**Приоритет:** После релиза
**Сейчас:** JWT живёт 7 дней, нет refresh.
**Рекомендуется:**
- Access token: 15-60 минут
- Refresh token: 7 дней
- Автоматический refresh при истечении
**Комментарий:** Работает и так, но refresh tokens — best practice.
---
### FE-017: Biometric Auth
**Effort:** 3-5 дней
**Приоритет:** После релиза
**Что делаем:** Face ID / Touch ID для входа в приложение.
**Комментарий:** Nice to have, не критично для MVP.
---
### RLS: Row Level Security
**Effort:** 1-2 недели
**Приоритет:** После релиза
**Что это:** Защита данных на уровне базы данных. Даже если баг в коде — пользователь увидит только свои данные.
**Подробный план:** См. ЧАСТЬ 4 в конце этого документа.
**Комментарий:** Серьёзный рефакторинг. Делать когда будет время на полноценное тестирование.
---
# 📋 App Store Compliance
> Требования для публикации в App Store.
| Задача | Статус | Комментарий |
|--------|--------|-------------|
| **PERMISSION-001**: Camera description | ⚠️ Проверить | Нужно человеческое описание в `app.json` |
| **PERMISSION-002**: Microphone description | ⚠️ Проверить | Для голосового AI |
| **DATA-001**: Account deletion | ❌ Нужно | Apple требует возможность удалить аккаунт |
| **PRIVACY-001**: Privacy Policy URL | ⚠️ Проверить | Должен быть в `app.json` |
| **PRIVACY-002**: Privacy Manifest (iOS 17+) | ❌ Нужно | `PrivacyInfo.xcprivacy` файл |
| **CONTENT-001**: AI Chat Consent | ⚠️ Проверить | Согласие перед использованием AI |
---
# 📊 Приоритеты
## Перед релизом (опционально, 2-3 часа):
1. VULN-010: CORS whitelist
2. VULN-012: Request size limit
3. FE-001: Stripe key в env
4. FE-006: Remove console.log
5. FE-007: JWT expiration check
## Сразу после релиза:
1. VULN-006: Winston + Sentry (мониторинг)
2. DATA-001: Account deletion (требование Apple)
3. BUG-001-005: Race conditions
## Когда будет время:
1. Refresh tokens
2. Biometric auth
3. RLS (Row Level Security)
4. SSL Pinning
## Ждём legacy backend:
1. FE-003: Legacy credentials (Phase 2)
2. LEGACY-001-003: Integration fixes (Phase 1/2)
---
# ПРИЛОЖЕНИЕ: RLS (Row Level Security) — План реализации
> Подробный план на случай если решим делать.
## Что это и зачем
**Сейчас:** Проверки доступа в коде backend. Если баг — данные утекут.
**С RLS:** Защита на уровне БД. Даже при баге, SQL injection, или прямом доступе — пользователь увидит только свои данные.
## Этапы
| Этап | Время | Что делаем |
|------|-------|------------|
| 1. Подготовка | 2-3 часа | Создать роль `wellnuo_app`, включить RLS |
| 2. Политики | 4-6 часов | Создать policies для каждой таблицы |
| 3. Query Builder | 1-2 дня | Переделать под RLS |
| 4. Routes | 2-3 дня | Обновить все endpoints |
| 5. Тесты | 1 день | Unit + интеграционные |
| 6. Деплой | 2-3 часа | Миграция + проверка |
**Итого:** 1-2 недели
## Пример политики
```sql
-- Пользователь видит только своих beneficiaries
CREATE POLICY beneficiaries_select ON beneficiaries
FOR SELECT
USING (
id IN (
SELECT beneficiary_id FROM user_access
WHERE accessor_id = current_setting('app.current_user_id', true)
)
);
```
## Пример использования в коде
```javascript
// До (без RLS)
const { data } = await supabase.from('beneficiaries').select('*');
const accessible = data.filter(b => hasAccess(userId, b.id)); // ручная фильтрация
// После (с RLS)
const db = createDb(userId); // привязка к пользователю
const { data } = await db.from('beneficiaries').select('*'); // RLS фильтрует автоматически
```
---
**Файл:** `AUDIT_REPORT.md`
**Обновлён:** 2026-01-26