Appearance
Rapport d'audit sécurité — Heriade Contacts
Date : 21 mai 2026 Auditeur : Claude Code (security-audit skill) Environnement : Production (Railway) Scope : Audit complet OWASP Top 10
Résumé exécutif
L'application présente une architecture multi-tenant solide et une bonne validation des inputs. Cependant, 25 CVE actives dont 1 critique sont présentes dans les dépendances, l'application manque totalement de security headers (CSP, HSTS, X-Frame-Options en prod), n'a aucun rate limiting, et expose un open redirect dans le callback SSO.
Score global : 5.8/10
Résultats
CRITIQUE — Corriger immédiatement
| # | Vulnérabilité | OWASP | CWE | Fichier:ligne | Fix recommandé |
|---|---|---|---|---|---|
| C1 | @fastify/middie : bypass d'authentification middleware dans les child plugin scopes | A06, A07 | CWE-287 | node_modules/@fastify/middie v8.3.3 (requis >=9.3.2) | Résolution yarn : "@fastify/middie": ">=9.3.2" ou migration NestJS v11 |
HAUTE — Corriger avant prochaine release
| # | Vulnérabilité | OWASP | CWE | Fichier:ligne | Fix recommandé |
|---|---|---|---|---|---|
| H1 | Absence totale de Content Security Policy (confirmé prod Lighthouse) | A05 | CWE-79 | nuxt.config.ts (aucune config headers) | Ajouter CSP dans nitro.routeRules |
| H2 | Absence de HSTS (confirmé prod Lighthouse) | A05 | CWE-319 | nuxt.config.ts | Strict-Transport-Security: max-age=63072000; includeSubDomains |
| H3 | @nestjs/platform-fastify : URL Encoding Middleware Bypass | A06, A01 | CWE-639 | node_modules/@nestjs/platform-fastify v10.4.22 | Migration NestJS v11 |
| H4 | Fastify : Content-Type tab character bypass | A06, A03 | CWE-436 | node_modules/fastify v4.28.1 | Résolution fastify >= 5.7.2 (breaking change) |
| H5 | fast-uri : path traversal + host confusion (x4 CVE) | A06, A10 | CWE-22, CWE-918 | node_modules/fast-uri | resolutions: { "fast-uri": ">=3.1.2" } |
| H6 | JWT stocké en localStorage | A07, A02 | CWE-922 | apps/web/stores/auth.ts:41 | Migrer vers cookie HttpOnly+Secure+SameSite=Lax |
| H7 | Open Redirect non validé dans callback SSO | A01 | CWE-601 | apps/web/pages/login/callback.vue:35-36 | Valider que redirect commence par / (voir fix ci-dessous) |
| H8 | Absence totale de rate limiting | A04, A07 | CWE-307 | apps/api/src/main.ts | Installer @nestjs/throttler, 100 req/min global, 10/min sur auth |
| H9 | lodash : Code Injection via _.template | A06, A03 | CWE-94 | node_modules/lodash | resolutions: { "lodash": ">=4.18.0" } |
| H10 | Pagination sans plafond ?limit=999999 → DoS | A04 | CWE-770 | DTOs pagination (contacts, companies, reminders) | Ajouter @Max(100) sur tous les DTOs |
MOYENNE — À planifier
| # | Vulnérabilité | OWASP | CWE | Fichier:ligne | Fix recommandé |
|---|---|---|---|---|---|
| M1 | Absence X-Frame-Options, X-Content-Type-Options, Referrer-Policy | A05 | CWE-1021 | nuxt.config.ts, apps/api/src/main.ts | Headers globaux Nuxt + @fastify/helmet |
| M2 | Nuxt 3.20.2 : XSS reflétée via navigateTo() | A06, A03 | CWE-79 | node_modules/nuxt v3.20.2 | yarn workspace @heriade/web upgrade nuxt@>=3.21.6 |
| M3 | Token JWT visible dans l'historique navigateur via hash fragment | A07, A02 | CWE-598 | apps/web/pages/login/callback.vue:23,33 | Travailler avec le portail SSO pour adopter postMessage ou cookie |
| M4 | dev-login conditionné uniquement sur NODE_ENV | A07 | CWE-798 | apps/api/src/auth/auth.controller.ts:38 | Ajouter variable opt-in DEV_LOGIN_ENABLED=true explicite |
| M5 | Cache service worker des données CRM pendant 24h | A05 | CWE-524 | apps/web/nuxt.config.ts:68-76 | networkOnly pour endpoints authentifiés, ou TTL à 0 |
| M6 | CORS sans restriction des méthodes HTTP | A05 | CWE-942 | apps/api/src/main.ts:18-21 | Ajouter methods: ['GET', 'POST', 'PATCH', 'DELETE'] |
| M7 | Absence de Permissions-Policy | A05 | CWE-16 | nuxt.config.ts | Permissions-Policy: camera=(), microphone=(), geolocation=() |
| M8 | @nestjs/core : neutralisation impropre caractères spéciaux | A06 | CWE-116 | node_modules/@nestjs/core | Migration NestJS v11 |
BASSE / Informationnel
| # | Observation | OWASP | Recommandation |
|---|---|---|---|
| L1 | devalue (Nuxt indirect) : DoS via sparse array | A06 | Résolu par Nuxt >=3.21.6 |
| L2 | file-type : DoS infinite loop / ZIP bomb | A06 | resolutions: { "file-type": ">=21.3.2" } |
| L3 | JWT HS256 (symétrique) | A07 | Acceptable si secret fort, RS256/ES256 à considérer à terme |
| L4 | Logging minimal sur les échecs auth | A09 | Ajouter logs structurés sur UnauthorizedException (pino est déjà là) |
| L5 | lodash : Prototype Pollution | A06 | Résolu par resolutions: { "lodash": ">=4.18.0" } |
Dépendances et CVE
yarn audit : 25 vulnérabilités sur 1126 packages — 1 Critical · 13 High · 8 Moderate · 3 Low
| Sévérité | Package | Version actuelle | Version corrigée | Advisory |
|---|---|---|---|---|
| CRITICAL | @fastify/middie | 8.3.3 | >=9.3.2 | #1116760 — Middleware auth bypass child plugin scopes |
| HIGH | @nestjs/platform-fastify | 10.4.22 | >=11.1.14 | #1113662 — URL Encoding Middleware Bypass |
| HIGH | @nestjs/platform-fastify | 10.4.22 | >=11.1.16 | #1115135 — HEAD Request Middleware Bypass |
| HIGH | @fastify/middie | 8.3.3 | >=9.1.0 | #1112322 — Path Bypass |
| HIGH | @fastify/middie | 8.3.3 | >=9.2.0 | #1114184 — Improper Path Normalization |
| HIGH | @fastify/middie | 8.3.3 | >=9.3.2 | #1116761 — Middleware bypass via ignoreDuplicateSlashes |
| HIGH | fastify | 4.28.1 | >=5.7.2 | #1112877 — Content-Type tab character bypass |
| HIGH | fast-uri | ❤️.1.1 | >=3.1.1 | #1117870 — Path traversal via percent-encoded |
| HIGH | fast-uri | ❤️.1.2 | >=3.1.2 | #1117884 — Host confusion via percent-encoded |
| HIGH | lodash | <4.18.0 | >=4.18.0 | #1115806 — Code Injection via _.template |
| HIGH | devalue | <5.8.1 | >=5.8.1 | #1119005 — DoS via sparse array deserialization |
| MODERATE | @nestjs/platform-fastify | 10.4.22 | >=11.1.11 | #1111721 — URL Encoding TOCTOU |
| MODERATE | @nestjs/core | <11.1.18 | >=11.1.18 | #1117063 — Improper element neutralization |
| MODERATE | fastify | 4.28.1 | >=5.8.3 | #1115367 — request.protocol/host spoofable |
| MODERATE | lodash | <4.17.23 | >=4.17.23 | #1112455 — Prototype Pollution .unset/.omit |
| MODERATE | lodash | <4.18.0 | >=4.18.0 | #1115810 — Prototype Pollution via array path |
| MODERATE | file-type | <21.3.1 | >=21.3.1 | #1114301 — Infinite loop ASF parser |
| MODERATE | file-type | <21.3.2 | >=21.3.2 | #1114726 — ZIP Decompression Bomb DoS |
| MODERATE | nuxt | 3.20.2 | >=3.21.6 | #1119359 — Reflected XSS in navigateTo() |
| LOW | fastify | 4.28.1 | >=5.7.3 | #1112876 — DoS unbounded memory allocation |
| LOW | nuxt | 3.20.2 | >=3.21.6 | #1119395 — Cache poisoning __nuxt_island |
| LOW | @nuxt/nitro-server | 3.20.2 | >=3.21.6 | #1119393 — Cache poisoning |
Points positifs
- Multi-tenancy robuste (A01) : Middleware Prisma via
AsyncLocalStorage— isolation tenant centralisée, aucun filtre manuel dispersé dans les services. - Validation exemplaire (A03) :
ValidationPipeglobal avecwhitelist: true, forbidNonWhitelisted: true, DTOs bien typés, whitelist de colonnes triables (SORTABLE_FIELDS). - Pas de secrets commités (A02) :
git log --all -p -- '*.env'vide..gitignorecorrect. - JWT correctement validé côté serveur (A07) : Signature HS256, claims
app/env, algorithme fixé,APP_GUARDglobal. - dev-login bloqué en production (A07) :
NODE_ENV=productiondans le Dockerfile, 403 retourné. - CORS restrictif (A05) : Origin limité à
CORS_ORIGIN, headers restreints àcontent-type, x-app-access-token. - Logout complet (A07) : Redirige vers
/logoutdu portail SSO pour invalider la session côté provider. - Cache LRU avec hash SHA-256 du token : JWT jamais stocké en clair en mémoire serveur.
- Zéro SQL injection possible (A03) : Prisma ORM +
Prisma.sqltemplate tags sur les$queryRaw. - robots.txt + noindex cohérents (A05) : Application B2B correctement protégée de l'indexation.
Score global
| Catégorie OWASP | Score | Justification |
|---|---|---|
| A01 - Broken Access Control | 7/10 | Multi-tenancy solide, open redirect à corriger dans callback.vue |
| A02 - Cryptographic Failures | 6/10 | JWT en localStorage, hash fragment token, HS256 acceptable |
| A03 - Injection | 9/10 | Prisma ORM, ValidationPipe global, aucune concaténation SQL |
| A04 - Insecure Design | 5/10 | Aucun rate limiting, pagination sans plafond max |
| A05 - Security Misconfiguration | 4/10 | Absence totale de security headers confirmée en prod Lighthouse |
| A06 - Vulnerable Components | 2/10 | 1 CVE critique, 13 HIGH actifs, NestJS et Nuxt en retard de versions majeures |
| A07 - Auth Failures | 6/10 | Bonne validation JWT, dev-login protégé, mais localStorage et pas de rate limiting auth |
| A08 - Data Integrity | 7/10 | Aucun webhook externe, pas de désérialisation non fiable |
| A09 - Logging & Monitoring | 4/10 | Logging minimal, aucun audit trail des échecs auth |
| A10 - SSRF | 8/10 | URL Console Admin vient de la config serveur uniquement, pas d'input utilisateur |
| Global | 5.8/10 | Bonnes fondations architecturales, mise à jour des dépendances et security headers urgents |
Prochaines étapes
[24h] Résolutions yarn pour CVE critiques/hauts
Ajouter dans package.json racine :
json
"resolutions": {
"@fastify/middie": ">=9.3.2",
"fast-uri": ">=3.1.2",
"lodash": ">=4.18.0",
"file-type": ">=21.3.2"
}Puis relancer yarn install.
[48h] Fix open redirect dans callback.vue:35
typescript
// apps/web/pages/login/callback.vue
const rawRedirect = route.query.redirect as string | undefined
const redirect = rawRedirect?.startsWith('/') ? rawRedirect : '/contacts'
await navigateTo(redirect)[48h] Mise à jour Nuxt >=3.21.6
bash
yarn workspace @heriade/web upgrade nuxt@>=3.21.6Corrige : XSS navigateTo(), cache poisoning __nuxt_island, et embarque devalue >=5.8.1.
[Semaine 1] Security headers dans nuxt.config.ts
typescript
// nuxt.config.ts
nitro: {
routeRules: {
'/**': {
headers: {
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains',
'Content-Security-Policy': "default-src 'self'; script-src 'self'; connect-src 'self'; frame-ancestors 'none'",
}
}
}
}Et côté API, installer @fastify/helmet :
bash
yarn workspace @heriade/api add @fastify/helmettypescript
// apps/api/src/main.ts
await app.register(helmet)[Semaine 1] Rate limiting
bash
yarn workspace @heriade/api add @nestjs/throttlertypescript
// apps/api/src/app.module.ts
ThrottlerModule.forRoot([{ ttl: 60_000, limit: 100 }])
// Sur les endpoints auth
@Throttle({ default: { limit: 10, ttl: 60_000 } })[Semaine 1] Plafond sur la pagination
Ajouter @Max(100) sur tous les DTOs de pagination :
typescript
// apps/api/src/contacts/dto/contact-query.dto.ts
@IsOptional() @Type(() => Number) @IsInt() @Min(1) @Max(100)
limit?: number = 25Idem dans company-query.dto.ts et reminder-query.dto.ts.
[Semaine 2] Migration NestJS v10 → v11
Corrige 5 vulnérabilités HIGH/MODERATE sur @nestjs/platform-fastify et @nestjs/core. Guide officiel : https://docs.nestjs.com/migration-guide
[Sprint suivant] Migration JWT localStorage → cookie HttpOnly
Évaluer en coordination avec le portail SSO Heriade pour adopter une transmission du token via cookie HttpOnly+Secure+SameSite=Lax plutôt que hash fragment + localStorage.
[Sprint suivant] Logging structuré sur les événements auth
Pino est déjà dans les dépendances. Ajouter des logs structurés sur les UnauthorizedException (tentatives avec token invalide, tokens expirés, accès refusés) pour constituer un audit trail.
Avertissement
Ce rapport a été généré par un outil automatisé (Claude Code) et constitue une aide à l'audit, pas un audit professionnel certifié. Les findings doivent être relus et validés par un développeur ou un expert sécurité avant toute action corrective. Certains résultats peuvent être des faux positifs ou ne pas s'appliquer à votre contexte spécifique. Ne transmettez pas ce rapport tel quel à une équipe technique sans l'avoir préalablement vérifié.
