Engagement Manager 是一个用于跟踪攻击性安全项目的 Web 应用程序。它采用现代 UI,基于 Next.js、Prisma 和 PostgreSQL 构建。该应用包含日历、项目、客户、联系人、发现和操作员。

每次导入的导出内容限制为 2 MB 和 500 个发现,并设有每用户预览和确认速率限制。应用程序在手动创建、模板和扫描器导入中总共最多接受 10,000 个发现,单个项目最多 500 个。全局 Findings 列表每页加载 100 行,项目/报告发现查询受相同的每项目限制约束。未知布局会明显失败,而不会被静默视为成功导入。扫描器严重性仅为建议:批准前请审查其上下文。引用的 URL、HTML 和嵌入的远程图像不会被获取或执行。
报告允许 1–100 个发现、最多 100 张证据图像(每张 5 MB,总输入 20 MB)、500 页和 25 MB 输出。签发限制为每个项目 50 个版本,整个应用程序已签发 PDF 总计 1 GB。预览和签发设有每用户速率限制,并且每个应用程序进程一次只允许一个 PDF 渲染。发现最多保留 1000 个修订版本和 500 条评论;达到限制时会失败,而不会覆盖历史记录。DejaVu 字体及其再分发许可证包含在 assets/fonts 中;部署必须保留这些资产(Next 输出追踪会包含它们)。
Next.js Server Actions 共享单个 25mb 请求体大小限制(在 next.config.ts 中设置),用于证据上传。登录使用专用的同源 URL 编码路由,在身份验证或数据库操作之前设有 4 KB 流式限制。
这保留了现有的共享已认证工作区,而非新的按客户租户模型。所有新页面、操作和 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、受保护的数据库/备份存储以及对审计输出的运营监控。
在部署此升级之前,请进行常规应用程序备份,并使用 npm run db:migrate 应用附加的 20260904221808_reporting_workflow 和 20260906194500_add_revocable_sessions 迁移,然后重新生成 Prisma Client 并重新构建。现有发现从版本 1 的 Draft 开始,现有浏览器 cookie 必须重新登录,以便获得由服务器支持的会话 ID。不要重置现有数据库。备份通过现有的全数据库导出包含新表和已签发 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
浏览器测试套件会在端口 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(参见 package.json 中的 engines)。如果操作系统软件包版本较旧,请从可信的软件包源安装受支持的版本,并在运行 setup.sh 之前验证其签名。
在运行 Prisma 或应用之前,请在项目根目录中创建 .env 文件:```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` | 生产环境必需 | 必须至少为 **32 个字符**。生产环境中若缺少此变量,应用将拒绝启动。轮换此密钥会使所有现有会话失效。 |
| `TRUST_PROXY` | 否 | 仅当应用位于反向代理之后,且该代理会**覆盖** `X-Forwarded-For` / `X-Real-IP` 和 `X-Forwarded-Host` 时,才设置为 `1`(或 `true`)。在此模式下,登录来源检查会在存在 `X-Forwarded-Host` 时使用它;该值必须包含一个公共主机名,使用时若端口非默认端口也需包含。否则,代理必须保留公共 `Host` 头。这是实现准确的按来源登录限制所必需的生产拓扑。未设置时,请求头将被忽略以防止欺骗,登录使用更高的每分钟共享回退预算,这样单个客户端就无法造成 15 分钟的全局锁定。 |
| `ALLOWED_DEV_ORIGINS` | 否 | **仅限开发环境。** 允许加载 `/_next` 资源的额外主机名(逗号分隔)。服务器当前的局域网 IPv4 地址会自动允许。用于稳定的 DNS 名称。生产构建会忽略此项。 |
生成强密钥:```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 服务,提示输入数据库用户名和密码,写入一个 `chmod 600` 的 `.env` 文件,创建 PostgreSQL 角色和数据库,应用迁移,并初始化默认管理员账户。生产模式还会完成 `npm run build`,并仅打印生产启动命令。它不会从远程 shell 脚本安装 Node.js;请先安装受支持的 Node.js 版本。
对于无头或 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
启动时会同时打印一个回环地址和本机的局域网地址:```
`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(参见 package.json 中的 engines)Secure。uploads/ 目录(发现截图)需要持久化存储克隆仓库并安装依赖: ```bash npm ci
创建包含生产环境值的 .env 文件(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=productionCREATEDB 或超级用户权限uploads/ 位于持久化磁盘上并包含在备份中backups/ 位于持久化磁盘上pg_dump、pg_restore 和 zip 可用播种数据库后,你可以使用生成的临时管理员账户登录:
adminnpx prisma db seed / npm run db:seed 一次性写入仅所有者可读的 initial-admin-credentials.txt注意: 首次登录时,系统将要求你更改此临时密码。之后请立即删除
initial-admin-credentials.txt。所有密码必须至少为 16 个字符,并包含大写字母、小写字母、数字和符号。
/dashboard/users)备份和恢复完整的应用数据。在 Admin 页面上,Database 面板显示 Backup、Restore 和 Reset 按钮。Users 面板列出账户,并提供 New User 按钮用于添加用户。Appearance 面板允许管理员选择应用范围内的高亮颜色。
Backup 需要你的管理员密码,然后将名为 em-backup-YYYY-MM-DD-HHMM.zip 的 .zip 文件保存到应用目录下的 backups/(engagement-mgr/backups/)。成功导出后,在 Admin 页面上使用 Download。一个短期有效的签名授权保存在 HttpOnly cookie 中,且仅对创建该备份的管理员有效。
em-backup-2026-06-02-1430.zip。| 路径 | 内容 |
|---|---|
engagement-manager-backup/database.dump | 来自 pg_dump 的完整 PostgreSQL 自定义格式转储(模式、表、数据、枚举、关系) |
engagement-manager-backup/uploads/ | 数据库中引用的发现截图文件 |
.zip 文件,并替换当前数据库和 uploads/ 文件夹。浏览器恢复限制为 8 MB,因此解压缩不会独占 Web 进程。对于更大的归档文件,请停止应用,并以应用用户身份运行 npm run db:restore -- /absolute/path/to/em-backup.zip。离线命令从工作目录加载 .env,并要求 .env 或环境中具有非空的 DATABASE_URL。它接受最大 500 MB 的常规文件,并通过其展开大小限制流式处理每个归档条目。数据库恢复在一个事务中运行;在安装文件之前,会验证归档条目数量、路径、压缩比和展开大小。备份、恢复、重置和截图文件更改共享一个独占维护锁,因此数据库提交和文件系统交换不会重叠。需要你的管理员密码进行确认。admin。需要输入 RESET 并重新输入确认管理员的当前密码。该密码将成为重新创建账户的临时密码,必须在首次登录时更改。旧服务器
.zip 文件并将其复制到新服务器(例如使用 scp 或 rsync): ```bash
scp em-backup-2026-06-02-1430.zip user@new-server:/path/to/
新服务器
DATABASE_URL 和 JWT_SECRET 的 .env 文件(参见环境配置)。npm ci。initial-admin-credentials.txt 以 admin 身份登录,更改临时密码,并删除该凭据文件。/dashboard/users),点击 Restore(位于 Database 下),选择来自旧服务器的 .zip 文件,输入你的管理员密码,然后确认。注意事项
uploads/ 目录。git clone(或部署相同的修订版本),以便应用与备份所期望的 schema 匹配。如果旧服务器运行的 schema 比克隆的代码更新,请在导入前对齐版本。本节记录了 Engagement Manager 应用程序的架构、数据库 schema、安全措施以及已完成的开发阶段。
Modal.tsx 和 globals.css 中的 .modal-panel 实现完全不透明。报告相关新增内容:Finding 还存储 version、reviewStatus、authorId、reviewerId、templateId 和 importFingerprint;Screenshot 存储 sortOrder。FindingTemplate 保存经过审核的可复用措辞;FindingRevision 保存不可变的文本修订;FindingDraft 保存每个用户的私有草稿及其冲突版本;FindingComment 记录审核讨论;EngagementReport 保存报告标题、执行摘要和有序的 finding 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、 — 原子化的来源和密码确认尝试预留。密码验证还具有有界并发限制。要向现有模型添加新字段(例如,向 Engagement 添加 focus):
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 角色可以创建、编辑和删除 findings 和 screenshots;所有其他实体(engagements、clients、contacts、operators)对用户而言为只读。每个仪表板页面在读取机密数据之前都会针对数据库刷新会话。只有管理员可以访问 Admin 页面(/dashboard/users)、管理账户、更改应用程序范围的高亮颜色,以及备份、恢复或重置数据库。备份、恢复和重置需要重新确认密码。创建备份是一个 Server Action;浏览器下载使用 GET /api/db/backup?file=…,需携带 Admin 会话以及一个存储在 HttpOnly cookie 中的五分钟签名授权。HttpOnly、SameSite=Lax cookie 中的 jose JWT,以及一个匹配的服务器端 Session 行,注销时会撤销该行。有意省略 cookie 过期时间以保持浏览器会话行为;签名令牌和数据库记录均在一天后过期。事务性准入每个账户最多保留十个活动会话。src/proxy.ts)对所有受保护路由强制执行会话检查和 90 天密码轮换。| 扫描器/导出系列 | 接受的导出 |
|---|
| 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 运行、规则和结果 |
resetAthighlightColor(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、endReporting、outbrief、objectives、targets、exclusions、notes、operators(M:N)、contacts/trustedAgents(与 Contact 的 M:N)、findings、findingContexts、createdAt、updatedAt。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。/api/uploadsno-store