
Engagement Manager est une application web pour le suivi des engagements en sécurité offensive. Elle dispose d'une interface moderne, construite avec Next.js, Prisma et PostgreSQL.
Engagement Manager est une application web permettant de suivre les engagements de sécurité offensive. Elle dispose d'une interface moderne, construite avec Next.js, Prisma et PostgreSQL. L'application comprend un calendrier, des engagements, des clients, des contacts, des constats et des opérateurs.

| Famille de scanner/export | Export accepté |
|---|---|
| Burp Suite | Issues XML, y compris la DTD de schéma interne inerte |
| Nessus / Tenable | Nessus v2 XML (.nessus) |
| Nmap | XML ; les ports ouverts et leur sortie de script deviennent des observations informatives, et non des vulnérabilités déduites |
| OpenVAS / Greenbone | Rapport XML natif ou GMP get_reports_response |
| OWASP ZAP | Rapport JSON traditionnel avec sites et alertes |
| Nuclei | JSON Lines (-jsonl) |
| Qualys | XML de résultat de scan (structure SCAN/IP), et non le format distinct de l'API de détection d'hôtes |
| Semgrep / CodeQL et autres producteurs SARIF | Exécutions, règles et résultats SARIF JSON |
Les exports sont limités à 2 Mo et 500 constats par import, avec des limites de débit par utilisateur pour la prévisualisation et la confirmation. L'application accepte au maximum 10 000 constats au total et 500 pour un même engagement, toutes sources confondues (création manuelle, modèles et imports de scanners). La liste globale Findings charge 100 lignes par page, et les requêtes de constats d'engagement/rapport sont plafonnées par la même limite par engagement. Les formats inconnus échouent visiblement plutôt que d'être silencieusement traités comme un import réussi. Les sévérités des scanners sont des suggestions : examinez leur contexte avant approbation. Les URL référencées, le HTML et les images distantes intégrées ne sont ni récupérés ni exécutés.
Les rapports autorisent 1 à 100 constats, jusqu'à 100 images de preuve (5 Mo chacune, 20 Mo d'entrée au total), 500 pages et 25 Mo de sortie. L'émission est limitée à 50 versions par engagement et à 1 Go de PDF émis pour l'ensemble de l'application. La prévisualisation et l'émission ont des limites de débit par utilisateur, et un seul rendu PDF est admis à la fois par processus d'application. Les constats conservent au maximum 1000 révisions et 500 commentaires ; atteindre une limite échoue sans écraser l'historique. Les polices DejaVu et leur licence de redistribution sont incluses dans assets/fonts ; les déploiements doivent conserver ces ressources (le traçage de sortie de Next les inclut).
Les Server Actions de Next.js partagent une limite unique de taille de corps de 25mb (définie dans next.config.ts) pour les téléversements de preuves. La connexion utilise une route dédiée de même origine encodée en URL avec une limite de flux de 4 Ko avant l'authentification ou toute opération sur la base de données.
Cela préserve l'espace de travail authentifié partagé existant, et non un nouveau modèle de location par client. Toutes les nouvelles pages, actions et téléchargements PDF vérifient une session courante adossée à la base de données. Les brouillons sont limités à leur propriétaire ; les permissions de révision, d'approbation de modèle et d'émission sont appliquées côté serveur. Les réponses PDF confidentielles sont privées/no-store. Les PDF finaux ne contiennent qu'une liste blanche explicite de champs de rapport, jamais les brouillons privés, les commentaires de révision ou des engagements sans rapport.
L'implémentation suit la liste de contrôle OWASP Top 10:2025 : contrôles d'accès (A01), réponses privées et contrôles CSP/CSRF existants (A02), dépendances épinglées et CI (A03), protections existantes des sessions/secrets plus contrôles d'intégrité des rapports (A04/A08), Markdown/XML inertes et accès à la base de données paramétré (A05), traitement borné et révision indépendante (A06), vérifications de session en direct (A07), événements d'audit sans contenu (A09), et modifications transactionnelles avec nettoyage en cas d'échec (A10). Un condensé détecte une corruption accidentelle ; ce n'est pas une signature numérique ni une protection contre un administrateur de base de données. Ce n'est pas une certification de conformité. La production nécessite toujours HTTPS, un stockage protégé de la base de données/des sauvegardes et une surveillance opérationnelle de la sortie d'audit.
Avant de déployer cette mise à niveau, effectuez une sauvegarde normale de l'application et appliquez les migrations additives 20260904221808_reporting_workflow et 20260906194500_add_revocable_sessions avec npm run db:migrate, puis régénérez Prisma Client et reconstruisez. Les constats existants commencent en Draft à la version 1, et les cookies de navigateur existants doivent se reconnecter pour recevoir un ID de session adossé au serveur. Ne réinitialisez pas une base de données existante. Les sauvegardes incluent les nouvelles tables et les PDF émis via l'export complet existant de la base de données.
npm test npm run lint npx tsc --noEmit --noUnusedLocals --noUnusedParameters npm run build npm audit
`npm test` utilise le mode de test non isolé de Node avec `tsx` afin que les cas de test TypeScript individuels s'exécutent, plutôt que de simplement signaler le succès du sous-processus de fichier. Gardez les totaux d'assertions explicites visibles dans la CI.
Les régressions de base de données et de navigateur nécessitent une **base de données locale dédiée nommée `reporting_tests`**, avec les migrations appliquées. Elles créent et suppriment leurs propres lignes de fixture ; ne pointez jamais ces tests vers une base de données d'application. Définissez `REPORTING_TEST_DATABASE_URL` vers cette base de données de test, puis exécutez :```bash
DATABASE_URL="$REPORTING_TEST_DATABASE_URL" npx prisma migrate deploy
npm run test:reporting
npx playwright install chromium
npm run test:browser
La suite de navigateurs démarre son propre serveur de développement en boucle locale sur le port 3317 avec un secret de session réservé aux tests ; elle refuse de réutiliser un serveur existant. Définissez REPORTING_TEST_BROWSER sur un exécutable Chromium installé si souhaité. Elle teste la confidentialité des brouillons, les modifications conflictuelles, le téléversement de preuves, la revue indépendante, les permissions/immutabilité des PDF, la création de modèles sans JavaScript, et les imports sélectifs dédupliqués. Les tests d'intégration exercent de véritables conflits transactionnels et des rollbacks. Les suites ne remplacent pas la vérification en LAN distant, sur Safari ou en déploiement de production.
Cette application est conçue pour fonctionner sur Ubuntu, et nécessite les éléments suivants :```bash sudo apt update && sudo apt install -y nodejs npm postgresql postgresql-client postgresql-contrib zip
`postgresql-client` fournit `pg_dump`, `pg_restore` et `psql` ; `zip` crée les archives de sauvegarde. L'extraction de restauration est gérée par l'application avec une validation stricte des entrées et de la taille.
L'installation des paquets ne garantit pas toujours que PostgreSQL soit en cours d'exécution. Démarrez et activez le service avant de créer des rôles ou de lancer l'application :```bash
sudo systemctl enable --now postgresql
sudo systemctl status postgresql --no-pager
Si l'application échoue ensuite avec Can't reach database server at 127.0.0.1:5432, exécutez sudo systemctl start postgresql et confirmez avec pg_isready -h 127.0.0.1 -p 5432.
L'application nécessite Node.js ^22.12.0 ou >=24.0.0 (voir engines dans package.json). Si le paquet du système d'exploitation est plus ancien, installez une version prise en charge depuis une source de paquets fiable dont vous vérifiez les signatures avant d'exécuter setup.sh.
Créez un fichier .env à la racine du projet avant d'exécuter Prisma ou l'application :```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 | Requis | Notes |
|----------|----------|-------|
| `DATABASE_URL` | Oui | Chaîne de connexion PostgreSQL. Prisma utilise le paramètre de requête `schema=public`. La sauvegarde et la restauration utilisent un fichier pgpass temporaire accessible uniquement au propriétaire afin que le mot de passe ne soit pas placé dans les arguments du sous-processus. |
| `JWT_SECRET` | Oui en production | Doit comporter au moins **32 caractères**. L'application refuse de démarrer en production sans celui-ci. Sa rotation invalide toutes les sessions existantes. |
| `TRUST_PROXY` | Non | Définir à `1` (ou `true`) uniquement lorsque l'application se trouve derrière un reverse proxy qui **écrase** `X-Forwarded-For` / `X-Real-IP` et `X-Forwarded-Host`. Les vérifications d'origine de connexion utilisent `X-Forwarded-Host` lorsqu'il est présent dans ce mode ; il doit contenir un seul hôte public, y compris un port non par défaut le cas échéant. Sinon, le proxy doit préserver l'en-tête `Host` public. C'est la topologie de production requise pour des limites de connexion précises par source. Lorsque cette variable n'est pas définie, les en-têtes sont ignorés pour empêcher l'usurpation et la connexion utilise un budget de repli partagé d'une minute plus élevé afin qu'un client ne puisse pas imposer un verrouillage global de 15 minutes. |
| `ALLOWED_DEV_ORIGINS` | Non | **Développement uniquement.** Noms d'hôtes supplémentaires autorisés à charger les ressources `/_next` (séparés par des virgules). Les adresses IPv4 actuelles du réseau local du serveur sont autorisées automatiquement. Utilisez ceci pour un nom DNS stable. Les builds de production ignorent ceci. |
Générez un secret fort :```bash
openssl rand -base64 32
Assurez-vous que PostgreSQL est en cours d'exécution (voir Prérequis). Le script automatisé ./setup.sh démarre le service pour vous ; les étapes manuelles ci-dessous supposent qu'il est déjà démarré.
Exécutez les commandes suivantes pour créer la base de données et l'utilisateur 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;"
### Production
Utilisez un utilisateur de base de données dédié avec le **principe du moindre privilège** — n'accordez pas les droits `CREATEDB` ni les droits de superutilisateur :```bash
sudo -u postgres createuser --pwprompt em_app
sudo -u postgres createdb --owner=em_app engagement_manager
Définissez DATABASE_URL pour utiliser em_app (ou le nom d'utilisateur de votre choix). Les migrations s'exécutent avec cet utilisateur via npm run db:migrate.
Remarque : Les fichiers de base de données sont stockés dans le répertoire de données PostgreSQL (généralement
/var/lib/postgresql/<version>/main/).
Depuis la racine du dépôt, exécutez :```bash chmod +x setup.sh ./setup.sh
Le script installe les prérequis, démarre et active le service PostgreSQL, demande un nom d'utilisateur et un mot de passe de base de données, écrit un `.env` en `chmod 600`, crée le rôle et la base de données PostgreSQL, applique les migrations et initialise le compte administrateur par défaut. Le mode production exécute également `npm run build` et n'affiche que la commande de démarrage en production. Il n'installe pas Node.js à partir d'un script shell distant ; installez d'abord une version prise en charge de Node.js.
Pour une utilisation sans interface graphique ou 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
Exécutez ./setup.sh --help pour toutes les options.
--db-pass=... a été supprimé car les secrets en ligne de commande sont visibles par d'autres processus. Placez le mot de passe dans un fichier accessible uniquement au propriétaire et remplacez l'ancien argument par --db-pass-file=/secure/db-password ; l'exemple de configuration automatisée ci-dessus est prêt à copier-coller.setup.sh n'installe plus Node.js. Installez une version prise en charge de Node.js (^22.12.0 ou >=24.0.0) depuis une source de paquets fiable avant de l'exécuter.npm ci, donc package-lock.json doit être présent et synchronisé avec package.json..sql héritées ne peuvent pas être restaurées. Avant de mettre hors service un ancien serveur, mettez-le à niveau vers une version capable de créer la sauvegarde structurée de l'application et réexportez les données au format .zip.Depuis le répertoire du projet, une seule commande installe les mises à jour des paquets, démarre PostgreSQL s'il est arrêté, et lance l'application :```bash ./run.sh
Laissez cette fenêtre ouverte. Utilisez l'adresse locale ou réseau qu'elle affiche.
Pour le démarrer vous-même à la place : PostgreSQL doit être en cours d'exécution (`sudo systemctl start postgresql` si nécessaire). Ensuite, démarrez le serveur de développement :```bash
npm run dev
Le démarrage affiche à la fois une URL de loopback et l’adresse LAN de cette machine :```
`npm run dev` et `npm start` se lient à `0.0.0.0` afin que l'URL réseau fonctionne sur le LAN. Considérez l'accès LAN comme réservé au laboratoire sur un réseau de confiance. Le mode dev n'est pas durci pour l'internet public.
Si vous ouvrez l'application par **nom d'hôte** (et non par IP) et que le navigateur distant affiche une page blanche, ajoutez ce nom dans `.env` et redémarrez :```bash
ALLOWED_DEV_ORIGINS=dev.office.example
^22.12.0 ou >=24.0.0 (voir engines dans package.json)Secure en production.uploads/ (captures d'écran des findings)Cloner le dépôt et installer les dépendances : ```bash npm ci
Créez .env avec les valeurs de production (DATABASE_URL, JWT_SECRET ≥ 32 caractères).
Appliquez les migrations de base de données : ```bash npm run db:migrate
Exécutez les vérifications avant déploiement : ```bash npm run audit npm run typecheck npm run build
Démarrez l'application avec NODE_ENV=production : ```bash
NODE_ENV=production npm run start
Pour un vrai serveur, exécutez ceci sous un gestionnaire de processus (systemd, PM2, etc.) et placez un reverse proxy devant pour la terminaison TLS.
JWT_SECRET fait au moins 32 caractères et n'est pas commité dans gitNODE_ENV=production est défini pour le processus en cours d'exécutionCREATEDB ni superutilisateuruploads/ se trouve sur un disque persistant et est inclus dans les sauvegardesbackups/ se trouve sur un disque persistant si les administrateurs utilisent Backuppg_dump, pg_restore et zip sont disponibles si les administrateurs vont utiliser Backup/RestoreAprès avoir initialisé la base de données, vous pouvez vous connecter en utilisant le compte administrateur temporaire généré :
admininitial-admin-credentials.txt accessible uniquement au propriétaire par npx prisma db seed / npm run db:seedRemarque : Vous devrez changer ce mot de passe temporaire lors de la première connexion. Supprimez
initial-admin-credentials.txtimmédiatement après. Tous les mots de passe doivent comporter au moins 16 caractères et inclure une lettre majuscule, une lettre minuscule, un chiffre et un symbole.
/dashboard/users).Sur Admin, le panneau Database affiche les boutons Backup, Restore et Reset. Le panneau Users liste les comptes et fournit un bouton New User pour ajouter des utilisateurs. Le panneau Appearance permet à un administrateur de choisir la couleur de surbrillance à l'échelle de l'application.
Backup nécessite votre mot de passe administrateur, puis enregistre un fichier .zip nommé em-backup-YYYY-MM-DD-HHMM.zip dans backups/ du répertoire de l'application (engagement-mgr/backups/). Après une exportation réussie, utilisez Download sur la page Admin. Une autorisation signée à courte durée de vie est conservée dans un cookie HttpOnly et ne fonctionne que pour l'administrateur qui a créé la sauvegarde.
em-backup-2026-06-02-1430.zip.| Chemin | Contenu |
|---|---|
engagement-manager-backup/database.dump | Dump complet PostgreSQL au format personnalisé (schéma, tables, données, énumérations, relations) issu de pg_dump |
engagement-manager-backup/uploads/ | Fichiers de capture d'écran de constatations référencés dans la base de données |
.zip créé par Backup et remplace la base de données actuelle et le dossier uploads/. La restauration via navigateur est limitée à 8 Mo afin que la décompression ne puisse pas monopoliser le processus web. Pour une archive plus volumineuse, arrêtez l'application et exécutez npm run db:restore -- /absolute/path/to/em-backup.zip en tant qu'utilisateur de l'application. La commande hors ligne charge .env depuis le répertoire de travail et nécessite un DATABASE_URL non vide dans .env ou dans l'environnement. Elle accepte des fichiers réguliers jusqu'à 500 Mo et fait transiter chaque entrée d'archive par sa limite de taille développée. La restauration de la base de données s'exécute dans une seule transaction ; les nombres d'entrées d'archive, les chemins, les taux de compression et les tailles développées sont validés avant l'installation des fichiers. Backup, restore, reset et les modifications de fichiers de capture d'écran partagent un verrou de maintenance exclusif afin que les validations de base de données et les échanges de système de fichiers ne puissent pas se chevaucher. Nécessite votre mot de passe administrateur pour confirmer.admin. Nécessite de saisir RESET et de ressaisir le mot de passe actuel de l'administrateur qui confirme. Ce mot de passe devient le mot de passe temporaire du compte recréé et doit être changé lors de la première connexion.Ancien serveur
.zip et copiez-le sur le nouveau serveur (par exemple avec scp ou rsync) : ```bash
scp em-backup-2026-06-02-1430.zip user@new-server:/path/to/
Nouveau serveur
.env avec DATABASE_URL et JWT_SECRET (voir Configuration de l'environnement).npm ci.admin à l'aide du fichier initial-admin-credentials.txt réservé au propriétaire, changez le mot de passe temporaire et supprimez le fichier d'identifiants./dashboard/users), cliquez sur Restore (sous Database), sélectionnez le .zip de l'ancien serveur, saisissez votre mot de passe administrateur et confirmez.Remarques
uploads/.git clone (ou déployez la même révision) sur le nouveau serveur afin que l'application corresponde au schéma attendu par la sauvegarde. Si l'ancien serveur exécutait un schéma plus récent que le code cloné, alignez les versions avant l'import.Cette section documente l'architecture, le schéma de base de données, les mesures de sécurité et les phases de développement terminées pour l'application Engagement Manager.
Modal.tsx et .modal-panel dans globals.css.Ajouts pour le reporting : Finding stocke également version, reviewStatus, authorId, reviewerId, templateId et importFingerprint ; Screenshot stocke sortOrder. FindingTemplate contient la formulation réutilisable révisée ; FindingRevision contient les révisions de texte immuables ; FindingDraft contient les brouillons privés par utilisateur avec versions de conflit ; FindingComment enregistre les discussions de révision ; EngagementReport contient le titre du rapport, le résumé exécutif et les ID de findings ordonnés ; IssuedReport stocke un PDF immuable, un instantané du contenu et un condensé SHA-256 pour chaque version émise. Les relations utilisateur auteur/réviseur utilisent SetNull ; les brouillons privés sont supprimés lorsque leur utilisateur est supprimé. Les enregistrements de reporting suivent le cycle de vie de leur engagement/finding parent.
id, username, passwordHash, role (Admin, User), lastPasswordChange, lastLogin, sessions, createdAt, updatedAt.id, userId, expiresAt, createdAt — les enregistrements côté serveur rendent chaque session de connexion signée individuellement révocable lors de la déconnexion.key, count, resetAt — réservations atomiques des tentatives de source et de confirmation de mot de passe. La vérification du mot de passe dispose également d'une limite de concurrence bornée.highlightColor (Red, Blue, Teal, Green, Purple ou Amber) et 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 avec Contact), , , , .id, company (colonne DB : companyName), address, city, state, zip, phone (colonne DB : phoneNumber), website, notes, contacts, engagements, createdAt, updatedAt.id, clientId, name, title, email, phone (colonne DB : phoneNumber), notes, assignedEngagements, trustedEngagements, createdAt, updatedAt.id, engagementId (optionnel), title, category, severity, background, remediation, supportingData (colonne DB : 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.Pour ajouter un nouveau champ à un modèle existant (par exemple, focus sur Engagement) :
prisma/schema.prisma et ajoutez le champ au modèle souhaité : ```prisma
model Engagement {
id String @id @default(uuid())
codeName String
focus String? // new field
...
}
prisma/schema.prisma doit être suivie de : ```bash
npx prisma migrate dev --name describe_your_change
Cela crée une migration, met à jour la base de données et régénère les types du Prisma Client.
admin par défaut est généré via le seed Prisma. Les rôles Admin disposent d'un accès complet en création/modification/suppression sur tous les enregistrements. Les rôles User peuvent créer, modifier et supprimer des findings et des captures d'écran ; toutes les autres entités (engagements, clients, contacts, opérateurs) sont en lecture seule pour les utilisateurs. Chaque page du tableau de bord actualise la session auprès de la base de données avant de lire des données confidentielles. Seuls les admins peuvent accéder à la page Admin (/dashboard/users), gérer les comptes, modifier la couleur de surbrillance globale de l'application, et sauvegarder, restaurer ou réinitialiser la base de données. La sauvegarde, la restauration et la réinitialisation nécessitent une re-confirmation du mot de passe. La création d'une sauvegarde est une Server Action ; le téléchargement depuis le navigateur utilise GET /api/db/backup?file=… avec la session Admin et une autorisation signée de cinq minutes dans un cookie HttpOnly.jose stockés dans des cookies HttpOnly, SameSite=Lax, avec une ligne Session correspondante côté serveur que la déconnexion révoque. L'expiration du cookie est volontairement omise pour conserver le comportement de session du navigateur ; le jeton signé et l'enregistrement en base expirent tous deux après un jour. L'admission transactionnelle conserve au maximum dix sessions actives par compte.src/proxy.ts) applique les vérifications de session et la rotation des mots de passe tous les 90 jours sur toutes les routes protégées./api/uploads empêche les IDOR et renvoie des réponses no-store.endReportingoutbriefobjectivestargetsexclusionsnotesoperatorscontactstrustedAgentsfindingsfindingContextscreatedAtupdatedAt