Comment sécuriser vos notebooks Python en production : 8 étapes
TL;DR
- •La CVE-2026-39987 (Marimo, CVSS 9.8) rappelle que les notebooks en production sont une surface d’attaque critique souvent négligée.
- •8 étapes concrètes : audit de surface, authentification forte, isolation réseau, conteneurisation, restrictions kernel, gestion des secrets, monitoring et plan de réponse.
- •Chaque étape inclut des commandes et configurations directement applicables pour Jupyter, JupyterHub et Marimo.
- •Approche defense-in-depth : aucune mesure isolée ne suffit, c’est la combinaison des 8 couches qui protège.
Les notebooks Python — Jupyter, JupyterLab, Marimo, Databricks — sont devenus incontournables pour les équipes data, les chercheurs en IA et les développeurs. Mais leur déploiement en production expose des vulnérabilités structurelles que beaucoup d’organisations sous-estiment. La récente CVE-2026-39987 affectant Marimo — une exécution de code à distance (RCE) non authentifiée avec un score CVSS de 9.8 — illustre brutalement ce risque.
Ce guide vous donne 8 étapes actionables pour sécuriser vos notebooks Python en production, de l’audit initial au monitoring continu. Chaque étape est illustrée par des configurations concrètes que vous pouvez appliquer dès aujourd’hui.
Avis d’expert
« Nous voyons encore trop de notebooks Jupyter exposés sans authentification sur des réseaux de production. C’est l’équivalent d’un terminal root ouvert sur Internet. La CVE-2026-39987 de Marimo n’est qu’un rappel parmi d’autres : si votre notebook peut exécuter du code, un attaquant aussi. » — Nicolas Garnier, Pentester Senior, WebGuard Agency
Surface d’attaque d’un notebook Python en production
Étape 1 — Auditer votre surface d’exposition notebook
Avant de durcir quoi que ce soit, vous devez savoir exactement ce qui est exposé. La plupart des organisations découvrent avec surprise que des instances Jupyter tournent sur des ports non standard, lancées par des data scientists sans en informer l’équipe infrastructure.
Scannez votre réseau interne. Recherchez les ports typiques des notebooks : 8888 (Jupyter par défaut), 8889, 2718 (Marimo), 8080 et tout port où un serveur HTTP répond avec des headers caractéristiques. Un simple scan nmap avec détection de services révèle souvent des instances oubliées.
# Scanner les ports notebook courants
nmap -sV -p 8888,8889,2718,8080,8443 --script=http-title 10.0.0.0/24
# Vérifier les instances Jupyter exposées sur Internet
shodan search "jupyter" org:"votre-organisation"
Vérifiez les versions. Pour chaque instance découverte, identifiez la version exacte. Les notebooks affectés par la CVE-2026-39987 sont les versions Marimo antérieures à 0.12.4. Côté Jupyter, toute version de notebook server inférieure à 7.1 présente des vulnérabilités connues de CSRF et XSS.
Documentez chaque instance. Créez un inventaire : adresse IP, port, version, propriétaire, raison du déploiement. Cet inventaire sera votre référence pour toutes les étapes suivantes. Les instances non justifiées doivent être arrêtées immédiatement.
Étape 2 — Mettre en place une authentification forte
Le vecteur d’attaque numéro un des notebooks en production est l’absence d’authentification. Jupyter offre un token par défaut, mais il est souvent désactivé pour « faciliter l’accès ». La CVE-2026-39987 de Marimo exploite précisément une faiblesse dans la validation des sessions WebSocket.
Pour Jupyter / JupyterHub :
# jupyter_server_config.py
c.ServerApp.token = '' # NE JAMAIS FAIRE EN PROD
# Configuration recommandée :
c.ServerApp.password_required = True
c.ServerApp.password = 'argon2:...' # Hash argon2
c.ServerApp.allow_origin = 'https://votre-domaine.fr'
c.ServerApp.disable_check_xsrf = False # Critique
Pour Marimo : Mettez à jour immédiatement vers la version 0.12.4 ou supérieure. Configurez un reverse proxy (Nginx, Caddy) avec authentification OAuth2 ou SAML en amont. Marimo ne gère pas nativement l’authentification multi-utilisateur — déléguez cette responsabilité à votre infrastructure.
Dans tous les cas : déployez un SSO (Single Sign-On) avec votre fournisseur d’identité existant (Keycloak, Azure AD, Okta). L’authentification doit être forcée pour les requêtes HTTP et WebSocket — c’est l’oubli du canal WebSocket qui a rendu la CVE-2026-39987 exploitable.
Étape 3 — Isoler le réseau notebook
Un notebook compromis donne accès au réseau où il tourne. Si votre instance Jupyter est sur le même VLAN que vos bases de données de production, un attaquant pivote en quelques secondes. L’isolation réseau est la première barrière après l’authentification.
Créez un segment réseau dédié. Les notebooks doivent vivre dans un VLAN ou un sous-réseau isolé. Le trafic entrant ne doit passer que par un reverse proxy authentifiant. Le trafic sortant doit être restreint aux seules ressources nécessaires (API internes, registres de packages, buckets S3).
# Règles iptables pour isoler le subnet notebook
# Autoriser uniquement le reverse proxy en entrée
iptables -A INPUT -s 10.0.1.10/32 -p tcp --dport 8888 -j ACCEPT
iptables -A INPUT -p tcp --dport 8888 -j DROP
# Restreindre le trafic sortant
iptables -A OUTPUT -d 10.0.2.0/24 -p tcp --dport 5432 -j ACCEPT # PostgreSQL
iptables -A OUTPUT -d 10.0.3.0/24 -p tcp --dport 443 -j ACCEPT # API interne
iptables -A OUTPUT -p tcp --dport 443 -j DROP # Bloquer Internet
Utilisez des Network Policies Kubernetes si vos notebooks tournent sur K8s. Les NetworkPolicy permettent un contrôle granulaire du trafic pod-to-pod et sont la manière standard d’isoler des workloads dans un cluster. Combinées avec un service mesh (Istio, Linkerd), elles offrent en plus le chiffrement mTLS entre services.
Étape 4 — Conteneuriser avec des privilèges minimaux
La conteneurisation Docker est un standard pour le déploiement de notebooks en production, mais un conteneur mal configuré offre une fausse sécurité. Un conteneur exécuté en root avec toutes les capabilities Linux est aussi dangereux qu’un processus natif.
# Dockerfile sécurisé pour notebook
FROM jupyter/minimal-notebook:latest
USER root
# Créer un utilisateur non-root dédié
RUN useradd -m -s /bin/bash -u 1001 notebook-user
# Supprimer les packages dangereux
RUN apt-get remove -y wget curl netcat-openbsd && apt-get autoremove -y
# Restrictions filesystem
RUN chmod 700 /home/notebook-user
USER notebook-user
WORKDIR /home/notebook-user
# Aucune capability, read-only filesystem sauf /tmp et /home
# (configuré au docker run)
# docker run avec durcissement
docker run -d \
--name secure-notebook \
--user 1001:1001 \
--read-only \
--tmpfs /tmp:noexec,nosuid,size=512m \
--cap-drop=ALL \
--security-opt=no-new-privileges:true \
--security-opt seccomp=notebook-seccomp.json \
--memory=2g --cpus=1.0 \
--pids-limit=100 \
-p 127.0.0.1:8888:8888 \
secure-notebook:latest
Les points clés : --cap-drop=ALL supprime toutes les capabilities Linux, --read-only empêche l’écriture hors des volumes explicitement montés, et --pids-limit prévient les fork bombs. Pour JupyterHub, utilisez DockerSpawner qui automatise ce pattern pour chaque utilisateur.
Besoin d’un audit de vos notebooks en production ?
Nos pentesteurs identifient les vulnérabilités de vos déploiements Jupyter, Marimo et Databricks avant les attaquants.
DEMANDER UN AUDITÉtape 5 — Restreindre les kernels et l’exécution de code
Par défaut, un notebook Python peut exécuter n’importe quel code via le kernel IPython, y compris des commandes système avec !command ou subprocess. En production, cette liberté est un vecteur d’attaque majeur.
Limitez les magics et commandes système. Créez un profil IPython restrictif qui désactive les magics dangereuses :
# ipython_config.py — profil de production
# Désactiver l'exécution de commandes système
c.IPKernelApp.exec_lines = [
"import subprocess; subprocess.Popen = None",
"import os; os.system = None; os.popen = None",
"import shutil; shutil.rmtree = None",
]
# Limiter les imports autorisés via un hook
c.InteractiveShellApp.exec_files = ['/etc/jupyter/security_hooks.py']
Utilisez des profils seccomp personnalisés. Un profil seccomp adapté aux notebooks bloque les appels système inutiles (mount, ptrace, reboot) tout en préservant les appels nécessaires à l’exécution de code Python. Cette couche fonctionne au niveau du noyau Linux, indépendamment de toute restriction applicative qu’un attaquant pourrait contourner.
Étape 6 — Sécuriser la gestion des secrets
Les notebooks de production accèdent souvent à des bases de données, des APIs et des services cloud. Le réflexe courant — coller les credentials directement dans une cellule — est un désastre de sécurité. Les fichiers .ipynb sont des JSON en clair, et les outputs de cellules contenant des tokens finissent régulièrement dans des dépôts Git.
Intégrez un gestionnaire de secrets. Utilisez HashiCorp Vault, AWS Secrets Manager ou Azure Key Vault. Injectez les secrets via des variables d’environnement dans le conteneur, jamais en dur dans le code :
# Bonne pratique : récupérer les secrets depuis Vault
import os
import hvac # Client HashiCorp Vault
client = hvac.Client(url=os.environ['VAULT_ADDR'],
token=os.environ['VAULT_TOKEN'])
db_creds = client.secrets.kv.v2.read_secret_version(
path='production/postgresql'
)['data']['data']
# Jamais : password = "mon-mot-de-passe-en-dur"
Mettez en place un hook pre-commit. Des outils comme detect-secrets ou trufflehog scannent automatiquement les notebooks avant chaque commit pour détecter les secrets hardcodés. C’est un filet de sécurité indispensable, surtout dans les équipes data où la culture sécurité est parfois moins ancrée. Pensez aussi à nettoyer les outputs des cellules via nbstripout avant de versionner.
Avis d’expert
« Lors de nos pentests, nous trouvons des credentials en clair dans les notebooks dans 60 % des cas. Le pire : des tokens AWS avec des privilèges administrateur. Un simple nbstripout dans le pipeline CI/CD aurait suffi à éviter ces expositions. C’est une mesure qui prend cinq minutes et qui peut éviter des millions d’euros de dommages. » — Clara Fontaine, Analyste Sécurité, WebGuard Agency
Défense en profondeur : les 8 couches de protection
Étape 7 — Monitorer et alerter en continu
Un notebook en production génère des signaux exploitables pour la détection d’anomalies : création de kernels, exécution de cellules, connexions WebSocket, appels API. Sans monitoring, vous ne saurez pas qu’un attaquant exploite votre instance.
Centralisez les logs. Configurez Jupyter pour émettre des logs structurés (JSON) vers votre SIEM (Elastic, Splunk, Datadog). Les événements critiques à surveiller :
- •Création de kernel par un utilisateur inconnu ou à une heure inhabituelle
- •Exécution de commandes système (
subprocess,os.system) via le kernel - •Téléchargement de packages depuis des registres non autorisés
- •Connexions réseau sortantes vers des destinations inconnues
- •Échecs d’authentification répétés sur l’interface WebSocket
Créez des alertes spécifiques. Un pattern comme « création de kernel + import subprocess + connexion sortante vers une IP publique » en moins de 30 secondes est un signal fort de compromission. Configurez votre SIEM pour déclencher une alerte de niveau critique et initier automatiquement l’isolation du conteneur. Cette approche s’intègre dans une stratégie d’IA appliquée à la cybersécurité où la détection est augmentée par des modèles de classification d’anomalies.
Étape 8 — Établir un plan de réponse spécifique notebooks
Votre plan de réponse aux incidents (PRI) doit inclure un playbook spécifique aux compromissions de notebooks. Un notebook compromis n’est pas un serveur web compromis — les procédures diffèrent significativement.
Phase 1 — Confinement immédiat : Isolez le conteneur du réseau (ne l’arrêtez pas, préservez les preuves). Suspendez les sessions actives. Révoquez les tokens et secrets accessibles depuis le notebook.
Phase 2 — Investigation : Analysez l’historique des cellules exécutées (fichiers .ipynb_checkpoints), les logs kernel, les connexions réseau établies depuis le conteneur et les fichiers créés ou modifiés. Vérifiez si l’attaquant a pivoté vers d’autres systèmes — un notebook compromis sert souvent de point d’entrée pour une attaque de supply chain plus large.
Phase 3 — Remédiation : Redéployez le notebook depuis une image propre (jamais de « nettoyage » en place). Effectuez une rotation complète des secrets. Auditez les systèmes adjacents. Documentez l’incident pour améliorer les défenses.
Phase 4 — Amélioration : Chaque incident doit alimenter un retour d’expérience (REX) formalisé. Mettez à jour vos règles de détection SIEM, vos profils seccomp et vos NetworkPolicies en conséquence. C’est la boucle d’amélioration continue qui différencie une posture de sécurité mature d’une défense statique. Les risques liés aux LLMs évoluent rapidement — vos défenses aussi doivent s’adapter.
Checklist récapitulative
- ✓ Étape 1 — Inventaire complet de toutes les instances notebooks en production
- ✓ Étape 2 — Authentification forte (SSO + validation WebSocket) sur chaque instance
- ✓ Étape 3 — Isolation réseau (VLAN dédié ou NetworkPolicy K8s)
- ✓ Étape 4 — Conteneurs non-root avec capabilities réduites et profils seccomp
- ✓ Étape 5 — Restrictions kernel (magics désactivées, commandes système bloquées)
- ✓ Étape 6 — Secrets via Vault/KMS, pre-commit hooks avec detect-secrets
- ✓ Étape 7 — Logs centralisés + alertes SIEM spécifiques notebooks
- ✓ Étape 8 — Playbook incident spécifique avec phases confinement/investigation/remédiation
Questions fréquentes
Les notebooks Python sont-ils sûrs en production ?
Par défaut, non. Les notebooks Jupyter et Marimo exécutent du code arbitraire côté serveur avec les privilèges de l’utilisateur système. Sans durcissement (authentification, isolation réseau, conteneurisation, restrictions kernel), ils représentent une surface d’attaque critique. La CVE-2026-39987 de Marimo illustre ce risque avec une RCE non authentifiée CVSS 9.8.
Qu’est-ce que la CVE-2026-39987 de Marimo ?
La CVE-2026-39987 est une vulnérabilité d’exécution de code à distance (RCE) dans Marimo, un framework de notebooks Python réactifs. Elle permet à un attaquant non authentifié d’exécuter du code arbitraire sur le serveur via une requête WebSocket malformée, avec un score CVSS de 9.8 (critique). Les versions antérieures à 0.12.4 sont affectées.
Jupyter ou Marimo : lequel est le plus sécurisé ?
Les deux présentent des risques similaires en production. Jupyter a un écosystème de sécurité plus mature (JupyterHub, tokens, RBAC) mais une surface d’attaque plus large due aux extensions. Marimo est plus récent avec une architecture réactive qui limite certains vecteurs, mais la CVE-2026-39987 montre qu’il n’est pas exempt de failles critiques. Dans les deux cas, les 8 étapes de ce guide s’appliquent.
Comment isoler un notebook Python en production ?
L’isolation recommandée combine trois couches : conteneurisation Docker avec un utilisateur non-root et des capabilities réduites, isolation réseau via un VLAN dédié ou des NetworkPolicies Kubernetes sans accès direct à Internet, et isolation kernel avec des profils seccomp et AppArmor. JupyterHub avec DockerSpawner automatise cette approche à l’échelle.
Vos notebooks sont-ils sécurisés ?
Nos experts réalisent un audit complet de vos déploiements notebook en production — vulnérabilités, configuration, isolation et conformité.
CONTACTEZ NOS EXPERTS