diff --git a/HISTORY.md b/HISTORY.md index d8f214c..4c2b583 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8056,3 +8056,63 @@ ROADMAP називав три причини, з яких ламається с пісочниці народжується чистою. І `журнал аудиту 0 → 0` — проба з правилом `min`, тобто нуль у ній нічого не доводить; чому після повної установки аудит порожній, варто подивитись окремо. + +--- + +## 2026-08-29 — Хто заходив і хто що вимкнув + +Дві сліпі зони, знайдені питанням «чому в пісочниці аудит порожній». +Відповідь на саме питання виявилась нудною (установник робить лише те, +що в переліку сліпих зон), а от дорога до неї — ні. + +### Історія входів: 93 записи були в базі й ніде не показувались + +`core.login_attempts` наповнювалась на кожній спробі входу від першого +дня. Читалась рівно в одному місці — для стримування підбору. Ні +ендпойнта, ні сторінки, ні згадки у вебі. Тобто на питання «хто заходив +у мій моніторинг» продукт мав відповідь у базі й не мав способу її +показати. Для B2B це перше, що питає служба безпеки клієнта. + +**Головним була не верстка.** У таблиці немає `tenant_id` — саме тому ці +події й не в аудиті (журнал вимагає кабінет, а на момент перевірки +пароля кабінет ще невідомий). Показати «як є» означало б віддати одному +кабінету спроби входу чужих людей. Прив'язка непряма: спроба → +користувач → членство, трьома шляхами одночасно (`user_id`, `username`, +`email`) в одному `JOIN LATERAL` з `tenant_id` усередині. Саме `JOIN`, а +не `LEFT JOIN`: без збігу рядок ПРИБИРАЄТЬСЯ, а не лишається без імені. + +На бойових даних це виявилось не теорією: у власника **немає пошти**, +тож за `email` не прив'язується ЖОДНА з 94 спроб. Шлях через `username` +дає 93. Тобто зіставлення «як усі роблять» показало б порожню сторінку — +і виглядало б справним. + +Невдала спроба з неіснуючим логіном не належить нікому. Обрано: +**рядком не показувати, але порахувати** — «ще N спроб з M адрес не +показані». Сигнал «логіни перебирають» лишається, чужа людина в чужий +кабінет не потрапляє. Однозначно правильного варіанта тут немає, і в +коді так і написано. + +### Аудит: алерти й склад команди + +За цілий день роботи (драбина, тригери, канал, правила відповідності, +політика відкату) у журналі не з'явилось нічого. Тепер пишуться правила +алертів, канали, драбини, правила відповідності, а також додавання +людини в кабінет, зміна ролі, вилучення й скидання пароля. + +Два рішення про зміст: + +* **вимкнення видно з НАЗВИ дії** (`alr.rule.disable`), а не з різниці + подробиць. Питання «хто вимкнув правило, за яким приходив алерт» + має відповідатись переліком, а не порівнянням двох записів; +* **`config` каналу не їде в запис ВЗАГАЛІ.** Там не лише токен бота: у + webhook-каналі лежить адреса (доступ на запис у чужий чат) і заголовок + `Authorization`. Замість вмісту — прапорець `secret_changed`. + +Перевірено наживо: створення й видалення каналу лягли в журнал, токен у +ньому не з'явився. + +### Помилкова тривога, названа для чесності + +Я вирішив, що сім подій у `core.event_outbox` «висять». Ні: усі сім +позначені доставленими, а `pruneLoop` щогодини прибирає доставлені, +старші за граничний вік. Найстаршій було 11 годин. Механізм справний.