Подхвачено из закрытой параллельной сессии (план project_hardening_2026).
Загрузки: magic.js получает safeExt/EXT_FOR_MIME — имя файла на диске берёт
расширение из проверенного MIME, а не из client originalname (анти stored-XSS
.html/.svg). avatar/flashcard/chat-загрузки дополнительно проверяют magic-байты:
содержимое должно соответствовать MIME, иначе файл удаляется и 400.
Доступ: fileController.getFolderAccess отдаёт список раздачи только владельцу
или админу (была утечка имён/email учеников). testController.getOne гейтит
видимость как list() — ученик не прочитает тексты заданий черновиков/вариантов
по id.
XSS: classes.html escJ() экранирует строку для JS-литерала в inline-onclick
(имя ученика с кавычкой больше не ломает обработчик).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P0 целостность банка. Аудит показал: «180 битых» — ложная тревога (177 это
fill-blank с ответом в correct_text). Реально битых MCQ — 3 (single без верного
варианта), без темы — 1020 (все по математике).
- testController.update: нельзя опубликовать тест ученикам, если в нём есть
вопрос без правильного ответа (нет верного варианта И нет correct_text);
возвращает список brokenQuestions. unanswerableInTest() — переиспользуемо.
- scripts/fix-question-bank.js: ИИ-ремонт через шлюз Kilo. --broken (выбрать
верный вариант среди существующих), --topics (привязать матем-вопросы к
существующим темам, батчами). DRY-RUN по умолчанию → предложения в JSON на
вычитку → --apply применяет. --limit N для теста. Проверено: broken 3/3
(graph-вопросы корректно → ручная проверка), topics 12/12 в адекватные темы.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Раньше ученик видел лишь 1 тест на предмет (дефолтный). Теперь учитель/админ
может пометить любой свой тест доступным, и он появляется в каталоге на дашборде.
- Миграция 079: tests.available_to_students (default 0).
- testController: list для ученика отдаёт тесты с available_to_students=1 и вопросами;
create/update принимают флаг; update сделан частичным (не затирает поля при toggle).
- admin «Тесты»: бейдж «Доступен ученикам» + быстрый тумблер «Ученикам/Скрыть»
(toggleTstAvail; конструктор доступен и учителям — видят свои тесты).
- Дашборд: виджет «Тесты» → секция «Доступные тесты» (loadAvailableTests), клик
запускает фикс-тест. Прячется, если доступных нет.
⚠️ Живой БД нужен npm run migrate (колонка).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Безопасность:
- tests/🆔 скрыть is_correct и explanation для студентов (P0)
- SQL injection: limit/offset через placeholder вместо template literal
- Stored XSS: stripTags для lesson comments, flashcards, redBook sightings
- profile.html: escape e.message в showMsg (XSS через server error)
- attachment_url: валидация только /uploads/* путей
- requestId: генерировать UUID сервером, не доверять клиенту
- register: скрыть token_version из ответа
Надёжность:
- register: обработка UNIQUE constraint race condition
- pet buyBg: re-check баланса внутри транзакции
- DB errors: скрыть e.message в testController/questionController/courseController
- preferences: лимит 50KB на размер JSON
UX:
- board.html: debounce 250ms на search input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>