
Engagement Manager es una aplicación web para el seguimiento de compromisos de seguridad ofensiva. Cuenta con una interfaz de usuario moderna, construida con Next.js, Prisma y PostgreSQL.
Engagement Manager es una aplicación web para el seguimiento de compromisos de seguridad ofensiva. Cuenta con una interfaz de usuario moderna, construida con Next.js, Prisma y PostgreSQL. La aplicación incluye un calendario, compromisos, clientes, contactos, hallazgos y operadores.

| Familia de escáner/exportación | Exportación aceptada |
|---|---|
| Burp Suite | Issues XML, incluido el DTD de esquema interno inerte |
| Nessus / Tenable | Nessus v2 XML (.nessus) |
| Nmap | XML; los puertos abiertos y la salida de sus scripts se convierten en observaciones informativas, no en vulnerabilidades inferidas |
| OpenVAS / Greenbone | Informe XML nativo o GMP get_reports_response |
| OWASP ZAP | Informe JSON tradicional con sitios y alertas |
| Nuclei | JSON Lines (-jsonl) |
| Qualys | XML de resultados de escaneo (estructura SCAN/IP), no el formato separado de la API de detección de hosts |
| Semgrep / CodeQL y otros productores de SARIF | Ejecuciones, reglas y resultados SARIF JSON |
Las exportaciones están limitadas a 2 MB y 500 hallazgos por importación, con límites de tasa de previsualización y confirmación por usuario. La aplicación acepta como máximo 10.000 hallazgos en total y 500 para un solo compromiso entre creación manual, plantillas e importaciones de escáneres. La lista global de Findings carga 100 filas por página, y las consultas de hallazgos de compromiso/informe están limitadas por el mismo límite por compromiso. Los diseños desconocidos fallan de forma visible en lugar de tratarse silenciosamente como una importación exitosa. Las severidades del escáner son sugerencias: revisa su contexto antes de la aprobación. Las URL referenciadas, el HTML y las imágenes remotas incrustadas no se obtienen ni se ejecutan.
Los informes permiten de 1 a 100 hallazgos, hasta 100 imágenes de evidencia (5 MB cada una, 20 MB de entrada total), 500 páginas y 25 MB de salida. La emisión está limitada a 50 versiones por compromiso y 1 GB de PDF emitidos en toda la aplicación. La previsualización y la emisión tienen límites de tasa por usuario, y solo se admite una renderización de PDF por proceso de aplicación a la vez. Los hallazgos conservan como máximo 1000 revisiones y 500 comentarios; alcanzar un límite falla sin sobrescribir el historial. Las fuentes DejaVu y su licencia de redistribución se incluyen en assets/fonts; los despliegues deben conservar estos recursos (el trazado de salida de Next los incluye).
Las Server Actions de Next.js comparten un único límite de tamaño de cuerpo de 25mb (establecido en next.config.ts) para las subidas de evidencia. El inicio de sesión utiliza una ruta dedicada codificada por URL del mismo origen con un límite de transmisión de 4 KB antes de la autenticación o del trabajo en la base de datos.
Esto preserva el espacio de trabajo autenticado compartido existente, no un nuevo modelo de tenencia por cliente. Todas las páginas nuevas, acciones y descargas de PDF comprueban una sesión actual respaldada por la base de datos. Los borradores están limitados a su propietario; los permisos de revisión, aprobación de plantillas y emisión se aplican en el lado del servidor. Las respuestas de PDF confidenciales son privadas/no-store. Los PDF finales contienen solo una lista de campos de informe explícita permitida, nunca borradores privados, comentarios de revisión ni compromisos no relacionados.
La implementación utiliza la lista de verificación OWASP Top 10:2025: comprobaciones de acceso (A01), respuestas privadas y controles CSP/CSRF existentes (A02), dependencias fijadas y CI (A03), protecciones de sesión/secreto existentes más comprobaciones de integridad de informes (A04/A08), Markdown/XML inerte y acceso parametrizado a la base de datos (A05), procesamiento acotado y revisión independiente (A06), comprobaciones de sesión en vivo (A07), eventos de auditoría sin contenido (A09) y cambios transaccionales con limpieza en caso de fallo (A10). Un resumen detecta corrupción accidental; no es una firma digital ni una protección frente a un administrador de base de datos. Esto no es una certificación de cumplimiento. Producción sigue requiriendo HTTPS, almacenamiento protegido de base de datos/copias de seguridad y monitoreo operativo de la salida de auditoría.
Antes de desplegar esta actualización, realiza una copia de seguridad normal de la aplicación y aplica las migraciones aditivas 20260904221808_reporting_workflow y 20260906194500_add_revocable_sessions con npm run db:migrate, luego regenera Prisma Client y reconstruye. Los hallazgos existentes comienzan como Draft en la versión 1, y las cookies del navegador existentes deben iniciar sesión de nuevo para recibir un ID de sesión respaldado por el servidor. No reinicies una base de datos existente. Las copias de seguridad incluyen las nuevas tablas y los PDF emitidos a través de la exportación completa de base de datos existente.
npm test npm run lint npx tsc --noEmit --noUnusedLocals --noUnusedParameters npm run build npm audit
`npm test` usa el modo de prueba no aislado de Node con `tsx` para que los casos de prueba individuales de TypeScript se ejecuten, en lugar de limitarse a informar el éxito del subproceso del archivo. Mantén visibles los totales de aserciones explícitas en CI.
Las regresiones de base de datos y navegador requieren una **base de datos local dedicada llamada `reporting_tests`**, con las migraciones aplicadas. Estas crean y eliminan sus propias filas de fixture; nunca apuntes estas pruebas a una base de datos de aplicación. Establece `REPORTING_TEST_DATABASE_URL` a esa base de datos de prueba, luego ejecuta:```bash
DATABASE_URL="$REPORTING_TEST_DATABASE_URL" npx prisma migrate deploy
npm run test:reporting
npx playwright install chromium
npm run test:browser
El conjunto de pruebas del navegador inicia su propio servidor de desarrollo en loopback en el puerto 3317 con un secreto de sesión exclusivo para pruebas; se niega a reutilizar un servidor existente. Establece REPORTING_TEST_BROWSER a un ejecutable de Chromium instalado si se desea. Prueba la privacidad de borradores, ediciones conflictivas, carga de evidencias, revisión independiente, permisos/inmutabilidad de PDF, creación de plantillas sin JavaScript e importaciones selectivas deduplicadas. Las pruebas de integración ejercitan conflictos transaccionales reales y rollback. Los conjuntos de pruebas no reemplazan la verificación en LAN remota, Safari o despliegue en producción.
Esta aplicación está diseñada para ejecutarse en Ubuntu y requiere lo siguiente:```bash sudo apt update && sudo apt install -y nodejs npm postgresql postgresql-client postgresql-contrib zip
`postgresql-client` proporciona `pg_dump`, `pg_restore` y `psql`; `zip` crea archivos de respaldo. La extracción de la restauración es gestionada por la aplicación con validación estricta de entradas y tamaño.
Instalar los paquetes no siempre deja PostgreSQL en ejecución. Inicie y habilite el servicio antes de crear roles o iniciar la aplicación:```bash
sudo systemctl enable --now postgresql
sudo systemctl status postgresql --no-pager
Si la aplicación falla más tarde con Can't reach database server at 127.0.0.1:5432, ejecuta sudo systemctl start postgresql y confirma con pg_isready -h 127.0.0.1 -p 5432.
La aplicación requiere Node.js ^22.12.0 o >=24.0.0 (consulta engines en package.json). Si el paquete del sistema operativo es más antiguo, instala una versión compatible desde una fuente de paquetes de confianza cuyas firmas verifiques antes de ejecutar setup.sh.
Crea un archivo .env en la raíz del proyecto antes de ejecutar Prisma o la aplicación:```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
| Variable | Obligatorio | Notas |
|----------|----------|-------|
| `DATABASE_URL` | Sí | Cadena de conexión de PostgreSQL. Prisma utiliza el parámetro de consulta `schema=public`. La copia de seguridad y la restauración utilizan un archivo pgpass temporal solo para el propietario, de modo que la contraseña no se coloca en los argumentos del subproceso. |
| `JWT_SECRET` | Sí en producción | Debe tener al menos **32 caracteres**. La aplicación se niega a iniciarse en producción sin él. Rotarlo invalida todas las sesiones existentes. |
| `TRUST_PROXY` | No | Establézcalo en `1` (o `true`) solo cuando la aplicación esté detrás de un proxy inverso que **sobrescriba** `X-Forwarded-For` / `X-Real-IP` y `X-Forwarded-Host`. Las comprobaciones de origen de inicio de sesión utilizan `X-Forwarded-Host` cuando está presente en este modo; debe contener un host público, incluido un puerto no predeterminado cuando se utilice. De lo contrario, el proxy debe preservar el encabezado `Host` público. Esta es la topología de producción requerida para límites precisos de inicio de sesión por origen. Cuando no se establece, los encabezados se ignoran para evitar la suplantación y el inicio de sesión utiliza un presupuesto de respaldo compartido de un minuto más alto, de modo que un cliente no puede imponer un bloqueo global de 15 minutos. |
| `ALLOWED_DEV_ORIGINS` | No | **Solo desarrollo.** Nombres de host adicionales permitidos para cargar recursos de `/_next` (separados por comas). Las direcciones IPv4 actuales de la LAN del servidor se permiten automáticamente. Utilice esto para un nombre DNS estable. Las compilaciones de producción ignoran esto. |
Genere un secreto fuerte:```bash
openssl rand -base64 32
Asegúrate de que PostgreSQL esté en ejecución primero (consulta Requisitos previos). El script automatizado ./setup.sh inicia el servicio por ti; los pasos manuales a continuación asumen que ya está en funcionamiento.
Ejecuta los siguientes comandos para crear la base de datos y el usuario de 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;"
### Producción
Utilice un usuario de base de datos dedicado con **privilegios mínimos** — no otorgue `CREATEDB` ni derechos de superusuario:```bash
sudo -u postgres createuser --pwprompt em_app
sudo -u postgres createdb --owner=em_app engagement_manager
Establece DATABASE_URL para usar em_app (o el nombre de usuario que elijas). Las migraciones se ejecutan como este usuario mediante npm run db:migrate.
Nota: Los archivos de la base de datos se almacenan en el directorio de datos de PostgreSQL (normalmente
/var/lib/postgresql/<version>/main/).
Desde la raíz del repositorio, ejecuta:```bash chmod +x setup.sh ./setup.sh
El script instala los requisitos previos, inicia y habilita el servicio de PostgreSQL, solicita un nombre de usuario y una contraseña para la base de datos, escribe un `.env` con `chmod 600`, crea el rol y la base de datos de PostgreSQL, aplica las migraciones y siembra la cuenta de administrador predeterminada. El modo de producción también completa `npm run build` e imprime únicamente el comando de inicio de producción. No instala Node.js desde un script de shell remoto; instala primero una versión compatible de Node.js.
Para uso sin interfaz gráfica o en 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
Ejecute ./setup.sh --help para ver todas las opciones.
--db-pass=... porque los secretos en la línea de comandos son visibles para otros procesos. Coloque la contraseña en un archivo accesible solo por el propietario y reemplace el argumento antiguo con --db-pass-file=/secure/db-password; el ejemplo de configuración automatizada anterior está listo para copiar y pegar.setup.sh ya no instala Node.js. Instale una versión compatible de Node.js (^22.12.0 o >=24.0.0) desde una fuente de paquetes confiable antes de ejecutarlo.npm ci, por lo que package-lock.json debe estar presente y sincronizado con package.json..sql heredadas no se pueden restaurar. Antes de retirar un servidor antiguo, actualícelo a una versión que pueda crear la copia de seguridad estructurada de la aplicación y vuelva a exportar los datos como un .zip.Desde el directorio del proyecto, un solo comando instala las actualizaciones de paquetes, inicia PostgreSQL si está detenido y arranca la aplicación:```bash ./run.sh
Deja esa ventana abierta. Usa la dirección Local o Network que imprime.
Para iniciarlo tú mismo en su lugar: PostgreSQL debe estar en ejecución (`sudo systemctl start postgresql` si es necesario). Luego inicia el servidor de desarrollo:```bash
npm run dev
Startup imprime tanto una URL de loopback como la dirección LAN de esta máquina:```
`npm run dev` y `npm start` enlazan `0.0.0.0` para que la URL de red funcione en la LAN. Considera el acceso por LAN solo para laboratorio en una red de confianza. El modo de desarrollo no está reforzado para internet público.
Si abres la aplicación por **nombre de host** (no por IP) y el navegador remoto muestra una página en blanco, añade ese nombre a `.env` y reinicia:```bash
ALLOWED_DEV_ORIGINS=dev.office.example
^22.12.0 o >=24.0.0 (ver engines en package.json)Secure en producción.uploads/ (capturas de pantalla de hallazgos)Clona el repositorio e instala las dependencias: ```bash npm ci
Cree .env con valores de producción (DATABASE_URL, JWT_SECRET ≥ 32 caracteres).
Aplique las migraciones de la base de datos: ```bash npm run db:migrate
Ejecutar las comprobaciones previas al despliegue: ```bash npm run audit npm run typecheck npm run build
Inicie la aplicación con NODE_ENV=production: ```bash
NODE_ENV=production npm run start
Para un servidor real, ejecuta esto bajo un gestor de procesos (systemd, PM2, etc.) y coloca un proxy inverso delante para la terminación de TLS.
JWT_SECRET tiene al menos 32 caracteres y no está comprometido en gitNODE_ENV=production está configurado para el proceso en ejecuciónCREATEDB ni de superusuariouploads/ está en disco persistente e incluido en las copias de seguridadbackups/ está en disco persistente si los administradores usan Backuppg_dump, pg_restore y zip están disponibles si los administradores van a usar Backup/RestoreDespués de hacer el seed de la base de datos, puedes iniciar sesión usando la cuenta de administrador temporal generada:
admininitial-admin-credentials.txt con permisos solo para el propietario mediante npx prisma db seed / npm run db:seedNota: Se te pedirá que cambies esta contraseña temporal en el primer inicio de sesión. Elimina
initial-admin-credentials.txtinmediatamente después. Todas las contraseñas deben tener al menos 16 caracteres e incluir una letra mayúscula, una letra minúscula, un número y un símbolo.
/dashboard/users).En Admin, el panel Database muestra los botones Backup, Restore y Reset. El panel Users lista las cuentas y proporciona un botón New User para añadir usuarios. El panel Appearance permite a un administrador elegir el color de resaltado de toda la aplicación.
Backup requiere tu contraseña de administrador y luego guarda un .zip llamado em-backup-YYYY-MM-DD-HHMM.zip en backups/ dentro del directorio de la aplicación (engagement-mgr/backups/). Tras una exportación exitosa, usa Download en la página Admin. Se mantiene una concesión firmada de corta duración en una cookie HttpOnly y solo funciona para el administrador que creó la copia de seguridad.
em-backup-2026-06-02-1430.zip.| Ruta | Contenido |
|---|---|
engagement-manager-backup/database.dump | Volcado completo de PostgreSQL en formato personalizado (esquema, tablas, datos, enums, relaciones) de pg_dump |
engagement-manager-backup/uploads/ | Archivos de capturas de pantalla de hallazgos referenciados en la base de datos |
.zip creado por Backup y reemplaza la base de datos actual y la carpeta uploads/. La restauración desde el navegador está limitada a 8 MB para que la descompresión no pueda monopolizar el proceso web. Para un archivo más grande, detén la aplicación y ejecuta npm run db:restore -- /absolute/path/to/em-backup.zip como el usuario de la aplicación. El comando offline carga .env desde el directorio de trabajo y requiere un DATABASE_URL no vacío en .env o en el entorno. Acepta archivos regulares de hasta 500 MB y transmite cada entrada del archivo a través de su límite de tamaño expandido. La restauración de la base de datos se ejecuta en una sola transacción; los recuentos de entradas del archivo, las rutas, las tasas de compresión y los tamaños expandidos se validan antes de instalar los archivos. Backup, restore, reset y los cambios de archivos de capturas de pantalla comparten un bloqueo de mantenimiento exclusivo para que las confirmaciones de la base de datos y los intercambios del sistema de archivos no puedan solaparse. Requiere tu contraseña de administrador para confirmar.admin. Requiere escribir RESET y volver a introducir la contraseña actual del administrador que confirma. Esa contraseña se convierte en la contraseña temporal de la cuenta recreada y debe cambiarse en el primer inicio de sesión.Servidor antiguo
.zip y cópialo al nuevo servidor (por ejemplo con scp o rsync): ```bash
scp em-backup-2026-06-02-1430.zip user@new-server:/path/to/
Servidor nuevo
.env con DATABASE_URL y JWT_SECRET (consulta Configuración del entorno).npm ci.admin usando el archivo initial-admin-credentials.txt exclusivo del propietario, cambie la contraseña temporal y elimine el archivo de credenciales./dashboard/users), haga clic en Restore (en Database), seleccione el .zip del servidor antiguo, introduzca su contraseña de administrador y confirme.Notas
uploads/.git clone (o despliegue la misma revisión) en el nuevo servidor para que la aplicación coincida con el esquema esperado por la copia de seguridad. Si el servidor antiguo ejecutaba un esquema más reciente que el código clonado, alinee las versiones antes de importar.Esta sección documenta la arquitectura, el esquema de base de datos, las medidas de seguridad y las fases de desarrollo completadas para la aplicación Engagement Manager.
Modal.tsx y .modal-panel en globals.css.Adiciones de informes: Finding también almacena version, reviewStatus, authorId, reviewerId, templateId e importFingerprint; Screenshot almacena sortOrder. FindingTemplate contiene redacción reutilizable revisada; FindingRevision contiene revisiones de texto inmutables; FindingDraft contiene borradores privados por usuario con versiones de conflicto; FindingComment registra discusiones de revisión; EngagementReport contiene el título del informe, el resumen ejecutivo y los IDs de hallazgos ordenados; IssuedReport almacena un PDF inmutable, una instantánea del contenido y un digest SHA-256 para cada versión emitida. Las relaciones de autor/revisor de usuario usan SetNull; los borradores privados se eliminan cuando se elimina su usuario. Los registros de informes siguen el ciclo de vida de su engagement/finding padre.
id, username, passwordHash, role (Admin, User), lastPasswordChange, lastLogin, sessions, createdAt, updatedAt.id, userId, expiresAt, createdAt — los registros del lado del servidor hacen que cada sesión de inicio de sesión firmada sea revocable individualmente al cerrar sesión.key, count, resetAt — reservas atómicas de intentos de origen y de confirmación de contraseña. La verificación de contraseña también tiene un límite de concurrencia acotado.highlightColor (Red, Blue, Teal, Green, Purple o Amber) y 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 con Contact), , , , .id, company (columna de BD: companyName), address, city, state, zip, phone (columna de BD: phoneNumber), website, notes, contacts, engagements, createdAt, updatedAt.id, clientId, name, title, email, phone (columna de BD: phoneNumber), notes, assignedEngagements, trustedEngagements, createdAt, updatedAt.id, engagementId (opcional), title, category, severity, background, remediation, supportingData (columna de BD: 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.Para añadir un nuevo campo a un modelo existente (p. ej., focus en Engagement):
prisma/schema.prisma y añada el campo al modelo deseado: ```prisma
model Engagement {
id String @id @default(uuid())
codeName String
focus String? // new field
...
}
prisma/schema.prisma debe ir seguido de: ```bash
npx prisma migrate dev --name describe_your_change
Esto crea una migración, actualiza la base de datos y regenera los tipos de Prisma Client.
admin predeterminada se genera mediante el seed de Prisma. Los roles Admin tienen acceso completo de creación/edición/eliminación a todos los registros. Los roles User pueden crear, editar y eliminar hallazgos y capturas de pantalla; todas las demás entidades (compromisos, clientes, contactos, operadores) son de solo lectura para los usuarios. Cada página del panel refresca la sesión contra la base de datos antes de leer datos confidenciales. Solo los administradores pueden acceder a la página de administración (/dashboard/users), gestionar cuentas, cambiar el color de resaltado de toda la aplicación y hacer copias de seguridad, restaurar o restablecer la base de datos. La copia de seguridad, la restauración y el restablecimiento requieren re-confirmación de contraseña. Crear una copia de seguridad es una Server Action; la descarga desde el navegador usa GET /api/db/backup?file=… con la sesión de Admin y una concesión firmada de cinco minutos en una cookie HttpOnly.jose almacenados en cookies HttpOnly, SameSite=Lax y una fila Session correspondiente en el servidor que el logout revoca. La expiración de la cookie se omite intencionalmente para mantener el comportamiento de sesión del navegador; tanto el token firmado como el registro de la base de datos expiran después de un día. La admisión transaccional retiene como máximo diez sesiones activas por cuenta.src/proxy.ts) aplica comprobaciones de sesión y rotación de contraseña de 90 días en todas las rutas protegidas./api/uploads evita IDOR y devuelve respuestas no-store.endReportingoutbriefobjectivestargetsexclusionsnotesoperatorscontactstrustedAgentsfindingsfindingContextscreatedAtupdatedAt