Skip to content

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éOWASPCWEFichier:ligneFix recommandé
C1@fastify/middie : bypass d'authentification middleware dans les child plugin scopesA06, A07CWE-287node_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éOWASPCWEFichier:ligneFix recommandé
H1Absence totale de Content Security Policy (confirmé prod Lighthouse)A05CWE-79nuxt.config.ts (aucune config headers)Ajouter CSP dans nitro.routeRules
H2Absence de HSTS (confirmé prod Lighthouse)A05CWE-319nuxt.config.tsStrict-Transport-Security: max-age=63072000; includeSubDomains
H3@nestjs/platform-fastify : URL Encoding Middleware BypassA06, A01CWE-639node_modules/@nestjs/platform-fastify v10.4.22Migration NestJS v11
H4Fastify : Content-Type tab character bypassA06, A03CWE-436node_modules/fastify v4.28.1Résolution fastify >= 5.7.2 (breaking change)
H5fast-uri : path traversal + host confusion (x4 CVE)A06, A10CWE-22, CWE-918node_modules/fast-uriresolutions: { "fast-uri": ">=3.1.2" }
H6JWT stocké en localStorageA07, A02CWE-922apps/web/stores/auth.ts:41Migrer vers cookie HttpOnly+Secure+SameSite=Lax
H7Open Redirect non validé dans callback SSOA01CWE-601apps/web/pages/login/callback.vue:35-36Valider que redirect commence par / (voir fix ci-dessous)
H8Absence totale de rate limitingA04, A07CWE-307apps/api/src/main.tsInstaller @nestjs/throttler, 100 req/min global, 10/min sur auth
H9lodash : Code Injection via _.templateA06, A03CWE-94node_modules/lodashresolutions: { "lodash": ">=4.18.0" }
H10Pagination sans plafond ?limit=999999 → DoSA04CWE-770DTOs pagination (contacts, companies, reminders)Ajouter @Max(100) sur tous les DTOs

MOYENNE — À planifier

#VulnérabilitéOWASPCWEFichier:ligneFix recommandé
M1Absence X-Frame-Options, X-Content-Type-Options, Referrer-PolicyA05CWE-1021nuxt.config.ts, apps/api/src/main.tsHeaders globaux Nuxt + @fastify/helmet
M2Nuxt 3.20.2 : XSS reflétée via navigateTo()A06, A03CWE-79node_modules/nuxt v3.20.2yarn workspace @heriade/web upgrade nuxt@>=3.21.6
M3Token JWT visible dans l'historique navigateur via hash fragmentA07, A02CWE-598apps/web/pages/login/callback.vue:23,33Travailler avec le portail SSO pour adopter postMessage ou cookie
M4dev-login conditionné uniquement sur NODE_ENVA07CWE-798apps/api/src/auth/auth.controller.ts:38Ajouter variable opt-in DEV_LOGIN_ENABLED=true explicite
M5Cache service worker des données CRM pendant 24hA05CWE-524apps/web/nuxt.config.ts:68-76networkOnly pour endpoints authentifiés, ou TTL à 0
M6CORS sans restriction des méthodes HTTPA05CWE-942apps/api/src/main.ts:18-21Ajouter methods: ['GET', 'POST', 'PATCH', 'DELETE']
M7Absence de Permissions-PolicyA05CWE-16nuxt.config.tsPermissions-Policy: camera=(), microphone=(), geolocation=()
M8@nestjs/core : neutralisation impropre caractères spéciauxA06CWE-116node_modules/@nestjs/coreMigration NestJS v11

BASSE / Informationnel

