Ce document décrit les mesures de sécurité implémentées dans ArenaSales pour le POC, ainsi que les améliorations prévues pour la production.
Type: Token-based custom (pas JWT pour simplicité POC)
Flux d'authentification:
1. User → POST /api/auth/login {email, password}
2. Backend vérifie hash(password)
3. Si OK → Génère token aléatoire (32 bytes)
4. Stocke token dans table sessions (expire 24h)
5. Retourne token au client
6. Client stocke token dans localStorage
7. Chaque requête API → Header: Authorization: Bearer {token}
8. Backend valide token + expiration
POC (Actuel):
# Hash simple avec SHA256 + salt statique
salt = "arenasales_salt_2024"
password_hash = hashlib.sha256(f"{password}{salt}".encode()).hexdigest()- Salt statique (même pour tous les users)
- SHA256 trop rapide (vulnérable brute-force)
Production (Recommandé):
# Utiliser bcrypt avec salt unique par user
import bcrypt
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))Génération:
import secrets
token = secrets.token_urlsafe(32) # 256 bits d'entropieExpiration: 24 heures (configurable)
Stockage:
- Backend: SQLite (table sessions)
- Frontend: localStorage (
⚠️ XSS risk - voir mitigations)
Révocation:
- Logout → DELETE session
- Expiration auto → Cron cleanup (à implémenter)
| Rôle | Permissions |
|---|---|
| user | Analyser sites, voir propres analyses, exporter propres données |
| admin | Toutes permissions user + exporter toutes données, gérer users (roadmap) |
Middleware de protection:
@login_required
def protected_route():
user = request.current_user # Injecté par le décorateur
# Business logic
@admin_required
def admin_only_route():
# Nécessite role='admin'Fichier .env (NON commité):
# Application
APP_ENV=development
SECRET_KEY=your-secret-key-here
PORT=5000
# Database
DB_URL=sqlite:///arenasales.db
# APIs externes (Phase 2)
OPENAI_API_KEY=sk-...
PAPPERS_API_KEY=...
APOLLO_API_KEY=...
# Session
SESSION_LIFETIME=86400 # 24h en secondesFichier .env.example (commité):
APP_ENV=development
SECRET_KEY=__TO_FILL__
PORT=5000
DB_URL=sqlite:///arenasales.db
SESSION_LIFETIME=86400❌ À éviter:
# MAUVAIS - secret hardcodé
API_KEY = "sk-1234567890abcdef"✅ Bonne pratique:
import os
API_KEY = os.environ.get('API_KEY')
if not API_KEY:
raise ValueError("API_KEY non définie")Protection:
.gitignorecontient.env- Scan pré-commit avec
git-secrets(recommandé) - GitHub secret scanning activé
Si leak accidentel:
- Révoquer immédiatement le secret
- Générer nouveau secret
- Nettoyer historique Git :
git filter-branch - Forcer push :
git push --force
Collectées:
- Emails utilisateurs (comptes)
- Noms utilisateurs
- Emails/téléphones scrapés (publics sur sites)
Traitement:
- ✅ Collecte minimale
- ✅ Consentement implicite (données publiques)
⚠️ Chiffrement: Non (POC), AES-256 (prod)⚠️ Anonymisation: Non implémentée
Conformité RGPD:
- Droit d'accès:
/api/auth/me - Droit de suppression: À implémenter (J+7)
- Droit de portabilité: Export CSV partiel
- DPO: Non requis (< 250 employés, pas de données sensibles)
En transit (POC):
⚠️ HTTP uniquement (local dev)- ✅ CORS configuré
En transit (Production):
- ✅ HTTPS obligatoire (Let's Encrypt)
- ✅ TLS 1.2+ minimum
- ✅ HSTS header
- ✅ Certificate pinning (mobile apps, roadmap)
Au repos:
- ❌ SQLite non chiffré (POC)
- ✅ Passwords hashés
- ✅ Tokens aléatoires
Au repos (Production recommandé):
- Chiffrement database: SQLCipher ou PostgreSQL + pgcrypto
- Chiffrement disque: LUKS (Linux) / BitLocker (Windows)
- Key management: AWS KMS / HashiCorp Vault
POC: Manuelles
Production:
- Backups quotidiens automatiques
- Rétention 30 jours
- Stockage S3 chiffré (AES-256)
- Test restoration mensuel
# Logs stdout basiques
print(f"🔍 Analyse de: {url}")
print(f"✅ Page récupérée")
print(f"❌ Erreur: {error}")- Pas de niveau (DEBUG/INFO/ERROR)
- Pas de rotation
- Pas de stockage persistant
import logging
# Configuration
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.RotatingFileHandler('app.log', maxBytes=10485760, backupCount=5)
]
)
logger = logging.getLogger(__name__)
# Usage
logger.info(f"User {user_id} analyzed {url}")
logger.error(f"Scraping failed: {error}", exc_info=True)Règles de journalisation:
- ❌ Jamais logger: Mots de passe, tokens, API keys
- ✅ Toujours logger: Erreurs, tentatives auth, actions admin
⚠️ Masquer PII: Emails →e***@domain.com
Rétention:
- Logs application: 30 jours
- Logs accès: 90 jours
- Logs sécurité: 1 an
Non implémenté (POC)
Production recommandé:
from flask_limiter import Limiter
limiter = Limiter(
app,
key_func=lambda: request.headers.get('Authorization', 'anonymous'),
default_limits=["100 per hour"]
)
@app.route('/api/analyze')
@limiter.limit("10 per minute")
@login_required
def analyze():
# Limité à 10 analyses/minute par userLimites suggérées:
- Auth endpoints: 5 req/min
- Analyse: 10 req/min
- Export: 2 req/min
- Autres: 100 req/hour
Configuration actuelle:
from flask_cors import CORS
CORS(app) # ⚠️ Trop permissif (POC)Production recommandé:
CORS(app, origins=[
"https://arenasales.com",
"https://app.arenasales.com"
],
methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization"],
max_age=3600)À ajouter (Production):
@app.after_request
def set_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Content-Security-Policy'] = "default-src 'self'"
return responseRisque: Moyen
Vecteur:
- Affichage données scrapées non sanitizées
- localStorage pour tokens (accessible JavaScript)
Mitigation POC:
- Pas de
innerHTMLavec données user - TailwindCSS escape automatique
Mitigation Production:
- DOMPurify pour sanitizer HTML
- Content-Security-Policy strict
- HTTPOnly cookies pour tokens (au lieu de localStorage)
Risque: Faible
Protection:
- Requêtes paramétrées uniquement :
cursor.execute('SELECT * FROM users WHERE email = ?', (email,)) - Jamais de f-strings SQL
- ORM recommandé (SQLAlchemy) pour production
Risque: Moyen (POC), Faible (Prod avec tokens)
Mitigation actuelle:
- Tokens dans headers (pas de cookies simples)
Mitigation Production:
- CSRF tokens pour formulaires
- SameSite cookies:
Strict - Double submit cookies
Risque: Haut
Problème: User peut scraper sites malveillants ou overload le serveur
Mitigation POC:
- Timeout 15s par requête
⚠️ Pas de rate limiting
Mitigation Production:
- Whitelist/blacklist domaines
- Rate limiting par user
- CAPTCHA si abuse détecté
- Sandbox scraping (conteneur isolé)
Risque: Moyen
Vecteur: Requêtes scraping massives
Mitigation:
- Rate limiting (production)
- Queue système (Celery + Redis)
- Auto-scaling cloud
- WAF (Web Application Firewall)
1. Détection:
- Monitoring Sentry/Datadog
- Alertes erreurs > 10/min
- Alerte tentatives login échouées > 20/min
2. Containment:
- Bloquer IP/token attaquant
- Désactiver endpoint compromis
- Backups immédiats
3. Éradication:
- Identifier vulnérabilité
- Patcher code
- Révoquer tokens/sessions
4. Recovery:
- Déployer patch
- Restore données si nécessaire
- Monitoring accru 48h
5. Post-mortem:
- Documenter incident
- Améliorer détection
- Communiquer si breach données
POC: Aucun test automatisé
Production recommandé:
- SAST: Bandit (Python), SonarQube
- DAST: OWASP ZAP
- Pentest: Annuel par équipe externe
- Bug bounty: HackerOne (après 1 an)
RGPD:
- ✅ Données minimales
⚠️ Registre traitement (à créer)⚠️ DPO requis si > 250 employees
ISO 27001: Non requis (POC)
SOC 2: Non applicable (pas de clients enterprise encore)
- Passwords hashés
- Tokens aléatoires
- Expiration sessions 24h
- CORS configuré
- Requêtes SQL paramétrées
- Rate limiting
- HTTPS
- Logs structurés
- Headers sécurité
- HTTPS obligatoire
- Bcrypt pour passwords
- HTTPOnly cookies
- Rate limiting complet
- WAF (Cloudflare)
- Monitoring sécurité
- Backups auto
- Pentest externe
- RGPD complet
- Encryption at rest
- Jamais commit secrets
- Toujours valider inputs
- Privilège minimum (least privilege)
- Fail secure (en cas d'erreur, refuser)
- Defense in depth (plusieurs couches)
- Pas de secrets hardcodés
- Inputs validés/sanitizés
- Erreurs gérées (pas de stack trace client)
- Logs sans PII
- Tests sécurité passent
Version: 1.0.0
Date: 2025-11-09
Prochaine revue: J+30 ou après incident