35 KiB
Технический справочник системы EDR
Endpoint Detection and Response — подробная документация реализации
Содержание
- Архитектура системы
- Агент мониторинга
- Модуль машинного обучения
- Серверная часть — API
- База данных
- Веб-интерфейс
- Развёртывание (Docker)
- Конфигурация и переменные окружения
- Расхождения с дипломным документом
1. Архитектура системы
Компоненты
┌──────────────────────────────────────────────────────────────────────┐
│ Linux-хост (объект мониторинга) │
│ │
│ ┌──────────────────┐ HTTP POST /api/metrics │
│ │ Агент │ ──────────────────────────▶ ┌───────────────┐ │
│ │ agent/main.py │ │ Бэкенд │ │
│ │ │ │ FastAPI │ │
│ │ • psutil │ │ :8000 │ │
│ │ • watchdog │ │ │ │
│ │ • EnsembleDetector │ • SQLite │ │
│ └──────────────────┘ │ • WebSocket │ │
│ └───────┬───────┘ │
│ │ │
│ WS /ws (push) │
│ │ │
│ ┌───────▼───────┐ │
│ │ Фронтенд │ │
│ │ React SPA │ │
│ │ :3000 │ │
│ └───────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
Поток данных (один цикл, 5 секунд)
psutil.process_iter() watchdog inotify
│ │
▼ ▼
collect_processes() _FSHandler._push()
│ │
▼ │
EnsembleDetector.predict() │
│ │
└──────────────┬────────────┘
▼
send_batch() → POST /api/metrics
│
┌─────────────┼───────────────┐
▼ ▼ ▼
INSERT metrics INSERT file_events INSERT system_metrics
│
аномалии найдены?
│ да
┌───────▼──────────┐
│ дедупликация 30с │
└───────┬──────────┘
новая? │ существующая? │
▼ ▼
INSERT alerts UPDATE alerts
+ WS broadcast count+1
2. Агент мониторинга
Файлы
| Файл | Назначение |
|---|---|
agent/main.py |
Цикл сбора метрик, watchdog, отправка батчей |
agent/model.py |
EnsembleDetector — ML-ансамбль |
agent/train.py |
Standalone-скрипт для предварительного обучения |
agent/requirements.txt |
Зависимости Python |
agent/Dockerfile |
Образ агента |
Сбор метрик процессов
# Атрибуты, запрашиваемые через psutil (psutil 6.x убрал 'connections' из attrs)
attrs = ["pid", "name", "cpu_percent", "memory_info", "open_files"]
# CPU нормализован на число логических ядер
cpu_percent_normalized = info["cpu_percent"] / _CPU_COUNT
Важно: нормализация CPU. psutil возвращает суммарную процессорную нагрузку — на 4-ядерной машине процесс может показывать 400%. Агент делит на psutil.cpu_count(logical=True), приводя значение к диапазону 0–100% от мощности всей машины. Это обеспечивает корректное сравнение признаков между хостами с разным числом ядер.
Совместимость net_connections
В psutil 6.x метод Process.connections() переименован в Process.net_connections(). Агент обходит это через:
def _proc_connections(proc: psutil.Process) -> int:
for method in ("net_connections", "connections"):
fn = getattr(proc, method, None)
if fn is None:
continue
try:
return len(fn())
except (psutil.NoSuchProcess, psutil.AccessDenied, OSError):
return 0
return 0
Прогрев CPU
Первый вызов cpu_percent() в psutil всегда возвращает 0.0 — библиотека требует временного интервала для расчёта дельты. При старте выполняется:
psutil.cpu_percent(interval=None) # системный вызов
for proc in psutil.process_iter(["cpu_percent"]):
proc.cpu_percent(interval=None) # на процесс
time.sleep(1) # интервал измерения
Мониторинг файловой системы
| Директория | Причина наблюдения |
|---|---|
/etc |
Persistence-атаки через изменение конфигурации |
/tmp |
Типичное место загрузки malware-полезной нагрузки |
/var/log |
Попытки зачистки следов атаки |
Буфер событий ограничен 500 записями (FIFO). При переполнении самые старые события вытесняются.
Формат батча (JSON → POST /api/metrics)
{
"timestamp": "2025-05-28T14:30:00.000000+00:00",
"processes": [
{
"pid": 1234,
"name": "nginx",
"cpu_percent": 2.1,
"memory_mb": 45.3,
"open_files": 22,
"connections": 8,
"is_anomaly": false
}
],
"system": {
"cpu_percent": 12.5,
"ram_percent": 34.2,
"network_connections": 47
},
"file_events": [
{
"path": "/tmp/suspicious.sh",
"event_type": "create",
"timestamp": "2025-05-28T14:29:58.000000+00:00"
}
]
}
Поле is_anomaly заполняется агентом на основе результата EnsembleDetector.predict() до отправки. Бэкенд дополнительно проверяет пороговые значения.
3. Модуль машинного обучения
Класс
agent/model.py → EnsembleDetector (экспортируется как AnomalyDetector для обратной совместимости)
AnomalyDetector = EnsembleDetector # алиас в конце model.py
Вектор признаков
FEATURE_COLS = ["cpu_percent", "memory_mb", "open_files", "connections"]
| Признак | Тип | Индикатор компрометации |
|---|---|---|
cpu_percent |
float, 0–100 | Криптомайнеры, брутфорс, ransomware |
memory_mb |
float, МБ | Heap overflow, утечки памяти |
open_files |
int | Шифровальщики (mass file access) |
connections |
int | C2-коммуникации, DDoS, сканирование |
Предобработка: RobustScaler
x' = \frac{x - \text{median}(X_{\text{train}})}{\text{IQR}(X_{\text{train}})}
Параметры сохраняются в data/model.pkl под ключом "scaler" и применяются при инференсе без повторного обучения.
Алгоритмы ансамбля
IsolationForest(
n_estimators=100, # число деревьев изоляции
contamination=0.01, # ожидаемая доля аномалий: 1%
random_state=42,
n_jobs=-1, # параллельное обучение на всех ядрах
)
LocalOutlierFactor(
n_neighbors=20, # размер локального окружения k
contamination=0.01,
novelty=True, # режим инференса на новых данных (обязательно!)
n_jobs=-1,
)
OneClassSVM(
kernel="rbf", # гауссово ядро (RBF)
nu=0.01, # верхняя оценка доли аномалий
gamma="scale", # γ = 1 / (n_features * Var(X))
)
Правило голосования
Каждая модель возвращает v_i \in \{+1, -1\}:
+1— нормальный процесс-1— аномальный
\text{is\_anomaly} = \left[\sum_{i=1}^{3} v_i \leq -1\right]
Сумма -1 означает 2 голоса «аномалия», -3 — единогласно. Один «выброс» от одного алгоритма не даёт алерт.
Доверенные процессы (TRUSTED_PROCESSES)
Для 30+ процессов (браузеры, Electron-приложения, IDE, медиаплееры) ML-классификация пропускается. Полный список:
TRUSTED_PROCESSES = frozenset({
"firefox", "firefox-bin", "Web Content", "Isolated Web Co",
"chrome", "chromium", "chromium-browser",
"brave", "brave-browser", "opera",
"electron", "Electron",
"telegram", "Telegram", "telegram-desktop",
"slack", "discord", "discord-bin",
"code", "code-oss", "vscodium", "cursor",
"zoom", "zoom-bin", "zoomus",
"obsidian",
"spotify", "vlc", "mpv",
"libreoffice", "soffice",
"thunderbird",
"gnome-shell", "plasmashell", "kwin_wayland", "kwin_x11",
"Xorg", "Xwayland",
})
Пороговые алерты бэкенда (CPU > 80%, RAM > 3 ГБ, TCP > 100) работают для доверенных процессов.
Обучающие данные
Режим 1 — UNSW-NB15 (если data/UNSW_NB15_training-set.csv присутствует):
df = pd.read_csv(DATASET_PATH)
numeric = df.select_dtypes(include=[np.number]).dropna()
X = numeric.iloc[:, :4].values.astype(np.float32)
Из датасета берутся первые 4 числовые колонки как приближение к вектору признаков.
Режим 2 — Синтетические данные (10 000 образцов, n=10_000):
| Признак | Сегмент | Доля | Распределение |
|---|---|---|---|
| CPU | Фоновые | 75% | Exponential(scale=0.3) |
| CPU | Лёгкие | 20% | Uniform[0.5, 15] |
| CPU | Тяжёлые | 5% | Uniform[15, 60] |
| RAM | Мелкие | 50% | Uniform[0.5, 50] МБ |
| RAM | Средние | 30% | Uniform[50, 300] МБ |
| RAM | Крупные | 12% | Uniform[300, 800] МБ |
| RAM | Десктоп | 8% | Uniform[800, 2000] МБ |
| Файлы | Немного | 70% | randint[0, 20) |
| Файлы | Много | 30% | randint[20, 80) |
| TCP | Мало | 70% | randint[0, 2) |
| TCP | Больше | 30% | randint[2, 20) |
Сохранение и загрузка модели
# Сохранение (joblib)
joblib.dump({
"iforest": self.iforest,
"lof": self.lof,
"ocsvm": self.ocsvm,
"scaler": scaler,
}, MODEL_PATH) # data/model.pkl
# Загрузка при старте (если pkl существует)
bundle = joblib.load(MODEL_PATH)
# legacy-проверка: если bundle не dict — переобучение
Предварительное обучение (standalone)
cd agent
python train.py
# Done. Model saved to data/model.pkl (257000 training samples)
4. Серверная часть — API
Стек
| Компонент | Версия | Роль |
|---|---|---|
| FastAPI | ≥0.111 | ASGI-фреймворк, REST + WebSocket |
| aiosqlite | ≥0.20 | Асинхронный драйвер SQLite |
| uvicorn | ≥0.29 | ASGI-сервер |
| Pydantic v2 | встроен в FastAPI | Валидация входных данных |
OpenAPI документация: http://localhost:8000/docs
REST-эндпоинты (полный список)
| Метод | Эндпоинт | Параметры | Описание |
|---|---|---|---|
GET |
/api/stats |
— | Агрегированная статистика для панели Overview |
GET |
/api/metrics |
limit=200 |
Последние метрики процессов |
GET |
/api/alerts |
limit=50 |
Последние алерты (с дедупликацией) |
GET |
/api/file-events |
limit=100 |
Последние события файловой системы |
GET |
/api/system-metrics |
limit=60 |
Системные CPU/RAM для графика (хронологически) |
GET |
/api/active-anomalies |
— | Текущие аномальные процессы (last snapshot per PID) |
POST |
/api/metrics |
тело: MetricsBatch |
Приём батча от агента |
GET /api/stats — пример ответа
{
"total_processes": 142,
"anomalies_today": 3,
"alerts_today": 1,
"cpu_avg": 18.4,
"ram_avg": 52.1
}
cpu_avg и ram_avg — средние по таблице system_metrics за сегодня.
GET /api/system-metrics — пример ответа
[
{
"id": 1001,
"timestamp": "2025-05-28T14:29:55+00:00",
"cpu_percent": 15.2,
"ram_percent": 48.7,
"net_connections": 42
},
...
]
Возвращается в хронологическом порядке (ORDER BY id DESC → reversed) для прямой подачи в Recharts.
GET /api/active-anomalies — запрос к БД
SELECT m.*
FROM metrics m
INNER JOIN (
SELECT pid, MAX(id) AS max_id FROM metrics GROUP BY pid
) latest ON m.id = latest.max_id
WHERE m.is_anomaly = 1
ORDER BY m.cpu_percent DESC
LIMIT 20
Возвращает последний снимок каждого PID, у которого is_anomaly=1.
POST /api/metrics — логика обработки
- Bulk INSERT всех процессов в
metrics - INSERT одной строки системных метрик в
system_metrics - Bulk INSERT событий ФС в
file_events - Пороговая проверка (независимо от ML):
cpu_percent > 80ИЛИmemory_mb > 3000ИЛИconnections > 100- Такие процессы помечаются
is_anomaly=Trueдаже если ML не сработал
- Дедупликация алертов (30-секундное окно):
- Если алерт с этим PID+именем был менее 30 секунд назад —
UPDATE alerts SET count=count+1 - Иначе —
INSERT alerts+WS broadcast
- Если алерт с этим PID+именем был менее 30 секунд назад —
Severity и Reason
def _severity(proc: ProcessMetric) -> str:
if proc.cpu_percent > 80 or proc.memory_mb > 2000:
return "high"
if proc.cpu_percent > 50 or proc.memory_mb > 1000:
return "medium"
return "low"
def _reason(proc: ProcessMetric) -> str:
parts = []
if proc.cpu_percent > 80: parts.append(f"CPU {proc.cpu_percent:.1f}%")
if proc.memory_mb > 2000: parts.append(f"ОЗУ {proc.memory_mb:.0f} МБ")
if proc.connections > 50: parts.append(f"{proc.connections} TCP-соединений")
if proc.open_files > 200: parts.append(f"{proc.open_files} открытых файлов")
detail = ", ".join(parts) if parts else "статистический выброс (ансамбль ML: IF + LOF + OC-SVM)"
return f"Аномальное поведение: {detail}"
Если ни одного порогового признака нет — причина описывается как «статистический выброс ансамбля».
WebSocket
GET /ws → WebSocket upgrade
Сервер хранит список подключённых клиентов в _WsManager. При обнаружении новой аномалии (прошедшей дедупликацию) рассылается:
{
"type": "alert",
"pid": 4567,
"process_name": "python3",
"reason": "Аномальное поведение: CPU 95.2%",
"severity": "high",
"timestamp": "2025-05-28T14:30:05.123456+00:00"
}
Дедуплицированные алерты (инкремент count) не рассылаются по WS — клиент получает уведомление только при первом обнаружении.
Клиенты, разорвавшие соединение, автоматически удаляются из списка при следующей широковещательной рассылке.
Pydantic-схемы
class ProcessMetric(BaseModel):
pid: int
name: str
cpu_percent: float
memory_mb: float
open_files: int
connections: int
is_anomaly: bool = False
class SystemMetric(BaseModel):
cpu_percent: float
ram_percent: float
network_connections: int
class FileEvent(BaseModel):
path: str
event_type: str
timestamp: str
class MetricsBatch(BaseModel):
processes: list[ProcessMetric]
system: SystemMetric
file_events: list[FileEvent] = []
timestamp: str
5. База данных
SQLite, файл: data/edr.db. Четыре таблицы.
metrics
CREATE TABLE metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL, -- ISO 8601 UTC
pid INTEGER,
name TEXT,
cpu_percent REAL, -- нормализовано на _CPU_COUNT
memory_mb REAL, -- RSS в МБ
open_files INTEGER,
connections INTEGER,
is_anomaly INTEGER DEFAULT 0 -- 0=норма, 1=аномалия (ML или порог)
);
alerts
CREATE TABLE alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL, -- время первого обнаружения
pid INTEGER,
process_name TEXT,
reason TEXT, -- человекочитаемое описание
severity TEXT, -- low | medium | high
last_seen TEXT, -- время последнего повтора (для дедупликации)
count INTEGER DEFAULT 1 -- сколько раз алерт повторился
);
Дедупликация: при поступлении нового батча бэкенд ищет алерт с тем же PID и именем, у которого last_seen >= NOW() - 30s. Если найден — инкрементирует count и обновляет last_seen. Иначе создаёт новую запись.
file_events
CREATE TABLE file_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
path TEXT,
event_type TEXT -- create | modify | delete
);
system_metrics
CREATE TABLE system_metrics (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL,
cpu_percent REAL, -- система в целом (psutil.cpu_percent)
ram_percent REAL, -- psutil.virtual_memory().percent
net_connections INTEGER -- psutil.net_connections() count
);
Одна строка на батч (каждые 5 секунд). Используется для графика нагрузки в реальном времени и для расчёта cpu_avg/ram_avg в /api/stats.
6. Веб-интерфейс
Стек
| Технология | Версия | Назначение |
|---|---|---|
| React | 18.3 | Декларативный UI-фреймворк |
| TypeScript | 5.4 | Статическая типизация |
| Vite | 5.3 | Сборщик, HMR |
| Tailwind CSS | 3.4 | Utility-first стили |
| Radix UI / shadcn/ui | 1.x | Headless-компоненты (доступность) |
| Recharts | 2.12 | Графики (обёртка над D3.js) |
| TanStack Query | 5.x | Серверный стейт, кэш, polling |
| Lucide React | последняя | Иконки |
Структура компонентов
frontend/src/
├── App.tsx — корневой компонент, Tab-навигация
├── components/
│ ├── Overview.tsx — вкладка «Обзор»
│ ├── Processes.tsx — вкладка «Процессы»
│ ├── Alerts.tsx — вкладка «Алерты» + WS
│ ├── FileEvents.tsx — вкладка «События ФС»
│ ├── SystemChart.tsx — Recharts LineChart (CPU + RAM)
│ └── ui/ — shadcn/ui компоненты
├── hooks/
│ └── use-toast.ts — Toast-уведомления
└── lib/
└── api.ts — типы и fetcher-функции
Вкладка Overview (Обзор)
Компонент: components/Overview.tsx
Карточки-счётчики (StatCard × 5)
| Заголовок | Источник | Цвет акцента |
|---|---|---|
| Всего процессов | stats.total_processes |
#00d4ff (cyan) |
| Аномалий сегодня | stats.anomalies_today |
#ff6b35 (orange) |
| Алертов сегодня | stats.alerts_today |
#ff3366 (red) |
| Средний CPU | stats.cpu_avg % |
#00ff9d (green) |
| Средний RAM | stats.ram_avg % |
#a78bfa (violet) |
Панель «Активные угрозы» (ActiveAnomaliesList)
Источник данных: GET /api/active-anomalies (polling 5 сек)
Отображает список процессов с is_anomaly=1 в текущем снимке. Для каждого показывает:
- Имя процесса + PID
- CPU%, RAM МБ, TCP-соединения (цветные метки)
- Пульсирующая красная точка-индикатор
При отсутствии аномалий показывает зелёный чекмарк.
График системной нагрузки (SystemChart)
Источник: GET /api/system-metrics?limit=60 (polling 5 сек)
Recharts LineChart с двумя линиями — CPU% и RAM%. Ось X — время (HH:MM:SS). Подписи и тултипы в стиле JetBrains Mono.
Располагается на 2/3 ширины рядом с «Активными угрозами».
Диаграмма серьёзности (SeverityDonut)
Источник: GET /api/alerts?limit=200 (polling 10 сек)
Recharts PieChart (кольцо, innerRadius=48, outerRadius=72). Три сегмента:
| Сегмент | Цвет |
|---|---|
| Высокий | #ff3366 |
| Средний | #ff6b35 |
| Низкий | #00d4ff |
Топ процессов по алертам (TopProcessesBar)
Источник: те же данные алертов, что и SeverityDonut.
Recharts BarChart (горизонтальный, layout="vertical"). Агрегирует count алертов по имени процесса, показывает топ-6 по убыванию. Цвет баров: #ff6b35.
Вкладка Processes (Процессы)
Компонент: components/Processes.tsx
Источник: GET /api/metrics?limit=200 (polling 5 сек)
Таблица «последний снимок» для каждого уникального PID (MAX(id) per PID), отсортированная по убыванию CPU%.
| Колонка | Значение |
|---|---|
| PID | числовой идентификатор |
| Имя | имя исполняемого файла |
| CPU% | нормализованная нагрузка |
| RAM МБ | RSS-память |
| TCP | число соединений |
| Файлы | число дескрипторов |
| Статус | зелёный «Normal» / красный «Anomaly» |
Строки с аномалиями: красная левая рамка (border-l-2 border-red-500), красный фон.
Вкладка Alerts (Алерты)
Компонент: components/Alerts.tsx
Источник: GET /api/alerts?limit=50 (polling 5 сек) + WebSocket /ws
Таблица алертов. Колонки: Время, PID, Процесс, Описание, Серьёзность, Счётчик.
Индикатор WS-подключения (зелёная/красная точка).
При получении WS-сообщения:
- Принудительная инвалидация кэша
alertsиstatsчерезqueryClient.invalidateQueries - Показ Toast-уведомления в правом нижнем углу
Severity-бейджи:
| Уровень | Цвет |
|---|---|
high |
красный |
medium |
оранжевый |
low |
синий |
Вкладка File Events (События ФС)
Компонент: components/FileEvents.tsx
Источник: GET /api/file-events?limit=100 (polling 5 сек)
Таблица: Время, Путь, Тип события. Цветные бейджи типов:
| Тип | Цвет |
|---|---|
create |
зелёный |
modify |
жёлтый |
delete |
красный |
Дизайн-система
Фон: #0a0a0f (почти чёрный)
Поверхность: #111118
Акцент зелёный: #00ff9d (cyber green)
Акцент синий: #00d4ff (cyan)
Акцент красный: #ff3366
Шрифт: JetBrains Mono (monospace)
Стиль: SOC-dashboard, тёмный, декоративная сетка
TypeScript-типы (frontend/src/lib/api.ts)
interface ProcessMetric {
id: number
timestamp: string
pid: number
name: string
cpu_percent: number
memory_mb: number
open_files: number
connections: number
is_anomaly: number // 0 | 1 (SQLite INTEGER, не boolean)
}
interface Alert {
id: number
timestamp: string
last_seen: string | null
count: number
pid: number
process_name: string
reason: string
severity: 'low' | 'medium' | 'high'
}
interface SystemMetricRow {
id: number
timestamp: string
cpu_percent: number
ram_percent: number
net_connections: number
}
Polling-интервалы
| Данные | Интервал | Эндпоинт |
|---|---|---|
| Stats | 5 сек | /api/stats |
| System metrics | 5 сек | /api/system-metrics |
| Active anomalies | 5 сек | /api/active-anomalies |
| Processes | 5 сек | /api/metrics |
| Alerts (polling) | 5 сек | /api/alerts |
| Alerts (push) | мгновенно | WebSocket /ws |
| File events | 5 сек | /api/file-events |
7. Развёртывание (Docker)
docker-compose.yml
services:
backend:
build: ./backend
volumes:
- ./data:/app/data # SQLite + model.pkl
ports:
- "8000:8000"
environment:
- DB_PATH=/app/data/edr.db
healthcheck:
test: python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/stats')"
interval: 10s
timeout: 5s
retries: 10
agent:
build: ./agent
volumes:
- ./data:/app/data # общий том с backend
environment:
- BACKEND_URL=http://backend:8000
- COLLECT_INTERVAL=5
- MODEL_PATH=/app/data/model.pkl
- DATASET_PATH=/app/data/UNSW_NB15_training-set.csv
depends_on:
backend:
condition: service_healthy
pid: host # видит PID-пространство хоста
privileged: true # требуется для psutil.net_connections()
frontend:
build: ./frontend # multi-stage: node build → nginx
ports:
- "3000:3000"
depends_on:
- backend
Ключевые особенности агентского контейнера:
pid: host— агент видит реальные процессы хоста (без этого будут только контейнерные PID)privileged: true—psutil.net_connections()требует root для чтения/proc/net/tcp- Зависимость от
service_healthyбэкенда — агент стартует только после того, как/api/statsвозвращает 200
Запуск
# Стандартный запуск
docker compose up --build
# С датасетом UNSW-NB15
cp /path/to/UNSW_NB15_training-set.csv data/
docker compose up --build
# Без Docker (разработка)
cd backend && uvicorn main:app --reload --port 8000
cd agent && python main.py
cd frontend && pnpm dev
8. Конфигурация и переменные окружения
Агент (agent/main.py)
| Переменная | По умолчанию | Описание |
|---|---|---|
BACKEND_URL |
http://localhost:8000 |
Адрес бэкенда |
COLLECT_INTERVAL |
5 |
Интервал сбора метрик (секунды) |
Модель (agent/model.py)
| Переменная | По умолчанию | Описание |
|---|---|---|
MODEL_PATH |
data/model.pkl |
Путь для сохранения/загрузки ансамбля |
DATASET_PATH |
data/UNSW_NB15_training-set.csv |
Путь к датасету для обучения |
Бэкенд (backend/main.py)
| Переменная | По умолчанию | Описание |
|---|---|---|
DB_PATH |
../data/edr.db |
Путь к SQLite-базе данных |
9. Расхождения с дипломным документом
В файле diploma_practical_section.md обнаружены следующие расхождения с реальной кодовой базой:
9.1 Таблица Overview устарела
В документе:
«Пять карточек-счётчиков» + «График реального времени»
В коде (после коммита 4253552):
ActiveAnomaliesList— живой список текущих аномальных процессовSeverityDonut— кольцевая диаграмма распределения алертов по серьёзностиTopProcessesBar— горизонтальный bar-chart топ-6 процессов по числу алертов
Все три компонента опрашивают /api/active-anomalies и /api/alerts.
9.2 Отсутствует таблица system_metrics
Документ описывает 3 таблицы. Реально их 4: metrics, alerts, file_events, system_metrics.
9.3 Отсутствуют колонки alerts.last_seen и alerts.count
Документ не упоминает дедупликацию алертов. Реально таблица alerts содержит last_seen TEXT и count INTEGER DEFAULT 1. Дедупликационное окно — 30 секунд.
9.4 Отсутствуют два API-эндпоинта
Документ описывает 5 эндпоинтов. Реально их 7:
GET /api/system-metrics— данные для графика нагрузкиGET /api/active-anomalies— текущие аномальные процессы
9.5 Не описана нормализация CPU
Агент делит cpu_percent на psutil.cpu_count(logical=True). Без этого на многоядерных хостах значения CPU > 100% приводят к ложным алертам.
9.6 WebSocket срабатывает только на новые алерты
Документ: «при поступлении батча с аномальными процессами бэкенд транслирует каждому клиенту JSON».
Реально: WS-сообщение отправляется только при INSERT alerts (первое обнаружение). Повторные срабатывания в течение 30 секунд дают только UPDATE count без WS-рассылки.
9.7 Порог для включения RAM в reason
Документ: RAM > 2 ГБ → severity=high.
Реально _severity() проверяет memory_mb > 2000 для "high", но _reason() добавляет строку ОЗУ тоже при memory_mb > 2000. Пороговый алерт (без ML) срабатывает при memory_mb > 3000. Документ эту разницу не разграничивает.
Справочник отражает состояние кодовой базы на коммит 4253552 (ветка master).