Sécurité by design : ces règles s'appliquent dès la première ligne de code. Vérifiées automatiquement par
/security-checket le pipeline CI.
# ❌ JAMAIS
DATABASE_URL = "postgresql://user:password@localhost/db"
API_KEY = "sk-prod-abc123"
# ✅ TOUJOURS
DATABASE_URL = os.environ["DATABASE_URL"] # fail-fast si absentDéfinissez les secrets comme requis sans valeur par défaut. L'application doit refuser de démarrer si un secret est absent plutôt que de tourner avec une valeur de dev en production.
# Python (pydantic-settings)
class Settings(BaseSettings):
DATABASE_URL: str # requis — pas de default
SECRET_KEY: str # requis — pas de default
REDIS_URL: str # requis — pas de default
DEBUG: bool = False # optionnel avec défaut sûr
# Node.js
const DATABASE_URL = process.env.DATABASE_URL
if (!DATABASE_URL) throw new Error('DATABASE_URL is required')# Mot de passe fort
openssl rand -base64 32
# Clé JWT (hex)
openssl rand -hex 32
# Clé de chiffrement AES-256
openssl rand -hex 32- Hashing : bcrypt (cost factor ≥ 12) ou argon2id
- Jamais MD5, SHA1, SHA256 seul pour les mots de passe
- Jamais stocker en clair, jamais logger
# Python — bcrypt direct (pas passlib)
import bcrypt
def hash_password(password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)).decode()
def verify_password(password: str, hashed: str) -> bool:
return bcrypt.checkpw(password.encode(), hashed.encode())- Access token : durée courte (15min à 1h)
- Refresh token : durée longue + révocable (7-30j, stocké en DB pour pouvoir invalider)
- Algorithme : HS256 minimum, RS256 si multi-services
- Jamais stocker dans
localStorage(XSS) — préférerhttpOnlycookie - Jamais mettre de données sensibles dans le payload (décodable côté client)
# Toujours vérifier côté serveur
@router.get("/admin/users")
async def list_users(
current_user: User = Depends(get_admin_user), # 403 si pas admin
):
...
# Toujours vérifier la propriété de la ressource
@router.get("/documents/{doc_id}")
async def get_document(
doc_id: str,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
doc = await db.get(Document, doc_id)
if not doc or doc.user_id != current_user.id: # ← vérification propriété
raise HTTPException(404) # 404, pas 403 — ne pas confirmer l'existenceConfigurer sur les endpoints sensibles :
- Authentification : 10 req/min par IP
- Reset de mot de passe : 5 req/min par IP
- Endpoints publics : 60 req/min par IP
Valider à la frontière — jamais faire confiance aux inputs utilisateur :
# Pydantic (Python)
class UserCreate(BaseModel):
email: EmailStr # validation format email
password: str = Field(min_length=8, max_length=128)
name: str = Field(min_length=1, max_length=100, pattern=r'^[\w\s-]+$')
# Zod (TypeScript)
const UserCreateSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(128),
name: z.string().min(1).max(100),
})# ❌ JAMAIS — injection SQL
query = f"SELECT * FROM users WHERE email = '{email}'"
# ✅ TOUJOURS — requêtes paramétrées
result = await db.execute(select(User).where(User.email == email))
# ✅ SQLAlchemy ORM (paramétré automatiquement)
user = await db.scalar(select(User).where(User.id == user_id))# ❌ Jamais en production
allow_origins=["*"]
# ✅ Liste explicite
allow_origins=["https://monapp.fr", "https://www.monapp.fr"]Configurer via le reverse proxy (Nginx) ou le framework :
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "DENY";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Content-Security-Policy "default-src 'self'; script-src 'self'";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";# ❌ JAMAIS logger des données sensibles
logger.info(f"Login: email={email}, password={password}")
logger.debug(f"JWT token: {token}")
# ✅ Logger les événements sans les secrets
logger.info(f"Login success: user_id={user.id}")
logger.warning(f"Login failed: email={email[:3]}***")
logger.info(f"Token issued: user_id={user.id}, expires={exp}")- Tous les secrets sont dans des variables d'environnement
- Aucune valeur par défaut pour les secrets requis
- CORS configuré avec une liste explicite de domaines
- Rate limiting actif sur auth et endpoints publics
- Headers de sécurité configurés
- HTTPS uniquement (redirection HTTP → HTTPS)
- Logs sans données PII ni secrets
- Dépendances auditées (
npm audit/pip-audit) - Scan SAST dans la CI (Semgrep, Bandit, CodeQL...)