Дата: 2026-06-02
Версия PHP: 8.x
Файлов: 96 .php
Фреймворк: Fury (кастомный MVC)
Каждая фаза — законченный пакет работ. Внутри фазы задачи отсортированы по приоритету (критичные → высокие → средние → низкие). Фазы следуют друг за другом: нет смысла начинать Phase 3, пока не закрыта Phase 1.
Приоритеты:
Цель: Закрыть векторы, через которые злоумышленник может получить полный доступ к системе.
Коммит: 175224e (ветка dev)
Блокер для следующих фаз: нет смысла строить валидацию и обработку ошибок поверх дыр в аутентификации и SQL.
Где: server/SHServ/Routes.php:58-82
Что: Все endpoint'ы /api/v1/* открыты без проверки сессии/токена. Любой HTTP-запрос может управлять устройствами, менять скрипты, перезагружать реле.
Фикс:
Routes.php (или App.php) перед роутингом.Authorization: Bearer <token> или Cookie: auth_token=<token>.POST /events/new (устройства шлют без сессии, но с device token)./dev/test/*) если devmode === false.Где:
server/Fury/Modules/ThinBuilder/ThinBuilderProcessing.php:40-108server/Fury/Modules/ThinBuilder/ThinBuilder.php:58-87Что: addslashes() используется как основной механизм экранирования. Это недостаточно для MySQL (возможен multibyte-обход). Все insert, update, delete и where собирают raw SQL строки конкатенацией.
Фикс:
ThinBuilder на PDO prepared statements.? плейсхолдеры + bindValue().Где: server/SHServ/config.php:6,13-14,20
Что:
debug => true и devmode => true в коммите — стектрейсы и dev-tools доступны на проде.db.user = "eugene", db.password = "root" в plaintext в git.Фикс:
.env файл на уровне server/ (добавить в .gitignore).vlucas/phpdotenv или простой парсер.config.php оставить как config.example.php с placeholder'ами.config.php → .env-based загрузку.debug = false, devmode = false. Переопределять через env.Где: server/SHServ/Models/Example_Auth.php:24
Что: Пароли хешируются sha1(), который криптографически сломан и быстрый к брутфорсу.
Фикс:
password_hash($password, PASSWORD_ARGON2ID) при регистрации/смене.password_verify($password, $hash) при входе.password_hash, при первом успешном password_verify обновлять из sha1.Где: server/SHServ/Sessions.php:13
Что: Токен генерируется uniqid($uid . time()) — низкая энтропия, предсказуем.
Фикс:
$token = bin2hex(random_bytes(32));
Где: server/SHServ/Sessions.php:38
Что: setcookie("auth_token", ...) без HttpOnly, Secure, SameSite. Увеличивает impact XSS.
Фикс:
setcookie("auth_token", $token, [
'expires' => time() + 86400 * 30,
'path' => '/',
'httponly' => true,
'secure' => true, // если HTTPS
'samesite' => 'Strict',
]);
get.config сливает конфигГде: server/console.php:10-12
Что: Команда get.config выводит весь массив FCONF (включая пароль БД) без аутентификации.
Фикс: Удалить команду или закрыть проверкой локального файла-ключа (например, только если php_sapi_name() === 'cli' и uid = владелец файла).
Цель: Убрать silent failures, сделать транзакции атомарными, починить инвертированную логику error handler'а.
Коммит: 1a30037 (ветка dev)
Блокер: пока error handler инвертирован, трудно доверять логам и репортам.
Где: server/Fury/Modules/ErrorHandler/ErrorHandler.php:26-29
Что:
if(!FCONF["debug"]) {
error_reporting(-1);
}
Ошибки показываются, когда debug выключен. Логика наоборот.
Фикс: Поменять ветки — показывать error_reporting(-1) когда debug === true, подавлять (или логировать) когда false.
Где: server/Fury/Modules/ErrorHandler/ErrorHandler.php:41
Что: set_exception_handler закомментирован — uncaught exceptions утекают со стектрейсами.
Фикс: Раскомментировать и направлять через exception_handler.
Где: server/SHServ/Models/Devices.php:30-82
Что: connect_new_device делает два INSERT'а (devices, device_auth) без транзакции. Если второй упадёт, первый остаётся сиротой. Есть fallback-удаление, но оно не сработает при fatal error между INSERT'ами.
Фикс:
$db->beginTransaction();
try {
// insert devices
// insert device_auth
$db->commit();
} catch (\Exception $e) {
$db->rollBack();
throw $e;
}
null на SQL-ошибкахГде: server/Fury/Modules/ThinBuilder/ThinBuilder.php:28-30
Что: query() возвращает null при ошибке SQL, не логируя и не бросая исключение. TODO-комментарий подтверждает, что это известный баг.
Фикс:
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION в DB.php.return null — пусть PDO бросает.try/catch в контроллере, маппинг в response_error.response_error должен ставить HTTP-статусГде: server/SHServ/Utils.php:14-30
Что: response_error() всегда отдаёт HTTP 200 OK. Клиенты (и прокси) не могут отличить ошибку от успеха по статусу.
Фикс:
function response_error($msg, $alias = "error", $status_code = 400) {
http_response_code($status_code);
// ...
}
Контроллеры передают подходящий код: 400 для клиентских, 500 для серверных.
Где:
server/SHServ/Models/Areas.php:103-105 — remove_obsolete() пустойserver/SHServ/Helpers/Validator.php — класс пустой, не используетсяФикс: Реализовать или удалить.
Цель: Валидация входных данных, единообразные ответы, защита от abuse.
Коммит: 35f9ec8 (ветка dev)
Блокер: валидация входных данных бессмысленна, если за ней всё равно стоит уязвимый ThinBuilder (Phase 1). Поэтому Phase 3 идёт после Phase 1.
Где:
server/SHServ/Controllers/AreasRESTAPIController.php:52-77 — new_area не валидирует type, alias, display_nameserver/SHServ/Controllers/DevicesRESTAPIController.php:32 — IP проверяется только strlen < 7server/SHServ/Controllers/ScriptsRESTAPIController.php:19-29 — run_action_script передаёт сырые $params в callable без валидацииserver/SHServ/Controllers/DevicesRESTAPIController.php:206 — do_device_action передаёт сырые $params на устройствоФикс:
Validator (или использовать filter_var + regex).alias — /^[a-z0-9_]+$/, display_name — max 255, type — whitelist.filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4).$params — whitelist по action alias или JSON Schema.scope_file возвращает не JSONГде: server/SHServ/Controllers/ScriptsRESTAPIController.php:59-79
Что: На success возвращает raw PHP source code, на error — JSON. Клиент должен угадывать формат.
Фикс: Всегда JSON-обёртка:
{ "status": true, "data": { "source": "<?php ..." } }
Где: server/SHServ/Controllers/ScriptsRESTAPIController.php:81-102
Что: Принимает $path и $file от клиента, пишет на диск с минимальной проверкой (strpos($filepath, ".php")). Можно писать вне ControlScripts/Scopes/.
Фикс:
__DIR__ . "/../../ControlScripts/Scopes/".realpath($filepath) начинается с разрешённого префикса.set_script_state молча проглатывает невалидные значенияГде: server/SHServ/Controllers/ScriptsRESTAPIController.php:110-120
Что: Любое значение, не равное "enable", трактуется как disable. Невалидный запрос ("foobar") получает success.
Фикс:
if (!in_array($state, ["enable", "disable"], true)) {
return response_error("Invalid state", "invalid_state", 400);
}
Где: server/SHServ/Routes.php:58-82
Что: Нет rate limiting. Можно спамить do_device_action, /events/new, сканирование.
Фикс: Простой in-memory rate limiter (на основе IP) или nginx limit_req для начала. Для критичных endpoint'ов — 10 req/sec per IP.
Цель: Сделать работу с устройствами отказоустойчивой и неблокирующей.
Коммит: b4968d4 (ветка dev)
Где: server/SHServ/Tools/DeviceScanner.php:72-98
Что: scan_ips создаёт curl handle для каждого IP диапазона. Для /24 — 253 хендла одновременно. Может исчерпать fd и память.
Фикс: Batch-сканирование, sliding window (например, 32 параллельных хендла максимум). Остальные ставить в очередь.
Где: server/SHServ/Tools/DeviceAPI/Base.php:138-226
Что: Один transient network failure = полный провал запроса. Нет повторных попыток.
Фикс: Добавить retry loop (3 попытки, backoff 100ms, 300ms). Только для идемпотентных операций (status, action). POST на /setup — без retry или с idempotency key.
Где: server/SHServ/Tools/DeviceAPI/Base.php:178-179
Что: CONNECTTIMEOUT => 1, TIMEOUT => 5 — не конфигурируются.
Фикс: Вынести в FCONF['device_api_timeout'] или device-level config.
Где:
server/SHServ/Controllers/DevicesRESTAPIController.php:177 — device_statusserver/SHServ/RequiredControlScriptsScope.php:12-21 — online event handler вызывает get_about()Что: PHP-поток блокируется на время cURL timeout. Если устройство offline, ждём полные 5 секунд.
Фикс:
curl_multi (уже есть в DeviceScanner, переиспользовать).reset_device игнорирует ответ устройстваГде: server/SHServ/Controllers/DevicesRESTAPIController.php:382
Что: Вызывает $device->device_api()->reset() и не проверяет результат. Если устройство offline, клиент получает "success".
Фикс: Проверять http_code == 200, иначе device_request_fail.
Цель: Убрать мёртвый код, дедупликацию, странные сайд-эффекты.
Коммит: d9c9e17 (ветка dev)
Можно выполнять параллельно с Phase 2–4, но не раньше Phase 1.
Где: server/SHServ/App.php:35-36
Что: На каждый HTTP-запрос принудительно включается spotlights_off action script. Это не должно жить в bootstrap.
Фикс: Вынести в миграцию/seed-скрипт, выполняемый один раз при установке.
place_in_area / unassign_from_areaГде:
server/SHServ/Controllers/DevicesRESTAPIController.php:238-279server/SHServ/Controllers/AreasRESTAPIController.php:108-129Что: Логика размещения устройства в area дублируется почти дословно.
Фикс: Вынести в сервис AreaPlacementService или shared validator.
is_dir($item) проверяет CWD, не Scopes/Где: server/SHServ/App.php:92
Что:
array_filter($scripts_dir, function($item) {
return !is_dir($item) and ...;
});
$item — имя без пути, is_dir проверяет текущую директорию, а не ControlScripts/Scopes/.
Фикс:
!is_dir(__DIR__ . "/../ControlScripts/Scopes/" . $item)
$device вместо $scriptГде: server/SHServ/Controllers/ScriptsRESTAPIController.php:143,146-148
Что: Переменная называется $device, но содержит Script. На ошибке возвращается alias "device_not_found" вместо "script_not_exists".
Фикс: Переименовать и исправить alias.
User.php конструирует Profile с $uidГде: server/SHServ/Entities/User.php:19
Что: new Profile($uid) — вместо profile_id передаётся uid. TODO-комментарий подтверждает баг.
Фикс: Передать реальный profile_id (запросить из БД или взять из поля profile_id).
Где: server/SHServ/Tools/DeviceAPI/Hatch.php:21,29,37,45
Что: Методы is_opened, is_closed, is_opening, is_closing ссылаются на $status_response, которой нет в scope.
Фикс: Использовать $state или корректное имя переменной.
Где:
server/ControlScripts/Scopes/OfficeRoomScope.php:71-98server/ControlScripts/Scopes/SpotlightsScope.php:127-160server/ControlScripts/Scopes/TestScriptsScope.php:31-71Что: Большие закомментированные блоки.
Фикс: Удалить или перенести в docs/notes.md.
ControlScripts/Common.phpГде: server/ControlScripts/Common.php:8-123
Что: Десятки device alias'ов (spotlight_main_back_1, buttons_backdoor...) зашиты в код.
Фикс: Вынести sync map в БД или dedicated YAML/JSON конфиг.
root_folder() хрупкийГде: server/SHServ/App.php:63-65
Что:
list($root) = explode('SHServ', __DIR__);
Сломается, если директория переименуется.
Фикс:
return dirname(__DIR__, 2);
get_all() magic numberГде: server/SHServ/Models/Areas.php:92
Что: Хардкод limit 1000.
Фикс: Константа AREAS_MAX_LIMIT или pagination.
Где: server/Fury/Kernel/Logging.php:88-96
Что: Логи пишутся в SHServ/Logs/ (под web root), chmod 0755, нет flock.
Фикс:
chmod 0640.flock() или log-rotate.| Файл | Phase | Проблемы |
|---|---|---|
SHServ/config.php |
1.3 | Secrets, debug mode |
SHServ/App.php |
1.1, 2.3, 5.1, 5.3, 5.9 | Auth middleware, сет-эффект, is_dir, root_folder |
SHServ/Routes.php |
1.1, 3.5 | Нет auth, нет rate limit |
SHServ/Sessions.php |
1.5, 1.6 | Токены, cookie flags |
SHServ/Models/Example_Auth.php |
1.4 | SHA1 |
SHServ/Controllers/ScriptsRESTAPIController.php |
3.1, 3.2, 3.3, 3.4, 5.4 | Валидация, raw ответ, path traversal, alias |
SHServ/Controllers/DevicesRESTAPIController.php |
3.1, 4.5 | IP валидация, reset ignore response |
SHServ/Controllers/AreasRESTAPIController.php |
3.1 | Валидация area |
SHServ/Controllers/EventsController.php |
3.1 | Валидация event |
SHServ/Models/Devices.php |
2.3 | Нет транзакции |
SHServ/Models/Areas.php |
5.10 | Magic number, пустой stub |
SHServ/Models/Scripts.php |
1.7 | unlink без auth |
SHServ/Tools/DeviceScanner.php |
4.1 | 253 concurrent curl |
SHServ/Tools/DeviceAPI/Base.php |
4.2, 4.3 | Нет retry, hardcoded timeouts |
SHServ/Tools/DeviceAPI/Hatch.php |
5.6 | Undefined variable |
Fury/Modules/ThinBuilder/ThinBuilder.php |
1.2, 2.4 | SQL injection, silent null |
Fury/Modules/ThinBuilder/ThinBuilderProcessing.php |
1.2 | SQL injection |
Fury/Modules/ErrorHandler/ErrorHandler.php |
2.1, 2.2 | Инвертированная логика, закомментированный handler |
Fury/Kernel/Logging.php |
5.11 | Логи в web root |
console.php |
1.7 | Слив конфига |