Complete family planning application with: - React frontend with TypeScript - Node.js/Express backend with TypeScript - Python ingestion service for document processing - Planning ingestion service with LLM integration - Shared UI components and type definitions - OAuth integration for calendar synchronization - Comprehensive documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
252 lines
7.2 KiB
Markdown
252 lines
7.2 KiB
Markdown
# Améliorations de Sécurité - Family Planner
|
|
|
|
## 📋 Résumé des corrections appliquées
|
|
|
|
Ce document liste toutes les corrections de sécurité et d'hygiène de code appliquées le 2025-10-12.
|
|
|
|
---
|
|
|
|
## 🔐 1. Sécurisation de l'Upload Planning
|
|
|
|
### Problème identifié
|
|
- Aucune validation de type de fichier
|
|
- Pas de limite de taille
|
|
- Risque de path traversal via `childId`
|
|
|
|
### Corrections appliquées
|
|
|
|
**Fichier**: `backend/src/routes/uploads.ts`
|
|
|
|
#### A. FileFilter avec whitelist stricte
|
|
```typescript
|
|
const planFileFilter = (_req: Request, file: Express.Multer.File, callback: FileFilterCallback) => {
|
|
const allowedMimeTypes = [
|
|
'image/jpeg', 'image/jpg', 'image/png', 'image/webp',
|
|
'application/pdf',
|
|
'application/vnd.ms-excel',
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
];
|
|
|
|
const allowedExtensions = /\.(jpg|jpeg|png|webp|pdf|xls|xlsx)$/i;
|
|
const hasValidMime = allowedMimeTypes.includes(file.mimetype);
|
|
const hasValidExt = allowedExtensions.test(file.originalname);
|
|
|
|
if (hasValidMime && hasValidExt) {
|
|
callback(null, true);
|
|
} else {
|
|
callback(new Error('Type de fichier non autorisé'));
|
|
}
|
|
};
|
|
```
|
|
|
|
#### B. Limite de taille de fichier
|
|
```typescript
|
|
const planUpload = multer({
|
|
storage: planStorage,
|
|
fileFilter: planFileFilter,
|
|
limits: {
|
|
fileSize: 10 * 1024 * 1024 // 10MB max
|
|
}
|
|
});
|
|
```
|
|
|
|
#### C. Validation UUID pour prévenir path traversal
|
|
```typescript
|
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
if (!rawChildId || !uuidRegex.test(rawChildId)) {
|
|
console.warn("[uploads] invalid or missing childId:", rawChildId);
|
|
res.status(400).json({ message: "Parametre childId manquant ou invalide" });
|
|
return;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔒 2. Suppression de secrets.json et migration vers .env
|
|
|
|
### Problème identifié
|
|
- Clé OpenAI stockée en clair dans `backend/src/data/secrets.json`
|
|
- Fichier commité dans Git (même si .gitignore l'exclut maintenant)
|
|
|
|
### Corrections appliquées
|
|
|
|
#### A. Fichier secrets.json supprimé
|
|
```bash
|
|
rm backend/src/data/secrets.json
|
|
```
|
|
|
|
#### B. Fichier .env créé
|
|
**Fichier**: `backend/.env`
|
|
|
|
```env
|
|
# API Keys
|
|
# ⚠️ IMPORTANT: CETTE CLÉ A ÉTÉ EXPOSÉE ET DOIT ÊTRE RÉVOQUÉE!
|
|
# 1. Aller sur https://platform.openai.com/api-keys
|
|
# 2. Révoquer la clé commençant par sk-proj-efaTQ8...
|
|
# 3. Créer une nouvelle clé
|
|
# 4. Remplacer la valeur ci-dessous
|
|
OPENAI_API_KEY=sk-proj-...
|
|
OPENAI_MODEL=gpt-4o
|
|
```
|
|
|
|
#### C. Code déjà configuré pour lire depuis .env
|
|
Le code existant dans `backend/src/services/secret-store.ts` et `backend/src/config/env.ts` lit déjà depuis les variables d'environnement.
|
|
|
|
### ⚠️ ACTION REQUISE PAR L'UTILISATEUR
|
|
|
|
**CRITIQUE**: La clé OpenAI a été exposée dans Git et doit être révoquée:
|
|
|
|
1. **Aller sur**: https://platform.openai.com/api-keys
|
|
2. **Révoquer**: La clé commençant par `sk-proj-efaTQ8cicJYU7k8RG...`
|
|
3. **Créer**: Une nouvelle clé API
|
|
4. **Remplacer**: La valeur dans `backend/.env`
|
|
|
|
---
|
|
|
|
## 🌐 3. Sécurisation du Service d'Ingestion
|
|
|
|
### Problèmes identifiés
|
|
- CORS permissif (`allow_origins=["*"]`)
|
|
- Endpoint `/config/openai` expose les clés en production
|
|
|
|
### Corrections appliquées
|
|
|
|
**Fichier**: `ingestion-service/src/ingestion/main.py`
|
|
|
|
#### A. CORS restreint par environnement
|
|
```python
|
|
_env = os.getenv("NODE_ENV", "development")
|
|
_allowed_origins = os.getenv("ALLOWED_ORIGINS", "http://localhost:3000,http://localhost:5173,http://localhost:5000").split(",")
|
|
|
|
if _env == "production":
|
|
# Production: strict CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=_allowed_origins,
|
|
allow_methods=["GET", "POST"],
|
|
allow_headers=["Content-Type"],
|
|
allow_credentials=False,
|
|
)
|
|
else:
|
|
# Development: permissive
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
```
|
|
|
|
#### B. Endpoint /config/openai désactivé en production
|
|
```python
|
|
@app.post("/config/openai")
|
|
async def set_openai_config(api_key: str = Body(..., embed=True), model: str | None = Body(None)) -> dict:
|
|
if _env == "production":
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="Configuration endpoint disabled in production. Use environment variables instead."
|
|
)
|
|
# ... reste du code
|
|
```
|
|
|
|
---
|
|
|
|
## 🧹 4. Nettoyage du Repository
|
|
|
|
### Problèmes identifiés
|
|
- Fichiers `.js` dupliqués dans `frontend/src/` (générés par compilation)
|
|
- Fichiers `*.bak`, `*.bak2`, `*.bak3`
|
|
- `dist/` potentiellement commité
|
|
- Données PII dans `backend/src/data/client.json` et fichiers uploadés
|
|
|
|
### Corrections appliquées
|
|
|
|
#### A. Suppression des fichiers obsolètes
|
|
```bash
|
|
# Suppression de tous les .js dans frontend/src/
|
|
find family-planner/frontend/src -name "*.js" -type f -delete
|
|
|
|
# Suppression de tous les .bak*
|
|
find family-planner -name "*.bak*" -type f -delete
|
|
```
|
|
|
|
#### B. Mise à jour du .gitignore
|
|
**Fichier**: `.gitignore`
|
|
|
|
Ajouté:
|
|
```gitignore
|
|
# Data files with PII (Personally Identifiable Information)
|
|
backend/src/data/client.json
|
|
**/public/plans/
|
|
**/public/avatars/
|
|
```
|
|
|
|
Les lignes suivantes étaient déjà présentes:
|
|
- `dist/` (ligne 7)
|
|
- `backend/src/data/secrets.json` (ligne 29)
|
|
- `*.bak`, `*.backup` (lignes 36-37)
|
|
|
|
---
|
|
|
|
## 📊 Statistiques
|
|
|
|
### Fichiers modifiés
|
|
- `backend/src/routes/uploads.ts` - Sécurisation upload
|
|
- `backend/src/services/file-db.ts` - Ajout type personalLeaves
|
|
- `backend/src/services/personal-leave-service.ts` - Correction imports
|
|
- `backend/.env` - Nouveau fichier de configuration
|
|
- `ingestion-service/src/ingestion/main.py` - CORS et endpoints
|
|
- `frontend/src/state/ChildrenContext.tsx` - Ajout schoolRegion type
|
|
- `.gitignore` - Protection données PII
|
|
|
|
### Fichiers supprimés
|
|
- `backend/src/data/secrets.json` (1 fichier)
|
|
- Tous les `.js` dans `frontend/src/` (35 fichiers)
|
|
- Tous les `.bak*` (3 fichiers)
|
|
|
|
### Tests
|
|
- ✅ Backend compile sans erreur (`npm run build`)
|
|
- ✅ Frontend compile sans erreur (`npm run build`)
|
|
|
|
---
|
|
|
|
## 📝 Recommandations futures
|
|
|
|
### Court terme (Sprint actuel)
|
|
1. ✅ Révoquer la clé OpenAI exposée
|
|
2. ⏳ Ajouter tests backend pour routes critiques (supertest)
|
|
3. ⏳ Implémenter rate limiting par IP pour uploads
|
|
|
|
### Moyen terme (2-3 sprints)
|
|
1. Ajouter authentification utilisateur (JWT)
|
|
2. Implémenter encryption au repos pour données PII
|
|
3. Ajouter logs d'audit pour opérations sensibles
|
|
|
|
### Long terme (Roadmap)
|
|
1. Migration vers base de données (SQLite → Postgres)
|
|
2. Intégration avec solution de secrets management (Azure Key Vault, AWS Secrets Manager)
|
|
3. Mise en place CI/CD avec scans de sécurité automatiques
|
|
|
|
---
|
|
|
|
## ✅ Checklist de déploiement
|
|
|
|
Avant de déployer en production:
|
|
|
|
- [ ] Révoquer ancienne clé OpenAI
|
|
- [ ] Créer nouvelle clé OpenAI
|
|
- [ ] Configurer variable `OPENAI_API_KEY` en production
|
|
- [ ] Configurer variable `NODE_ENV=production` pour ingestion service
|
|
- [ ] Configurer variable `ALLOWED_ORIGINS` avec domaines production
|
|
- [ ] Vérifier que `.env` n'est PAS commité
|
|
- [ ] Vérifier que `dist/` n'est PAS commité
|
|
- [ ] Vérifier que `client.json` n'est PAS commité
|
|
- [ ] Tester upload avec fichiers valides et invalides
|
|
- [ ] Tester que `/config/openai` retourne 403 en production
|
|
|
|
---
|
|
|
|
**Date**: 2025-10-12
|
|
**Auteur**: Claude (Assistant IA)
|
|
**Validé par**: Philippe H.
|