Гайд разработчика модулей Umbryx¶
Этот гайд описывает создание модулей расширений для платформы Umbryx.
Быстрый старт¶
Создайте каталог в /modules/ с manifest.yaml:
modules/
my-module/
manifest.yaml
backend/
routes.py # Опционально: кастомные API-маршруты
README.md
Модуль обнаруживается автоматически при старте бэкенда. Регистрационный код не нужен.
Справочник по манифесту¶
manifest.yaml — единственный источник истины о возможностях модуля.
Обязательные поля¶
id: my-module # lowercase, дефисы, [a-z0-9-]{2,63}
name: My Module # отображаемое имя
version: 1.0.0 # семвер
Метаданные¶
description: >
Что делает этот модуль.
author: Your Name
license: MIT
icon: server # имя иконки из компонента Icon
Зависимости¶
depends_on:
- topology # id других обязательных модулей
conflicts_with:
- old-module # несовместимые модули
Права¶
Объявите, к каким данным модулю нужен доступ:
permissions:
- read:hosts # чтение хостов
- write:findings # создание/обновление находок
- exec:jobs # исполнение задач
- write:object # запись в универсальную объектную модель
Валидные режимы: read, write, exec
Валидные scope: hosts, creds, findings, scopes, loot, jobs, notes, networks, tasks, object, settings
Типы объектов¶
Типизированные объекты в универсальной таблице core_objects:
objects:
- type: vulnerability # уникальный идентификатор типа
name: Vulnerability # отображаемое имя
icon: bug
list_columns: [title, severity, status] # колонки авто-списка
fields:
- name: title
type: string
label: Title
required: true
- name: severity
type: enum
label: Severity
options: [low, medium, high, critical]
default: medium
- name: cvss_score
type: float
label: CVSS Score
- name: description
type: text
label: Description
- name: is_exploitable
type: boolean
label: Exploitable
default: false
- name: remediation
type: text
label: Remediation
- name: related_finding
type: reference
label: Related Finding
reference_type: finding # ссылка на другой тип объектов
- name: metadata
type: json
label: Metadata
Типы полей: string, text, integer, float, boolean, enum, datetime, json, reference
Объекты доступны через универсальный CRUD API:
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /api/modules/objects?type=vulnerability |
Список объектов |
| POST | /api/modules/objects |
Создать объект |
| GET | /api/modules/objects/{id} |
Получить объект |
| PATCH | /api/modules/objects/{id} |
Обновить объект |
| DELETE | /api/modules/objects/{id} |
Удалить объект |
Точки расширения¶
Регистрация UI-расширений:
extensions:
sidebar.nav:
- id: my-view
label: My View
icon: bolt
view: myView
project.tab:
- id: project-items
label: Items
icon: list
dashboard.widget:
- id: stats-widget
label: Statistics
component: StatsWidget
host.tab:
- id: host-details
label: My Tab
settings.section:
- id: my-settings
label: Module Settings
report.section:
- id: my-report
label: Custom Section
actions.host:
- id: scan-host
label: Scan
icon: target
importer:
- id: csv-import
label: Import CSV
accept: .csv
search.provider:
- id: search-items
label: Search Items
Известные точки расширения: sidebar.nav, project.tab, host.tab, network.tab, dashboard.widget, settings.section, report.section, actions.host, actions.finding, actions.cred, actions.object, importer, search.provider
Размещение в навигации (обязательно для sidebar.nav)¶
Сайдбар использует двухуровневую иерархию семантических групп.
Группы определены централизованно в
frontend/src/constants.js::TAB_GROUPS — сверяйтесь с этим файлом как
авторитетным списком:
| Группа | Назначение | Текущие секции |
|---|---|---|
| (Overview) | Выбор проекта и дашборд | Projects, Overview |
| Recon | Разведка и сканирование | Hosts, Scope, ROE, Watch, Network, Attack Path, Attack Graph |
| Exploit | Evidence и доступ | Creds, Findings, Loot, Notes |
| Automate | Автоматизация и исполнение | Jobs, Playbooks, Automations, Cheatsheet |
| Infra | Управление инфраструктурой | Platform Ops, Workstations, Connections |
| Output | Отчётность и трекинг | Access, Objectives, Checklist, KB, Timeline, Report, Purple |
Правила:
-
Встраивание лучше новых пунктов навигации. Прежде чем добавить
sidebar.nav, подумайте, нельзя ли выразить функциональность какhost.tab,dashboard.widget,actions.hostили inline-кнопку в существующей секции. Кнопка на карточке нужного объекта почти всегда лучше новой страницы верхнего уровня. -
Соответствуйте ментальной модели пользователя. Выбирайте группу, где пользователь будет искать функцию; пункт без подходящей зарегистрированной группы не отрисуется.
-
Новые группы требуют обоснования. Новая группа верхнего уровня — существенное UX-изменение. Допустимо, когда:
- модуль вводит принципиально новую фазу workflow (не покрытую существующими группами)
- есть 3+ связанные секции, не помещающиеся ни в одну группу
-
лид модуля + UX-ревьюер одобрили
-
Один пункт на модуль. Модуль даёт максимум один
sidebar.nav; остальное — через другие точки (host.tab,dashboard.widgetи т.д.). -
Предпочитайте контекстную интеграцию. Для AI/ML-функций: добавляйте кнопки «Ask AI» через
actions.*на карточках объектов, а не отдельную AI-секцию. Отдельная секция оправдана только для полноценного workspace (интерфейс чата, оркестрация агентов и т.п.).
Визуальная согласованность¶
Весь UI модулей обязан использовать общий глоссарий цветов и статусов
из src/constants.js:
| Импорт | Назначение |
|---|---|
NODE_STATUS |
Цвета статусов хостов (unknown/alive/scanned/access/pwned/owned) |
SEVERITY |
Цвета severity находок (critical/high/medium/low/info) |
FINDING_STATUS |
Цвета жизненного цикла находок |
OBJECTIVE_STATUS |
Цвета статусов objectives |
ACTIVITY_TYPES / ACTIVITY_STATUS |
Из utils/hosts.js — цвета журнала активностей |
Никогда не хардкодьте hex-значения. Импортируйте из constants.js
или utils/hosts.js. Нужен новый уровень статуса/severity — сначала
добавьте в канонический источник, потом импортируйте.
Имена иконок должны совпадать с ключами в src/components/Icon.jsx.
Указывайте icon: в манифесте для пунктов навигации и типов объектов.
Процесс ревью навигации¶
Структуру сайдбара (TAB_GROUPS в src/constants.js) ревьюят:
- после каждых 3 новых модулей
- или раз в квартал (что раньше)
Чек-лист ревью: 1. В какой-то группе больше 5 пунктов? Рассмотрите разделение. 2. Есть пункты, которые пользователи не находят? Смотрите тикеты/отзывы. 3. Есть группы из 1 пункта? Рассмотрите слияние со смежной группой. 4. Появилась новая фаза workflow, не попадающая ни в одну группу?
Настройки¶
Объявление конфигурируемых настроек:
settings:
- key: max_items
label: Maximum Items
type: integer
default: 100
description: Maximum number of items per project
- key: api_key
label: API Key
type: string
placeholder: "Enter your API key"
- key: enabled_features
label: Features
type: enum
options: [basic, advanced, enterprise]
default: basic
Роуты бэкенда¶
Кастомные API-эндпоинты:
Создайте backend/routes.py:
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.core_object import CoreObject
router = APIRouter(prefix="/api/ext/my-module", tags=["my-module"])
@router.get("/custom-endpoint")
def my_endpoint(db: Session = Depends(get_db)):
return {"status": "ok"}
# Загрузчик ищет этот атрибут
FASTAPI_ROUTER = router
Роутер монтируется в приложение FastAPI автоматически при старте.
Универсальная объектная модель¶
Таблица core_objects даёт schema-driven хранилище без кастомных
таблиц:
from app.models.core_object import CoreObject
from app.core.modules import obj_type_registry
# Валидация данных по схеме
errors = obj_type_registry.validate_data("vulnerability", data)
# Создание объекта
obj = CoreObject(
id="vuln_001",
type="vulnerability",
module_id="my-module",
name=data.get("title"),
data=data,
)
db.add(obj)
db.commit()
Связи¶
Объекты могут иметь типизированные связи:
# Добавить связь
obj.relations["affects"] = [{"id": "host_123", "weight": "high"}]
db.commit()
# Запрос через API
GET /api/modules/objects/{id}/relations/affects
Интеграция фронта¶
Компонент ExtensionPoint¶
Рендер расширений, зарегистрированных модулями:
import ExtensionPoint from '../modules/ExtensionPoint.jsx';
// Все виджеты дашборда из модулей
<ExtensionPoint point="dashboard.widget" projectId={pid} />
ExtensionPoint находит зарегистрированные записи точки расширения и
рендерит компонент каждого модуля, обёрнутый в error boundary.
Error boundaries¶
Компоненты модулей автоматически оборачиваются в error boundary
компонентом ExtensionPoint. Чтобы изолировать вручную:
import ModuleErrorBoundary from '../modules/ModuleErrorBoundary.jsx';
<ModuleErrorBoundary moduleName="my-module">
<MyComponent />
</ModuleErrorBoundary>
Справочник API¶
Управление модулями¶
| Метод | Эндпоинт | Auth | Описание |
|---|---|---|---|
| GET | /api/modules |
любой | Список всех модулей |
| POST | /api/modules/{id}/enable |
admin | Включить модуль |
| POST | /api/modules/{id}/disable |
admin | Выключить модуль |
| GET | /api/modules/{id}/health |
любой | Health-check |
Типы объектов¶
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /api/modules/object-types |
Все схемы типов |
| GET | /api/modules/object-types/{type} |
Схема конкретного типа |
Расширения¶
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /api/modules/extensions |
Все записи расширений |
| GET | /api/modules/extensions/{point} |
Записи конкретной точки |
CRUD объектов¶
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /api/modules/objects |
Список (фильтры type, project_id, status) |
| POST | /api/modules/objects |
Создать |
| GET | /api/modules/objects/{id} |
Получить |
| PATCH | /api/modules/objects/{id} |
Обновить |
| DELETE | /api/modules/objects/{id} |
Удалить |
| POST | /api/modules/objects/{id}/relations |
Добавить связь |
| GET | /api/modules/objects/{id}/relations/{type} |
Получить связи |
Граница core и расширений¶
Core (не извлекается)¶
- Аутентификация и авторизация (
auth,core/deps,core/security) - Управление пользователями и организациями (
users,organizations,projects) - БД и сессии (
database.py,alembic) - Система событий (
core/events,ws) - Настройки и конфигурация (
core/config,core/enums) - Логирование (
core/logging_setup) - Обработка ошибок (
core/errors) - Сам фреймворк модулей (
core/modules)
Кандидаты на извлечение¶
- Provisioning инфраструктуры (
core/infra/*) - AI-ассистент (
routers/ai) - Граф атак и топология (
routers/attack_graph*,routers/topology) - Управление DNS (
routers/dns) - Импортёры сканов (
routers/import_scanners) - Интеграция C2 (
routers/c2) - Плейбуки (
routers/playbooks) - Отчёты (
routers/report) - Маркетплейс (
routers/marketplace)
Флоу обнаружения модулей¶
- Бэкенд сканирует
/modules/*/manifest.yamlпри старте - Каждый манифест парсится и валидируется
- Проверяются зависимости и конфликты
- Типы объектов регистрируются в
obj_type_registry - Точки расширения регистрируются в
ext_registry - Импортируются entry points бэкенда (монтируется
FASTAPI_ROUTER) - Фронт при аутентификации запрашивает
/api/modules/extensions - Компоненты
ExtensionPointрендерят зарегистрированные расширения
Жизненный цикл¶
Модули включаются/выключаются на лету через админ-API:
# Выключить
curl -X POST -b "rt_auth=TOKEN" http://localhost:3000/api/modules/my-module/disable
# Включить
curl -X POST -b "rt_auth=TOKEN" http://localhost:3000/api/modules/my-module/enable
# Health-check
curl -b "rt_auth=TOKEN" http://localhost:3000/api/modules/my-module/health
Выключение разрегистрирует все типы объектов и записи расширений
модуля. Существующие данные в core_objects сохраняются.
Реальные примеры миграции¶
Паттерн 1: модуль только с настройками (AI Assistant)¶
Для возможностей, у которых код уже есть и нужна лишь формальная регистрация:
# modules/ai-assistant/manifest.yaml
id: ai-assistant
name: AI Assistant
version: 1.0.0
description: AI-powered security assistant with multi-provider LLM support.
permissions:
- read:hosts
- read:findings
extensions:
settings.section:
- id: ai-providers
label: AI Providers
component: AISection
settings:
- key: provider
label: Default Provider
type: enum
options: [openai, anthropic, ollama]
default: anthropic
Что произошло: код бэкенда (routers/ai.py, core/ai_manager.py)
уже был подключён в main.py. Манифест декларирует только метаданные,
права, настройки и точки расширения. Код не двигался. Модуль виден в
System → Modules, его настройки обнаруживаемы.
Паттерн 2: декларация типа объекта (DNS Management)¶
Для возможностей со структурированными данными под универсальную модель:
# modules/dns-management/manifest.yaml
objects:
- type: dns_record
name: DNS Record
list_columns: [hostname, record_type, value, provider]
fields:
- name: hostname
type: string
required: true
- name: record_type
type: enum
options: [A, AAAA, CNAME, MX, TXT, NS, SRV, CAA, PTR]
default: A
- name: value
type: string
required: true
- name: provider
type: enum
options: [manual, cloudflare]
default: manual
Что произошло: у DNS-записей была кастомная таблица
(dns_records). Манифест декларирует тип dns_record. Новые записи
создаются через универсальный CRUD API (POST /api/modules/objects) и
рендерятся через GenericObjectView. Старая таблица остаётся для
совместимости на время миграции.
Паттерн 3: подключение точки расширения (ROE)¶
Для возможностей, дающих проектный таб:
# modules/roe/manifest.yaml
id: roe
name: Rules of Engagement
version: 1.0.0
extensions:
project.tab:
- id: roe
label: ROE
icon: scroll
Что произошло: у ROE уже были роутер бэкенда и вью фронта
(RoeView.jsx). Манифест декларирует точку project.tab, чтобы
система модулей знала: модуль даёт таб уровня проекта. Подключение
таба в AppTabRouter.jsx на переходный период не менялось.
Чек-лист миграции¶
При переводе существующей возможности в модуль:
- [ ] Создать
modules/<name>/manifest.yamlс метаданными, правами, объектами, расширениями, настройками - [ ] Проверить появление модуля в панели System → Modules
- [ ] Проверить типы объектов в
GET /api/modules/object-types - [ ] Проверить точки расширения в
GET /api/modules/extensions - [ ] Прогнать валидатор:
python3 -c "from app.core.modules import *; from app.core.modules.manifest import Manifest; m = Manifest.from_file(Path('modules/<name>/manifest.yaml')); from app.core.modules.validator import validate_module; print(validate_module(m))" - [ ] При кастомных API-маршрутах создать
backend/routes.pyсFASTAPI_ROUTER - [ ] Протестировать жизненный цикл:
POST /api/modules/<name>/disable, затем/enable - [ ] Дополнить этот гайд использованным паттерном миграции