PD-19 — Decomposition des tâches agents¶
Contexte¶
Ce document décompose le plan d'implémentation PD-19 en tâches assignées aux agents spécialisés pour l'étape 6 du workflow de gouvernance.
Documents de référence : - PD-19-specification.md — Spécification fonctionnelle - PD-19-tests.md — Scénarios de tests contractuels - PD-19-plan.md — Plan d'implémentation - PD-19-code-contracts.yaml — Contrats d'interface
Architecture existante¶
| Composant | Fichier | État |
|---|---|---|
| RateLimitService | src/modules/auth/services/rate-limit.service.ts | Existe (registration, login, profile) |
| RateLimitGuard | src/modules/auth/guards/rate-limit.guard.ts | Existe |
| ConfigSchema | src/config/config.schema.ts | Existe (à étendre) |
| main.ts | src/main.ts | Helmet/CORS basiques à remplacer |
| Middleware directory | src/common/middleware/ | N'existe pas (à créer) |
Tâches agent-developer¶
TASK-DEV-01 — Configuration Security Headers¶
Fichier : src/config/security-headers.config.ts (CRÉER)
Entrées : - PD-19-code-contracts.yaml section security_headers_config - INV-01, INV-02
Sortie attendue :
// Configuration du socle de security headers par environnement
export const securityHeadersConfig = registerAs('securityHeaders', () => ({
xContentTypeOptions: 'nosniff',
xFrameOptions: 'DENY',
contentSecurityPolicy: "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",
referrerPolicy: 'no-referrer',
permissionsPolicy: 'geolocation=(), camera=(), microphone=(), payment=(), usb=()',
strictTransportSecurity: {
maxAge: /* 31536000 prod/test, 300 dev */,
includeSubDomains: true,
enabled: /* true sauf dev sans HTTPS */,
},
cacheControl: {
sensitiveRoutes: ['auth/*', 'users/me'],
sensitiveStatuses: [401, 403],
value: 'no-store',
},
removeHeaders: ['Server', 'X-Powered-By'],
}));
TASK-DEV-02 — Configuration CORS¶
Fichier : src/config/cors.config.ts (CRÉER)
Entrées : - PD-19-code-contracts.yaml section cors_config - INV-03, INV-04, INV-05
Sortie attendue :
export const corsConfig = registerAs('cors', () => ({
origins: {
production: ['https://app.probatiovault.com', 'https://www.probatiovault.com'],
test: ['https://app-test.probatiovault.com', 'https://site-test.probatiovault.com'],
development: ['http://localhost:3000', 'http://localhost:8080'],
},
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Authorization', 'Content-Type', 'Accept', 'Origin', 'X-Requested-With', 'X-Correlation-Id'],
exposedHeaders: ['X-Correlation-Id', 'Retry-After'],
credentials: true,
maxAge: { production: 600, test: 300, development: 60 },
}));
TASK-DEV-03 — Extension rate-limit.config.ts¶
Fichier : src/config/rate-limit.config.ts (MODIFIER)
Entrées : - PD-19-code-contracts.yaml section rate_limit_config - INV-06
Modification : Ajouter la section global :
global: {
max: Number.parseInt(process.env.RATE_LIMIT_GLOBAL_MAX || '100', 10),
windowSeconds: Number.parseInt(process.env.RATE_LIMIT_GLOBAL_WINDOW_S || '60', 10),
keyPrefix: 'rl:global:',
},
trustedProxies: process.env.TRUSTED_PROXIES || 'loopback',
TASK-DEV-04 — Extension config.schema.ts¶
Fichier : src/config/config.schema.ts (MODIFIER)
Entrées : - Plan §3 Phase 1 - ERR-05 (fail-fast)
Modifications :
// Section CORS (nouvelle)
CORS_ORIGINS: Joi.string()
.required()
.description('CORS allowed origins (comma-separated)'),
// Section Rate Limiting Global (nouvelle)
RATE_LIMIT_GLOBAL_MAX: Joi.number()
.integer()
.min(1)
.default(trackedDefault('RATE_LIMIT_GLOBAL_MAX', 100))
.description('Global rate limit max requests'),
RATE_LIMIT_GLOBAL_WINDOW_S: Joi.number()
.integer()
.min(1)
.default(trackedDefault('RATE_LIMIT_GLOBAL_WINDOW_S', 60))
.description('Global rate limit window in seconds'),
TRUSTED_PROXIES: Joi.string()
.default(trackedDefault('TRUSTED_PROXIES', 'loopback'))
.description('Trusted proxies for X-Forwarded-For resolution'),
TASK-DEV-05 — SecurityHeadersMiddleware¶
Fichier : src/common/middleware/security-headers.middleware.ts (CRÉER)
Entrées : - PD-19-code-contracts.yaml section security_headers_middleware - INV-01, INV-02, INV-11
Comportement requis : 1. Appliquer tous les headers contractuels via res.setHeader() 2. Supprimer Server et X-Powered-By via res.removeHeader() 3. Utiliser res.on('finish', ...) pour garantir Cache-Control: no-store sur : - Routes /auth/* et /users/me - Statuts 401 et 403 4. S'exécuter sur TOUTE réponse (y compris erreurs)
Contrat :
@Injectable()
export class SecurityHeadersMiddleware implements NestMiddleware {
constructor(private readonly configService: ConfigService) {}
use(req: Request, res: Response, next: NextFunction): void;
}
TASK-DEV-06 — CorsMiddleware¶
Fichier : src/common/middleware/cors.middleware.ts (CRÉER)
Entrées : - PD-19-code-contracts.yaml section cors_middleware - INV-03, INV-04, INV-05, INV-08, INV-09
Comportement requis : 1. Extraire Origin de la requête 2. Valider contre la liste d'origines de l'environnement courant 3. Si autorisé : ajouter tous les headers CORS contractuels 4. Si non autorisé : n'ajouter AUCUN header CORS 5. Preflight (OPTIONS) : répondre directement avec 204 + headers CORS 6. Journaliser les refus (événement CORS_REJECTED)
Contrat :
@Injectable()
export class CorsMiddleware implements NestMiddleware {
constructor(
private readonly configService: ConfigService,
private readonly logger: Logger,
) {}
use(req: Request, res: Response, next: NextFunction): void;
}
TASK-DEV-07 — GlobalRateLimitMiddleware¶
Fichier : src/common/middleware/global-rate-limit.middleware.ts (CRÉER)
Entrées : - PD-19-code-contracts.yaml section global_rate_limit_middleware - INV-06, INV-07, INV-08, INV-09
Comportement requis : 1. Résoudre IP client (X-Forwarded-For si proxy de confiance) 2. Appeler RateLimitService.checkGlobalLimit(ip) 3. Si allowed: true : next() 4. Si allowed: false : répondre 429 avec : - Header Retry-After: <seconds> - Corps JSON : {"error":"rate_limited","message":"Too many requests","retryAfter":<seconds>} 5. Journaliser les dépassements (événement RATE_LIMIT_EXCEEDED)
Contrat :
@Injectable()
export class GlobalRateLimitMiddleware implements NestMiddleware {
constructor(
private readonly rateLimitService: RateLimitService,
private readonly configService: ConfigService,
private readonly logger: Logger,
) {}
use(req: Request, res: Response, next: NextFunction): void;
}
TASK-DEV-08 — Extension RateLimitService¶
Fichier : src/modules/auth/services/rate-limit.service.ts (MODIFIER)
Entrées : - PD-19-code-contracts.yaml section rate_limit_service - INV-06
Modification : Ajouter méthode checkGlobalLimit() :
private readonly KEY_PREFIX_GLOBAL = 'rl:global:';
private readonly globalMax: number;
private readonly globalWindowSeconds: number;
async checkGlobalLimit(hashedIp: string): Promise<RateLimitResult> {
const key = this.KEY_PREFIX_GLOBAL + hashedIp;
// INCR atomique + TTL
// Retourne { allowed, count, limit, retryAfter?, windowResetAt? }
}
async resetGlobal(hashedIp: string): Promise<void>;
getGlobalConfiguration(): { max: number; windowSeconds: number };
TASK-DEV-09 — Modification main.ts¶
Fichier : src/main.ts (MODIFIER)
Entrées : - Plan §3 Phase 1-4 - ERR-05 (fail-fast)
Modifications : 1. Fail-fast validation : Ajouter validation de la configuration sécurité avant démarrage 2. Supprimer app.use(helmet()) basique 3. Supprimer app.enableCors() basique 4. Appliquer les middlewares dans l'ordre : - SecurityHeadersMiddleware (position 1) - CorsMiddleware (position 2) - GlobalRateLimitMiddleware (position 3)
// Fail-fast si config invalide
const configService = app.get(ConfigService);
validateSecurityConfig(configService);
// Middlewares de sécurité (ordre critique)
app.use(SecurityHeadersMiddleware);
// Note: CorsMiddleware et GlobalRateLimitMiddleware via AppModule.configure()
TASK-DEV-10 — AppModule middleware configuration¶
Fichier : src/app.module.ts (MODIFIER)
Entrées : - Plan §4 (ordre des middlewares)
Modification : Implémenter configure(consumer: MiddlewareConsumer) :
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(SecurityHeadersMiddleware)
.forRoutes('*')
.apply(CorsMiddleware)
.forRoutes('*')
.apply(GlobalRateLimitMiddleware)
.forRoutes('*');
}
Tâches agent-qa-unit-integration¶
TASK-QA-01 — Tests unitaires SecurityHeadersMiddleware¶
Fichier : src/common/middleware/__tests__/security-headers.middleware.spec.ts (CRÉER)
Couverture : - TC-NOM-01 : Headers de sécurité (socle) - TC-NOM-02 : Headers conditionnels (HSTS / Cache-Control) - INV-01, INV-02, INV-11
Assertions : - X-Content-Type-Options === 'nosniff' - X-Frame-Options === 'DENY' - Content-Security-Policy présent et conforme - Server et X-Powered-By absents - Cache-Control: no-store sur routes sensibles et statuts 401/403
TASK-QA-02 — Tests unitaires CorsMiddleware¶
Fichier : src/common/middleware/__tests__/cors.middleware.spec.ts (CRÉER)
Couverture : - TC-NOM-03 : Origine autorisée par environnement - TC-NOM-04 : Preflight CORS contractualisé - TC-ERR-01 : Origine non autorisée
Assertions : - Origine autorisée → headers CORS présents - Origine non autorisée → aucun header CORS - Preflight → 204 + Access-Control-Max-Age correct
TASK-QA-03 — Tests unitaires GlobalRateLimitMiddleware¶
Fichier : src/common/middleware/__tests__/global-rate-limit.middleware.spec.ts (CRÉER)
Couverture : - TC-NOM-07 : Limitation sous seuil - TC-NOM-08 : Dépassement de seuil (429) - TC-ERR-03 : Rate limiting multi-IP - TC-ERR-04 : Corps JSON contractuel
Assertions : - 100 requêtes/60s → toutes passent - 101e requête → 429 avec Retry-After et corps JSON
TASK-QA-04 — Tests E2E security-headers¶
Fichier : test/e2e/security-headers.e2e-spec.ts (CRÉER)
Couverture : - TC-NOM-05 : Observabilité middleware global - TC-ERR-02 : Couverture erreurs techniques (404, 405, 500) - DIV-01-v2 : Headers sur 429 et refus CORS
Scénarios : - Requête 200 → socle headers présent - Requête 404 → socle headers présent - Requête 429 → socle headers présent - Route /auth/* → Cache-Control: no-store
TASK-QA-05 — Tests E2E CORS¶
Fichier : test/e2e/cors.e2e-spec.ts (CRÉER)
Couverture : - TC-NOM-03, TC-NOM-04, TC-ERR-01
Scénarios : - Origine localhost:3000 en dev → CORS accepté - Origine malicious.local → CORS refusé - Preflight OPTIONS → 204 + headers complets
TASK-QA-06 — Tests E2E rate-limit-global¶
Fichier : test/e2e/rate-limit-global.e2e-spec.ts (CRÉER)
Couverture : - TC-NOM-07, TC-NOM-08, TC-ERR-03, TC-ERR-04
Scénarios : - Rafale 100 requêtes → OK - Rafale 101 requêtes → 429 sur la 101e - Deux IPs distinctes → isolation
TASK-QA-07 — Test d'intégration résolution IP proxy¶
Fichier : test/integration/rate-limit-proxy.integration-spec.ts (CRÉER)
Couverture : - DIV-02-v2 : Résolution IP via chaîne de proxy de confiance
Scénarios : - X-Forwarded-For avec proxy de confiance → IP extraite correctement - X-Forwarded-For forgé (hors trust chain) → IP source directe utilisée - Absence de X-Forwarded-For → IP source directe utilisée
TASK-QA-08 — Tests RateLimitService.checkGlobalLimit()¶
Fichier : src/modules/auth/services/__tests__/rate-limit.service.global.spec.ts (CRÉER)
Couverture : - Méthode checkGlobalLimit() - Compteur Redis atomique - TTL correct (60s)
Ordre d'exécution¶
Phase 1 (Configuration)
├── TASK-DEV-01 (security-headers.config.ts)
├── TASK-DEV-02 (cors.config.ts)
├── TASK-DEV-03 (rate-limit.config.ts)
└── TASK-DEV-04 (config.schema.ts)
Phase 2 (Middlewares)
├── TASK-DEV-05 (SecurityHeadersMiddleware)
├── TASK-DEV-06 (CorsMiddleware)
└── TASK-DEV-07 (GlobalRateLimitMiddleware)
Phase 3 (Services)
└── TASK-DEV-08 (RateLimitService.checkGlobalLimit)
Phase 4 (Bootstrap)
├── TASK-DEV-09 (main.ts)
└── TASK-DEV-10 (app.module.ts)
Phase 5 (Tests)
├── TASK-QA-01 (tests unitaires security-headers)
├── TASK-QA-02 (tests unitaires cors)
├── TASK-QA-03 (tests unitaires rate-limit global)
├── TASK-QA-04 (tests E2E security-headers)
├── TASK-QA-05 (tests E2E cors)
├── TASK-QA-06 (tests E2E rate-limit-global)
├── TASK-QA-07 (tests intégration proxy)
└── TASK-QA-08 (tests RateLimitService)
Estimation¶
| Agent | Tâches | Effort estimé |
|---|---|---|
| agent-developer | TASK-DEV-01 à TASK-DEV-10 | 4.5 jours |
| agent-qa-unit-integration | TASK-QA-01 à TASK-QA-08 | 1.5 jours |
| Total | 18 tâches | 6 jours |
Document produit par le workflow de gouvernance IA — Étape 6a Date : 2026-02-09