bump v0.90.0

This commit is contained in:
Repinoid
2026-06-10 15:46:20 +04:00
parent e763999e85
commit 237149e87a
5 changed files with 161 additions and 51 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"servers": {
"elmer-server": {
"type": "stdio",
"command": "python3",
"args": [
"/home/naeel/elmer/.vscode/mcp_server.py"
]
}
}
}
+41
View File
@@ -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)
+75 -49
View File
@@ -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/<mac>\` не защищены 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)*
+32
View File
@@ -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()
+2 -2
View File
@@ -13,14 +13,14 @@
<img src="/static/logo.png" alt="elmAI" style="width:96px;height:96px;border-radius:20px;margin-bottom:10px;">
<h1>elmAI</h1>
<p class="subtitle">Диагностика авто через ELM327 + ИИ</p>
<p class="subtitle" style="font-size:12px;opacity:0.7;">v0.89.0-dev — 7 июня 2026</p>
<p class="subtitle" style="font-size:12px;opacity:0.7;">v0.90.0-dev — 7 июня 2026</p>
<div class="card" style="text-align:center;margin-bottom:20px;">
<p style="margin:0 0 10px 0;">📱 Скачай приложение на телефон:</p>
<a href="/static/app-debug.apk" style="color:#ff6b35;font-size:18px;font-weight:bold;text-decoration:none;">
⬇️ Скачать elmAI APK
</a>
<p style="font-size:11px;opacity:0.6;margin:4px 0 0 0;">v0.89.0-dev • нажмите чтобы скачать</p>
<p style="font-size:11px;opacity:0.6;margin:4px 0 0 0;">v0.90.0-dev • нажмите чтобы скачать</p>
</div>
<!-- Кнопка десктоп-диагностики скрыта — только для разработчика с прямым ELM327 -->