
Engagement Manager — это веб-приложение для отслеживания проектов в области наступательной безопасности. Оно отличается современным интерфейсом, созданным с использованием Next.js, Prisma и PostgreSQL.
Engagement Manager — это веб-приложение для отслеживания проектов в области наступательной безопасности. Оно имеет современный интерфейс, построенный на Next.js, Prisma и PostgreSQL. Приложение включает календарь, проекты, клиентов, контакты, находки и операторов.

| Семейство сканера/экспорта | Принимаемый экспорт |
|---|---|
| Burp Suite | Issues XML, включая инертную внутреннюю схему DTD |
| Nessus / Tenable | Nessus v2 XML (.nessus) |
| Nmap | XML; открытые порты и их вывод скриптов становятся информационными наблюдениями, а не выведенными уязвимостями |
| OpenVAS / Greenbone | Собственный XML-отчёт или GMP get_reports_response |
| OWASP ZAP | Традиционный JSON-отчёт с сайтами и оповещениями |
| Nuclei | JSON Lines (-jsonl) |
| Qualys | XML результата сканирования (структура SCAN/IP), а не отдельный формат API обнаружения хостов |
| Semgrep / CodeQL и другие производители SARIF | SARIF JSON runs, rules и results |
Экспорты ограничены 2 МБ и 500 находками на импорт, с ограничениями частоты предпросмотра и подтверждения на пользователя. Приложение принимает не более 10 000 находок в общей сложности и 500 для одного проекта, включая ручное создание, шаблоны и импорт сканеров. Глобальный список Findings загружает 100 строк на страницу, а запросы находок проекта/отчёта ограничены тем же лимитом на проект. Неизвестные макеты явно завершаются ошибкой, а не молча рассматриваются как успешный импорт. Уровни серьёзности сканера являются предложениями: проверьте их контекст перед одобрением. Указанные URL, HTML и встроенные удалённые изображения не загружаются и не выполняются.
Отчёты допускают 1–100 находок, до 100 изображений-доказательств (по 5 МБ каждое, 20 МБ общего ввода), 500 страниц и 25 МБ вывода. Выпуск ограничен 50 версиями на проект и 1 ГБ выпущенных PDF во всём приложении. Предпросмотр и выпуск имеют ограничения частоты на пользователя, и одновременно допускается только один рендеринг PDF на процесс приложения. Находки сохраняют не более 1000 ревизий и 500 комментариев; достижение лимита завершается ошибкой без перезаписи истории. Шрифты DejaVu и их лицензия на распространение включены в assets/fonts; развёртывания должны сохранять эти ресурсы (трассировка вывода Next включает их).
Next.js Server Actions используют единый лимит размера тела 25mb (заданный в next.config.ts) для загрузки доказательств. Вход использует выделенный URL-encoded маршрут того же источника с потоковым лимитом 4 КБ до аутентификации или работы с базой данных.
Это сохраняет существующее общее аутентифицированное рабочее пространство, а не новую модель мультитенантности для каждого клиента. Все новые страницы, действия и загрузки PDF проверяют текущую сессию, поддерживаемую базой данных. Черновики ограничены их владельцем; разрешения на проверку, утверждение шаблонов и выпуск обеспечиваются на стороне сервера. Конфиденциальные ответы PDF являются private/no-store. Финальные PDF содержат только явный список разрешённых полей отчёта, никогда не приватные черновики, комментарии рецензентов или несвязанные проекты.
Реализация использует чек-лист OWASP Top 10:2025: проверки доступа (A01), приватные ответы и существующие элементы управления CSP/CSRF (A02), закреплённые зависимости и CI (A03), существующие защиты сессий/секретов плюс проверки целостности отчётов (A04/A08), инертный Markdown/XML и параметризованный доступ к базе данных (A05), ограниченная обработка и независимая проверка (A06), проверки активной сессии (A07), события аудита без содержимого (A09) и транзакционные изменения с очисткой при сбое (A10). Дайджест обнаруживает случайное повреждение; это не цифровая подпись и не защита от администратора базы данных. Это не сертификация соответствия. Продакшн по-прежнему требует HTTPS, защищённого хранилища базы данных/резервных копий и операционного мониторинга вывода аудита.
Перед развёртыванием этого обновления сделайте обычную резервную копию приложения и примените аддитивные миграции 20260904221808_reporting_workflow и 20260906194500_add_revocable_sessions с помощью npm run db:migrate, затем перегенерируйте Prisma Client и пересоберите. Существующие находки начинаются как Draft в версии 1, и существующие cookies браузера должны войти снова, чтобы получить серверный идентификатор сессии. Не сбрасывайте существующую базу данных. Резервные копии включают новые таблицы и выпущенные PDF через существующий полный экспорт базы данных.
npm test npm run lint npx tsc --noEmit --noUnusedLocals --noUnusedParameters npm run build npm audit
`npm test` использует неизолированный режим тестирования Node с `tsx`, чтобы отдельные тестовые случаи TypeScript действительно выполнялись, а не просто сообщался успех подпроцесса файла. Сохраняйте явные итоговые значения утверждений видимыми в CI.
Регрессионные тесты базы данных и браузера требуют **выделенной локальной базы данных с именем `reporting_tests`** с применёнными миграциями. Они создают и удаляют свои собственные строки фикстур; никогда не направляйте эти тесты на базу данных приложения. Установите `REPORTING_TEST_DATABASE_URL` на эту тестовую базу данных, затем выполните:```bash
DATABASE_URL="$REPORTING_TEST_DATABASE_URL" npx prisma migrate deploy
npm run test:reporting
npx playwright install chromium
npm run test:browser
Браузерный набор запускает собственный loopback-сервер разработки на порту 3317 с тестовым секретом сессии; он отказывается переиспользовать существующий сервер. При необходимости задайте REPORTING_TEST_BROWSER в качестве пути к установленному исполняемому файлу Chromium. Он проверяет черновик конфиденциальности, конфликтующие правки, загрузку доказательств, независимую проверку, права доступа к PDF и их неизменяемость, создание шаблона без JavaScript и выборочный дедуплицированный импорт. Интеграционные тесты проверяют реальные транзакционные конфликты и откат. Эти наборы не заменяют проверку удалённой локальной сети, Safari или развёртывания в продакшене.
Это приложение рассчитано на работу в Ubuntu и требует следующего:```bash sudo apt update && sudo apt install -y nodejs npm postgresql postgresql-client postgresql-contrib zip
`postgresql-client` предоставляет `pg_dump`, `pg_restore` и `psql`; `zip` создаёт архивы резервных копий. Извлечение при восстановлении обрабатывается приложением со строгой проверкой записей и размера.
Установка пакетов не всегда оставляет PostgreSQL запущенным. Запустите и включите службу перед созданием ролей или запуском приложения:```bash
sudo systemctl enable --now postgresql
sudo systemctl status postgresql --no-pager
Если позже приложение завершится с ошибкой Can't reach database server at 127.0.0.1:5432, выполните sudo systemctl start postgresql и подтвердите с помощью pg_isready -h 127.0.0.1 -p 5432.
Приложению требуется Node.js ^22.12.0 или >=24.0.0 (см. engines в package.json). Если пакет ОС старше, установите поддерживаемый выпуск из доверенного источника пакетов, подписи которого вы проверяете перед запуском setup.sh.
Создайте файл .env в корне проекта перед запуском Prisma или приложения:```bash
cat > .env << 'EOF'
DATABASE_URL="postgresql://em_admin:em_pass@localhost:5432/engagement_manager?schema=public"
JWT_SECRET="replace-with-a-long-random-secret-at-least-32-characters"
EOF
chmod 600 .env
| Переменная | Обязательна | Примечания |
|----------|----------|-------|
| `DATABASE_URL` | Да | Строка подключения к PostgreSQL. Prisma использует параметр запроса `schema=public`. Резервное копирование и восстановление используют временный файл pgpass, доступный только владельцу, чтобы пароль не попадал в аргументы подпроцесса. |
| `JWT_SECRET` | Да в production | Должен содержать не менее **32 символов**. Приложение отказывается запускаться в production без него. Его ротация делает недействительными все существующие сессии. |
| `TRUST_PROXY` | Нет | Установите в `1` (или `true`) только если приложение находится за обратным прокси, который **перезаписывает** `X-Forwarded-For` / `X-Real-IP` и `X-Forwarded-Host`. Проверки источника входа используют `X-Forwarded-Host`, когда он присутствует в этом режиме; он должен содержать один публичный хост, включая нестандартный порт при его использовании. В противном случае прокси должен сохранять публичный заголовок `Host`. Это обязательная топология production для точных ограничений входа по источнику. Если параметр не задан, заголовки игнорируются для предотвращения подмены, а вход использует более высокий общий резервный лимит на одну минуту, чтобы один клиент не мог вызвать глобальную блокировку на 15 минут. |
| `ALLOWED_DEV_ORIGINS` | Нет | **Только для разработки.** Дополнительные имена хостов, которым разрешено загружать ресурсы `/_next` (через запятую). Текущие LAN IPv4-адреса сервера разрешены автоматически. Используйте это для стабильного DNS-имени. Сборки production игнорируют этот параметр. |
Сгенерируйте надёжный секрет:```bash
openssl rand -base64 32
Убедитесь, что PostgreSQL запущен (см. Предварительные требования). Автоматический ./setup.sh запускает службу за вас; приведённые ниже шаги ручной настройки предполагают, что она уже запущена.
Выполните следующие команды, чтобы создать базу данных и пользователя PostgreSQL:```bash sudo -u postgres createuser --pwprompt em_admin sudo -u postgres psql -c "ALTER USER em_admin CREATEDB;" sudo -u postgres createdb --owner=em_admin engagement_manager sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE engagement_manager TO em_admin;"
### Продакшн
Используйте выделенного пользователя базы данных с **минимальными привилегиями** — не предоставляйте права `CREATEDB` или суперпользователя:```bash
sudo -u postgres createuser --pwprompt em_app
sudo -u postgres createdb --owner=em_app engagement_manager
Установите DATABASE_URL, чтобы использовать em_app (или выбранное вами имя пользователя). Миграции выполняются от имени этого пользователя через npm run db:migrate.
Примечание: Файлы базы данных хранятся в каталоге данных PostgreSQL (обычно
/var/lib/postgresql/<version>/main/).
Из корня репозитория выполните:```bash chmod +x setup.sh ./setup.sh
Скрипт устанавливает необходимые компоненты, запускает и включает службу PostgreSQL, запрашивает имя пользователя и пароль базы данных, записывает `.env` с правами `chmod 600`, создаёт роль и базу данных PostgreSQL, применяет миграции и заполняет учётную запись администратора по умолчанию. В production-режиме также выполняется `npm run build` и выводится только команда запуска production. Он не устанавливает Node.js из удалённого shell-скрипта; сначала установите поддерживаемую версию Node.js.
Для headless- или CI-использования:```bash
sudo install -d -m 700 -o "$USER" /secure
openssl rand -base64 24 > /secure/db-password
chmod 600 /secure/db-password
./setup.sh -y --db-user=em_admin --db-pass-file=/secure/db-password
Запустите ./setup.sh --help, чтобы увидеть все параметры.
--db-pass=... был удалён, поскольку секреты в командной строке видны другим процессам. Поместите пароль в файл, доступный только владельцу, и замените старый аргумент на --db-pass-file=/secure/db-password; приведённый выше пример автоматической настройки готов к копированию и вставке.setup.sh больше не устанавливает Node.js. Установите поддерживаемую версию Node.js (^22.12.0 или >=24.0.0) из доверенного источника пакетов перед его запуском.npm ci, поэтому package-lock.json должен присутствовать и быть синхронизирован с package.json..sql не могут быть восстановлены. Перед выводом из эксплуатации старого сервера обновите его до версии, которая может создавать структурированную резервную копию приложения, и повторно экспортируйте данные в виде .zip.Из каталога проекта одна команда устанавливает обновления пакетов, запускает PostgreSQL, если он остановлен, и запускает приложение:```bash ./run.sh
Оставьте это окно открытым. Используйте адрес Local или Network, который он выводит.
Чтобы запустить его самостоятельно: PostgreSQL должен быть запущен (`sudo systemctl start postgresql` при необходимости). Затем запустите сервер разработки:```bash
npm run dev
При запуске выводятся как URL loopback, так и LAN-адрес этой машины:```
`npm run dev` и `npm start` привязываются к `0.0.0.0`, поэтому Network URL работает в локальной сети. Рассматривайте доступ по локальной сети как лабораторный, только в доверенной сети. Режим разработки не усилен для публичного интернета.
Если вы открываете приложение по **имени хоста** (не по IP) и удалённый браузер показывает пустую белую страницу, добавьте это имя в `.env` и перезапустите:```bash
ALLOWED_DEV_ORIGINS=dev.office.example
^22.12.0 или >=24.0.0 (см. engines в package.json)Secure в production.uploads/ (скриншоты находок)Клонируйте репозиторий и установите зависимости: ```bash npm ci
Создайте .env с production-значениями (DATABASE_URL, JWT_SECRET ≥ 32 символов).
Примените миграции базы данных: ```bash npm run db:migrate
Запустите проверки перед развёртыванием: ```bash npm run audit npm run typecheck npm run build
Запустите приложение с NODE_ENV=production: ```bash
NODE_ENV=production npm run start
Для реального сервера запускайте это под менеджером процессов (systemd, PM2 и т. п.) и разместите перед ним обратный прокси для завершения TLS.
JWT_SECRET содержит не менее 32 символов и не закоммичен в gitNODE_ENV=production установлен для запущенного процессаCREATEDB или суперпользователяuploads/ находится на постоянном диске и включён в резервные копииbackups/ находится на постоянном диске, если администраторы используют Backuppg_dump, pg_restore и zip доступны, если администраторы будут использовать Backup/RestoreПосле заполнения базы данных вы можете войти, используя созданную временную учётную запись администратора:
admininitial-admin-credentials.txt, доступный только владельцу, командой npx prisma db seed / npm run db:seedПримечание: При первом входе вам будет предложено сменить этот временный пароль. Сразу после этого удалите
initial-admin-credentials.txt. Все пароли должны содержать не менее 16 символов, включая заглавную букву, строчную букву, цифру и символ.
/dashboard/users).На странице Admin панель Database показывает кнопки Backup, Restore и Reset. Панель Users перечисляет учётные записи и предоставляет кнопку New User для добавления пользователей. Панель Appearance позволяет администратору выбрать цвет выделения для всего приложения.
Backup требует ваш пароль администратора, после чего сохраняет .zip с именем em-backup-YYYY-MM-DD-HHMM.zip в backups/ в каталоге приложения (engagement-mgr/backups/). После успешного экспорта используйте Download на странице Admin. Кратковременное подписанное разрешение хранится в cookie HttpOnly и работает только для администратора, создавшего резервную копию.
em-backup-2026-06-02-1430.zip.| Путь | Содержимое |
|---|---|
engagement-manager-backup/database.dump | Полный дамп PostgreSQL в пользовательском формате (схема, таблицы, данные, перечисления, связи) из pg_dump |
engagement-manager-backup/uploads/ | Файлы скриншотов находок, на которые ссылается база данных |
.zip, созданный функцией Backup, и заменяет текущую базу данных и папку uploads/. Восстановление через браузер ограничено 8 МБ, чтобы распаковка не могла монополизировать веб-процесс. Для архива большего размера остановите приложение и выполните npm run db:restore -- /absolute/path/to/em-backup.zip от имени пользователя приложения. Офлайн-команда загружает .env из рабочего каталога и требует непустой DATABASE_URL в .env или в окружении. Она принимает обычные файлы размером до 500 МБ и пропускает каждую запись архива через её лимит расширенного размера. Восстановление базы данных выполняется в одной транзакции; количество записей архива, пути, коэффициенты сжатия и расширенные размеры проверяются перед установкой файлов. Резервное копирование, восстановление, сброс и изменения файлов скриншотов используют общую эксклюзивную блокировку обслуживания, чтобы фиксации базы данных и замены в файловой системе не могли пересекаться. Требует вашего пароля администратора для подтверждения.admin. Требует ввести RESET и повторно ввести текущий пароль подтверждающего администратора. Этот пароль становится временным паролем воссозданной учётной записи и должен быть изменён при первом входе.Старый сервер
.zip и скопируйте его на новый сервер (например, с помощью scp или rsync): ```bash
scp em-backup-2026-06-02-1430.zip user@new-server:/path/to/
Новый сервер
.env с DATABASE_URL и JWT_SECRET (см. Конфигурация окружения).npm ci.admin, используя файл initial-admin-credentials.txt, доступный только владельцу, смените временный пароль и удалите файл с учётными данными./dashboard/users), нажмите Restore (в разделе Database), выберите .zip-файл со старого сервера, введите пароль администратора и подтвердите.Примечания
uploads/.git clone (или разверните ту же ревизию) на новом сервере, чтобы приложение соответствовало схеме, ожидаемой резервной копией. Если на старом сервере использовалась более новая схема, чем в клонированном коде, согласуйте версии перед импортом.В этом разделе описаны архитектура, схема базы данных, меры безопасности и завершённые этапы разработки приложения Engagement Manager.
Modal.tsx и .modal-panel в globals.css.Дополнения для отчётности: Finding также хранит version, reviewStatus, authorId, reviewerId, templateId и importFingerprint; Screenshot хранит sortOrder. FindingTemplate содержит проверенные переиспользуемые формулировки; FindingRevision содержит неизменяемые ревизии текста; FindingDraft содержит приватные черновики для каждого пользователя с версиями конфликтов; FindingComment фиксирует обсуждения при проверке; EngagementReport содержит заголовок отчёта, executive summary и упорядоченные ID находок; IssuedReport хранит неизменяемый PDF, снимок содержимого и дайджест SHA-256 для каждой выпущенной версии. Связи пользователя как автора/рецензента используют SetNull; приватные черновики удаляются при удалении их пользователя. Записи отчётности следуют жизненному циклу родительского engagement/finding.
id, username, passwordHash, role (Admin, User), lastPasswordChange, lastLogin, sessions, createdAt, updatedAt.id, userId, expiresAt, createdAt — серверные записи делают каждую подписанную сессию входа индивидуально отзываемой при выходе.key, count, resetAt — атомарные резервирования попыток входа по источнику и подтверждения пароля. Проверка пароля также имеет ограничение на параллелизм.highlightColor (Red, Blue, Teal, Green, Purple или Amber) и updatedAt.id, codeName, clientId, chargeCode, status (Prep, Recon, Testing, Reporting, Complete), focus, type (AI, Code_Review, Firewall, Multi, Pentest, Phishing, Physical, Purple_Team, Red_Team, USB_Drop, Vishing, Web_App, Wireless), location (Internal, External), startPrep, endPrep, startRecon, endRecon, startTesting, endTesting, startReporting, , , , , , , (M:N), / (M:N с Contact), , , , .id, company (столбец БД: companyName), address, city, state, zip, phone (столбец БД: phoneNumber), website, notes, contacts, engagements, createdAt, updatedAt.id, clientId, name, title, email, phone (столбец БД: phoneNumber), notes, assignedEngagements, trustedEngagements, createdAt, updatedAt.id, engagementId (опционально), title, category, severity, background, remediation, supportingData (столбец БД: supportingLinks), screenshots, engagementContext, createdAt, updatedAt.id, engagementId, findingId, observation, affectedHosts, createdAt, updatedAt.id, findingId, filePath, description, createdAt.id, name, title, email, phoneNumber, discord, github, notes, engagements (M:N), createdAt, updatedAt.Чтобы добавить новое поле в существующую модель (например, focus в Engagement):
prisma/schema.prisma и добавьте поле в нужную модель: ```prisma
model Engagement {
id String @id @default(uuid())
codeName String
focus String? // new field
...
}
prisma/schema.prisma должно сопровождаться: ```bash
npx prisma migrate dev --name describe_your_change
Это создаёт миграцию, обновляет базу данных и перегенерирует типы Prisma Client.
admin по умолчанию создаётся через Prisma seed. Роли Admin имеют полный доступ на создание/редактирование/удаление всех записей. Роли User могут создавать, редактировать и удалять находки и скриншоты; все остальные сущности (engagements, clients, contacts, operators) доступны пользователям только для чтения. Каждая страница панели управления обновляет сессию в базе данных перед чтением конфиденциальных данных. Только администраторы могут получить доступ к странице администратора (/dashboard/users), управлять учётными записями, изменять общеприкладной цвет подсветки, а также создавать резервные копии, восстанавливать или сбрасывать базу данных. Резервное копирование, восстановление и сброс требуют повторного подтверждения пароля. Создание резервной копии — это Server Action; загрузка в браузере использует GET /api/db/backup?file=… с сессией администратора и пятиминутным подписанным грантом в cookie HttpOnly.jose, хранящиеся в cookie HttpOnly, SameSite=Lax, и соответствующую серверную строку Session, которую отзывает выход из системы. Срок действия cookie намеренно не указан для сохранения поведения браузерной сессии; как подписанный токен, так и запись в базе данных истекают через один день. Транзакционный допуск сохраняет не более десяти активных сессий на учётную запись.src/proxy.ts) обеспечивает проверки сессии и 90-дневную ротацию пароля для всех защищённых маршрутов./api/uploads предотвращает IDOR и возвращает ответы no-store.endReportingoutbriefobjectivestargetsexclusionsnotesoperatorscontactstrustedAgentsfindingsfindingContextscreatedAtupdatedAt