#ObservationOWASPRecommandation
L1devalue (Nuxt indirect) : DoS via sparse arrayA06Résolu par Nuxt >=3.21.6
L2file-type : DoS infinite loop / ZIP bombA06resolutions: { "file-type": ">=21.3.2" }
L3JWT HS256 (symétrique)A07Acceptable si secret fort, RS256/ES256 à considérer à terme
L4Logging minimal sur les échecs authA09Ajouter logs structurés sur UnauthorizedException (pino est déjà là)
L5lodash : Prototype PollutionA06Ré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éPackageVersion actuelleVersion corrigéeAdvisory
CRITICAL@fastify/middie8.3.3>=9.3.2#1116760 — Middleware auth bypass child plugin scopes
HIGH@nestjs/platform-fastify10.4.22>=11.1.14#1113662 — URL Encoding Middleware Bypass
HIGH@nestjs/platform-fastify10.4.22>=11.1.16#1115135 — HEAD Request Middleware Bypass
HIGH@fastify/middie8.3.3>=9.1.0#1112322 — Path Bypass
HIGH@fastify/middie8.3.3>=9.2.0#1114184 — Improper Path Normalization
HIGH@fastify/middie8.3.3>=9.3.2#1116761 — Middleware bypass via ignoreDuplicateSlashes
HIGHfastify4.28.1>=5.7.2#1112877 — Content-Type tab character bypass
HIGHfast-uri❤️.1.1>=3.1.1#1117870 — Path traversal via percent-encoded
HIGHfast-uri❤️.1.2>=3.1.2#1117884 — Host confusion via percent-encoded
HIGHlodash<4.18.0>=4.18.0#1115806 — Code Injection via _.template
HIGHdevalue<5.8.1>=5.8.1#1119005 — DoS via sparse array deserialization
MODERATE@nestjs/platform-fastify10.4.22>=11.1.11#1111721 — URL Encoding TOCTOU
MODERATE@nestjs/core<11.1.18>=11.1.18#1117063 — Improper element neutralization
MODERATEfastify4.28.1>=5.8.3#1115367 — request.protocol/host spoofable
MODERATElodash<4.17.23>=4.17.23#1112455 — Prototype Pollution .unset/.omit
MODERATElodash<4.18.0>=4.18.0#1115810 — Prototype Pollution via array path
MODERATEfile-type<21.3.1>=21.3.1#1114301 — Infinite loop ASF parser
MODERATEfile-type<21.3.2>=21.3.2#1114726 — ZIP Decompression Bomb DoS
MODERATEnuxt3.20.2>=3.21.6#1119359 — Reflected XSS in navigateTo()
LOWfastify4.28.1>=5.7.3#1112876 — DoS unbounded memory allocation
LOWnuxt3.20.2>=3.21.6#1119395 — Cache poisoning __nuxt_island
LOW@nuxt/nitro-server3.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) : ValidationPipe global avec whitelist: 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. .gitignore correct.
  • JWT correctement validé côté serveur (A07) : Signature HS256, claims app/env, algorithme fixé, APP_GUARD global.
  • dev-login bloqué en production (A07) : NODE_ENV=production dans le Dockerfile, 403 retourné.
  • CORS restrictif (A05) : Origin limité à CORS_ORIGIN, headers restreints à content-type, x-app-access-token.
  • Logout complet (A07) : Redirige vers /logout du 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.sql template tags sur les $queryRaw.
  • robots.txt + noindex cohérents (A05) : Application B2B correctement protégée de l'indexation.

Score global

Catégorie OWASPScoreJustification
A01 - Broken Access Control7/10Multi-tenancy solide, open redirect à corriger dans callback.vue
A02 - Cryptographic Failures6/10JWT en localStorage, hash fragment token, HS256 acceptable
A03 - Injection9/10Prisma ORM, ValidationPipe global, aucune concaténation SQL
A04 - Insecure Design5/10Aucun rate limiting, pagination sans plafond max
A05 - Security Misconfiguration4/10Absence totale de security headers confirmée en prod Lighthouse
A06 - Vulnerable Components2/101 CVE critique, 13 HIGH actifs, NestJS et Nuxt en retard de versions majeures
A07 - Auth Failures6/10Bonne validation JWT, dev-login protégé, mais localStorage et pas de rate limiting auth
A08 - Data Integrity7/10Aucun webhook externe, pas de désérialisation non fiable
A09 - Logging & Monitoring4/10Logging minimal, aucun audit trail des échecs auth
A10 - SSRF8/10URL Console Admin vient de la config serveur uniquement, pas d'input utilisateur
Global5.8/10Bonnes 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.6

Corrige : 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/helmet
typescript
// apps/api/src/main.ts
await app.register(helmet)

[Semaine 1] Rate limiting

bash
yarn workspace @heriade/api add @nestjs/throttler
typescript
// 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 = 25

Idem 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

É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é.