From 237149e87a77cb716ee174b526d2e4399f834ae9 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Wed, 10 Jun 2026 15:46:20 +0400 Subject: [PATCH] bump v0.90.0 --- ....vscode/mcp.json | 11 ++++ ....vscode/mcp_server.py | 41 +++++++++++++ doc/audit-2026-06-07.md | 124 +++++++++++++++++++++++--------------- tools/analyze_sessions.py | 32 ++++++++++ web/templates/index.html | 4 +- 5 files changed, 161 insertions(+), 51 deletions(-) create mode 100644 ....vscode/mcp.json create mode 100644 ....vscode/mcp_server.py create mode 100644 tools/analyze_sessions.py diff --git a/....vscode/mcp.json b/....vscode/mcp.json new file mode 100644 index 0000000..5f0489e --- /dev/null +++ b/....vscode/mcp.json @@ -0,0 +1,11 @@ +{ + "servers": { + "elmer-server": { + "type": "stdio", + "command": "python3", + "args": [ + "/home/naeel/elmer/.vscode/mcp_server.py" + ] + } + } +} \ No newline at end of file diff --git a/....vscode/mcp_server.py b/....vscode/mcp_server.py new file mode 100644 index 0000000..b498ffb --- /dev/null +++ b/....vscode/mcp_server.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""MCP сервер для Elmer — БД, логи, ssh.""" + +import json, subprocess, sys + +def handle(req): + method = req.get("method", "") + params = req.get("params", {}) + + if method == "list_tools": + return { + "tools": [ + {"name": "query_db", "description": "SQL-запрос к elmer.db", "inputSchema": {"type": "object", "properties": {"sql": {"type": "string"}}}}, + {"name": "server_logs", "description": "Логи сервера (последние N строк)", "inputSchema": {"type": "object", "properties": {"lines": {"type": "number", "default": 30}}}}, + {"name": "ssh", "description": "Выполнить bash-команду на ВМ", "inputSchema": {"type": "object", "properties": {"cmd": {"type": "string"}}}}, + ] + } + + if method == "call_tool": + name = params.get("name", "") + args = params.get("arguments", {}) + + if name == "query_db": + ssh(f"sqlite3 /opt/elmer/elmer.db \"{args['sql']}\"") + elif name == "server_logs": + ssh(f"sudo journalctl -u elmer --no-pager -n {args.get('lines', 30)}") + elif name == "ssh": + ssh(args["cmd"]) + else: return {"error": f"unknown tool: {name}"} + + return {"result": "ok"} + +def ssh(cmd): + r = subprocess.run(["ssh", "-i", "/home/naeel/.ssh/naeel_vm_id_ed25519", "naeel@5.172.178.213", cmd], capture_output=True, text=True) + return {"stdout": r.stdout, "stderr": r.stderr} + +for line in sys.stdin: + line = line.strip() + if line: + resp = handle(json.loads(line)) + print(json.dumps(resp), flush=True) diff --git a/doc/audit-2026-06-07.md b/doc/audit-2026-06-07.md index e16d66c..26a975c 100644 --- a/doc/audit-2026-06-07.md +++ b/doc/audit-2026-06-07.md @@ -1,65 +1,91 @@ -# Отчёт об аудите (07.06.2026) +# Отчёт об аудите безопасности и багах (07.06.2026) -## Найдено +## 🔴 КРИТИЧЕСКИЕ (7) -### 🔴 Критичные +### 1. Жестко заданный API ключ в конфигурации +- **Файл:** [config.yaml](config.yaml#L5) +- **Описание:** \`api_key: "sk-78ec529c1eba4ba69995091046c9fa33"\` — настоящий ключ DeepSeek находится непосредственно в репозитории. +- **Влияние:** Экспозиция платного LLM, финансовый ущерб, возможность несанкционированного использования лимитов. -1. **Краш при отсутствии Bluetooth** - - **Файл:** [android/app/src/main/java/ru/elmer/client/ui/MainActivity.kt](android/app/src/main/java/ru/elmer/client/ui/MainActivity.kt) - - **Строки:** 155, 230, 310 - - **Описание:** Используется оператор `!!` для `btAdapter`. На устройствах без Bluetooth (или в эмуляторе) приложение упадёт при попытке проверить прибор или запустить сканирование. - - **Как исправить:** Добавить проверку `if (btAdapter == null)` перед использованием и выводить сообщение об ошибке. +### 2. check_same_thread=False в SQLite +- **Файл:** [api/db.py](api/db.py#L20) +- **Описание:** Использование \`check_same_thread=False\` без механизмов синхронизации в многопоточном Flask-приложении. +- **Влияние:** Состояние гонки (Race conditions), повреждение базы данных при одновременной записи. -2. **Отсутствие аутентификации на сервере** - - **Файл:** [api/routes.py](api/routes.py) - - **Описание:** Эндпоинты `/api/v1/session/upload`, `/api/v1/chat` и `/api/v1/ping-llm` принимают запросы без проверки API-ключа. Клиент передаёт `X-Api-Key`, но сервер его игнорирует. Любой может отправлять запросы и тратить токены LLM. - - **Как исправить:** Добавить декоратор `@api_key_required` или проверку заголовка в `before_request`. +### 3. /api/v1/ping-llm без аутентификации тратит токены +- **Файлы:** [api/ping.py](api/ping.py#L40-45), [web/script_endpoint.py](web/script_endpoint.py#L215-219) +- **Описание:** Эндпоинт доступен без заголовка \`X-Api-Key\` и выполняет реальный запрос к LLM. +- **Влияние:** Возможность DoS-атаки на кошелек API через бесконечные пинги. -3. **Состязание потоков (Race Condition) в ScriptRunnerService** - - **Файл:** [android/app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt](android/app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt) - - **Описание:** `onStartCommand` не проверяет, запущен ли уже процесс. Если дважды вызвать `startForegroundService` (нажав кнопку несколько раз), создадутся два конкурирующих потока `ScriptRunner`, которые будут одновременно работать с одним и тем же Bluetooth-сокетом. - - **Как исправить:** В `startRun` проверять флаг `running` и игнорировать повторные запуски. +### 4. Краш при отсутствии Bluetooth (Android) +- **Файл:** [android/app/src/main/java/ru/elmer/client/ui/MainActivity.kt](android/app/src/main/java/ru/elmer/client/ui/MainActivity.kt) +- **Описание:** Используется оператор \`!!\` для \`btAdapter\`. На устройствах без Bluetooth приложение упадёт. +- **Влияние:** Нестабильность приложения на эмуляторах и старых устройствах. -4. **Логическая ошибка в выборе Bluetooth-устройства** - - **Файл:** [android/app/src/main/java/ru/elmer/client/ui/MainActivity.kt](android/app/src/main/java/ru/elmer/client/ui/MainActivity.kt#L457) - - **Описание:** Функция `findElmDevice()` при наличии двух и более устройств показывает диалог, но возвращает `null` немедленно. Стейт-машина (`checkElm`, `scanDtc`) видит `null` и прерывает работу с ошибкой «ELM не найден». - - **Как исправить:** Перестроить логику: диалог выбора должен вызываться отдельно, сохранять `elmDevice`, и только потом запускать операции. +### 5. Отсутствие аутентификации на критичных эндпоинтах +- **Файл:** [api/routes.py](api/routes.py#L245), [api/routes.py](api/routes.py#L292) +- **Описание:** Эндпоинты \`POST /api/v1/elm/probe\` и \`GET /api/v1/elm/profile/\` не защищены API-ключом. +- **Влияние:** Идентификация структуры OBD-профилей любых пользователей. -### 🟡 Средние +### 6. Дублирование API с разной логикой +- **Файлы:** [web/script_endpoint.py](web/script_endpoint.py) vs [api/routes.py](api/routes.py) +- **Описание:** Маршрут \`/api/v1/session/upload\` реализован дважды. В \`web/\` версии отсутствует проверка идемпотентности (\`request_id\`). +- **Влияние:** Неконсистентное поведение, дублирование LLM-запросов при ретраях из мобильного приложения. -5. **Утечка памяти в ElmChecker** - - **Файл:** [android/app/src/main/java/ru/elmer/client/elm/ElmChecker.kt](android/app/src/main/java/ru/elmer/client/elm/ElmChecker.kt) - - **Описание:** Список `logLines` является членом класса. В `MainActivity` экземпляр `elmChecker` переиспользуется. Приложение копит логи всех операций в памяти до своей гибели. - - **Как исправить:** Очищать `logLines` в начале каждой операции или переносить лог в локальную переменную метода `run()`. +### 7. Состязание потоков в ScriptRunnerService (Android) +- **Файл:** [android/app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt](android/app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt) +- **Описание:** Повторный запуск сервиса создает новый поток \`ScriptRunner\`, конкурирующий за Bluetooth-сокет. -6. **Утечка курсоров в БД** - - **Файл:** [android/app/src/main/java/ru/elmer/client/db/SessionDb.kt](android/app/src/main/java/ru/elmer/client/db/SessionDb.kt) - - **Описание:** Методы `getResponses`, `getSessions`, `getPendingSessions` вызывают `cursor.close()` в конце цикла, но не в `finally`. При ошибке чтения курсор останется открытым. - - **Как исправить:** Использовать конструкцию `.use { ... }` (в Kotlin для `Cursor` доступно начиная с определенных версий) или `try { ... } finally { cursor.close() }`. +--- -7. **Не включены Foreign Keys** - - **Файл:** [android/app/src/main/java/ru/elmer/client/db/SessionDb.kt](android/app/src/main/java/ru/elmer/client/db/SessionDb.kt) - - **Описание:** Несмотря на наличие `REFERENCES sessions(id)`, SQLite в Android по умолчанию не проверяет целостность связей. - - **Как исправить:** Добавить `db.setForeignKeyConstraintsEnabled(true)` в `onConfigure`. +## 🟡 ВАЖНЫЕ (10) -8. **Слепой выбор устройства в сервисе** - - **Файл:** [android/app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt](android/app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt#L143) - - **Описание:** Сервис берёт `bonded[0]` — первое попавшееся сопряжённое устройство. Это могут быть наушники или магнитола. - - **Как исправить:** Передавать MAC-адрес выбранного ELM через `Intent`. +### 8. Database.close() не гарантирован в web/ +- **Файл:** [web/script_endpoint.py](web/script_endpoint.py#L117-120) +- **Описание:** Соединение с БД открывается, но не закрывается в блоке \`finally\`. +- **Влияние:** Утечка дескрипторов файлов и соединений SQLite. -9. **Отсутствие лимита на размер запроса** - - **Файл:** [api/routes.py](api/routes.py) - - **Описание:** Сервер принимает список `responses` любого размера. Злоумышленник может отправить миллион строк, вызвав OOM или переполнение диска логами. - - **Как исправить:** Проверять `len(responses)` перед обработкой. +### 9. Отсутствие checkpoint для WAL в SQLite +- **Файл:** [api/db.py](api/db.py#L22) +- **Описание:** Режим WAL включен, но \`wal_checkpoint\` никогда не вызывается явно. Журналы могут расти бесконечно. -### 🟢 Косметика +### 10. Утечка курсоров в БД (Android) +- **Файл:** [android/app/src/main/java/ru/elmer/client/db/SessionDb.kt](android/app/src/main/java/ru/elmer/client/db/SessionDb.kt) +- **Описание:** Курсоры закрываются только в конце успешных циклов, а не в \`finally\`. -10. **Неиспользуемые переменные** - - В `ElmProtocol.kt` константа `INIT_TIMEOUT` и другие не используются. - - В `MainActivity.kt` список `chatHistory` хранится в памяти, но не восстанавливается после `onSaveInstanceState`. +### 11. Не включены Foreign Keys (Android) +- **Файл:** [android/app/src/main/java/ru/elmer/client/db/SessionDb.kt](android/app/src/main/java/ru/elmer/client/db/SessionDb.kt) +- **Описание:** SQLite игнорирует \`REFERENCES\` без явной команды \`PRAGMA foreign_keys = ON\`. -11. **Устаревший API** - - `BluetoothAdapter.getDefaultAdapter()` помечен как Deprecated. В современных Android рекомендуется использовать `BluetoothManager`. +### 12. Раскрытие sensitive info в ошибках +- **Файлы:** [web/script_endpoint.py](web/script_endpoint.py#L127), [api/ping.py](api/ping.py#L48) +- **Описание:** \`str(e)\` пробрасывается клиенту, может содержать детали API или токены. -12. **Бесполезный пинг LLM** - - Эндпоинт `/api/v1/ping-llm` делает реальный `diagnose`, что стоит денег (токенов). Кэш есть, но при перезапуске сервера или по таймауту он всё равно будет жечь токены на "пустые" проверки. +### 13. Нет лимита на размер payload +- **Описание:** Сервер принимает JSON любого объема, что ведет к OOM (Out Of Memory). + +### 14. Нет rate-limiting +- **Описание:** Отсутствует защита от перебора ключей и спама запросами. + +### 15. Отсутствие CORS ограничений +- **Файл:** [web/app.py](web/app.py#L48) +- **Описание:** Flask слушает на \`0.0.0.0\`, разрешая запросы с любых источников. + +### 16. Уязвимость потокобезопасности AndrOBD +- **Файл:** [obd/protocol.py](obd/protocol.py#L114-121) +- **Описание:** Метод \`send()\` не синхронизирован, состояние протокола может быть повреждено при параллельном доступе. + +### 17. Слепой выбор Bluetooth-устройства в сервисе (Android) +- **Файл:** [android/app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt](android/app/src/main/java/ru/elmer/client/script/ScriptRunnerService.kt#L143) +- **Описание:** Берется первое сопряженное устройство (\`bonded[0]\`), что часто ошибочно. + +--- + +## 📋 ЗАМЕЧАНИЯ ПО АРХИТЕКТУРЕ +- **Dynamic Imports:** В \`web/script_endpoint.py\` импорты используют \`elmer.*\`, что может конфликтовать с установленными пакетами. +- **Git Hygiene:** Файл \`config.yaml\` содержит секреты и должен быть добавлен в \`.gitignore\` с предоставлением \`config.yaml.example\`. +- **Логирование:** Недостаточно информации для трассировки багов пользователя (отсутствуют IP и User-Agent в логах сессий). + +--- +*Дата аудита: 07.06.2026* +*Инструмент: GitHub Copilot (Gemini 3 Flash)* diff --git a/tools/analyze_sessions.py b/tools/analyze_sessions.py new file mode 100644 index 0000000..b071f21 --- /dev/null +++ b/tools/analyze_sessions.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +import sqlite3, json +db = sqlite3.connect("/opt/elmer/elmer.db") +for sid in [41, 40]: + r = db.execute("SELECT id, created_at, raw_responses FROM sessions WHERE id=?", (sid,)).fetchone() + print(f"\n=== SESSION #{r[0]} {r[1]} ===") + if not r[2]: print(" (no raw data)"); continue + data = json.loads(r[2]) + print(f" Total responses: {len(data)}") + cmds = {} + for d in data: + c = d.get("cmd","?") + s = d.get("step_id","?") + raw = d.get("raw","") + dec = d.get("decoded","") + key = f"{s} ({c})" + if key not in cmds: + cmds[key] = {"cnt": 0, "err": 0, "empty": 0, "ok": 0, "samples": []} + cmds[key]["cnt"] += 1 + if not raw or raw in ["?","(err)","NO DATA"]: + cmds[key]["err"] += 1 + elif raw == "": + cmds[key]["empty"] += 1 + else: + cmds[key]["ok"] += 1 + if len(cmds[key]["samples"]) < 2: + cmds[key]["samples"].append(f"{raw} -> {dec}") + for k, v in sorted(cmds.items()): + print(f" {k:30s} total={v['cnt']:3d} ok={v['ok']} err={v['err']} empty={v['empty']}") + for s in v["samples"]: + print(f" {s}") +db.close() diff --git a/web/templates/index.html b/web/templates/index.html index f520916..cea6641 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -13,14 +13,14 @@ elmAI

elmAI

Диагностика авто через ELM327 + ИИ

-

v0.89.0-dev — 7 июня 2026

+

v0.90.0-dev — 7 июня 2026

📱 Скачай приложение на телефон:

⬇️ Скачать elmAI APK -

v0.89.0-dev • нажмите чтобы скачать

+

v0.90.0-dev • нажмите чтобы скачать