Skip to content

Français, page affichée Read this page in English

Serenity, ton coffre de mots de passe chez toi. Un agent surveille les fuites et change ceux que tu lui confies.

CI

Un gestionnaire de mots de passe complet que tu héberges toi-même, avec son propre coffre chiffré, et un agent qui veille pendant que tu dors.

Je voulais mes mots de passe chez moi, sur ma machine, et pas chez un service qui en garde des millions. Les coffres auto-hébergés existent, mais aucun ne faisait ce qui me manquait vraiment : me dire quand un compte a fui, et changer le mot de passe à ma place quand je le lui demande. Serenity est né de là. Le coffre, l'API et les clients sont écrits ici, sans Bitwarden ni Vaultwarden dessous, avec une seule bibliothèque de cryptographie, libsodium, et aucune primitive faite main.

Tout repose sur un coffre à deux zones. Ce qui compte vraiment, la banque ou la messagerie principale, reste dans ta zone : chiffré sur ton appareil, illisible pour le serveur. Ce que tu confies à l'agent, entrée par entrée, il peut le lire, le surveiller et le changer. Le principe tient en une phrase : pas d'humain dans la boucle, mais un humain toujours informé. Et un kill switch l'arrête net, vérifié avant chaque action.

00 Sommaire

01 Fonctionnalités 02 Les écrans 03 Installer
04 Le coffre à deux zones 05 La cryptographie 06 Connexion et kit
07 La veille des fuites 08 L'agent 09 La rotation
10 Les garde-fous 11 Codes et import 12 La sauvegarde
13 Architecture 14 Les tests 15 Versions et licence

01 Fonctionnalités

Voici Serenity en onze gestes, du déverrouillage au verrouillage. À gauche, l'appli rejoue ses vrais écrans ; à droite, tu vois ce qui se passe derrière : la clé tirée de ton mot de passe, les cinq caractères qui partent vers Pwned Passwords, la rotation que l'agent te propose avant de toucher à quoi que ce soit. Chaque geste a son propre schéma plus bas.

Les fonctionnalités de Serenity en onze étapes animées : à gauche l’appli rejoue le geste, sur le téléphone pour les sept premières et sur la fenêtre de bureau pour les quatre dernières ; à droite le mécanisme se construit. 1, déverrouiller : le mot de passe maître tapé sur l’écran « Bon retour, Tristan. », le bouton Déverrouiller, le voile et le faisceau de lumière, puis le coffre ; à droite, Argon2id avec 64 Mio, 3 passes et un sel de 16 octets donne MK, qui donne AuthKey, envoyée au serveur qui n’en garde qu’un hachage, et MEK, qui ouvre UK puis AK, gardées en mémoire. 2, une phrase d’abord : le coffre dit « La veille répond… », puis l’anneau de santé s’allume jusqu’à 100 et la phrase devient « Tout va bien. » ; à droite, ce qui fait baisser le score : une fuite pèse 1, un mot de passe réutilisé 0,6, trop faible 0,5, trop ancien 0,3, et 5 points par adresse e-mail touchée ; vert dès 85, ambre dès 60, rouge dessous. 3, la fiche de Netflix : le mot de passe copié, effacé du presse-papiers après 30 s, sa force « Faible, 11 caractères », et le code à usage unique 933 532 qui décompte, calculé ici par HMAC-SHA1 depuis le secret de l’entrée et l’heure. 4, confier à l’agent : le bouton, la confirmation qui dit ce que le serveur pourra faire, puis la fiche « L’agent s’occupe de ce compte » ; à droite, Netflix passe de « Protégé par toi » à « Confié à l’agent » : bloc chiffré par UK, déchiffré en mémoire, rechiffré par AK avec un nonce neuf, envoyé par POST /delegate avec confirm: true. 5, la veille : l’écran Fuites, « Vérifier maintenant », puis la santé à 67, « Une chose demande ton attention. » et l’alerte Netflix vue dans une fuite ; à droite, l’empreinte SHA-1 dont seuls les 5 premiers caractères partent vers Pwned Passwords, qui renvoie des centaines de suffixes comparés sur place, et la santé qui passe de 100 à 67. 6, tu approuves : l’écran Agent propose de changer le mot de passe de Netflix en quatre étapes, Générer, Changer, Prouver, Valider, et tu touches Approuver ; à droite, les quatre étapes au prochain passage, la révision en attente à côté de l’actuelle jusqu’à la preuve, et les garde-fous : 3 rotations au maximum par jour, seuls les sites de l’allowlist, kill switch relu avant chaque action. 7, codes 2FA : l’écran Codes, « Nouveaux codes dans » qui décompte, le code copié ; à droite, secret, heure par tranches de 30 s, HMAC-SHA1 avec Web Crypto, aucune requête au serveur, et le code qui tourne hors ligne ; en zone agent, le serveur peut calculer le même code pour se reconnecter. 8, le kill switch sur la fenêtre de bureau : maintenu 1 s, l’orbe violet devient gris, « L’agent est arrêté », la lumière passe au gris ; à droite, la jauge d’une seconde, et la veille d’un compte, d’une adresse et la rotation qui s’arrêtent net ; le relâcher demande le mot de passe maître. 9, la palette : Ctrl K, les suggestions et les actions, « net » tapé, Netflix seul, Ctrl Entrée copie son mot de passe ; à droite, les raccourcis : / pour chercher, G puis V, C, F ou A, Ctrl N, Ctrl L, Ctrl virgule, Échap. 10, l’import dans Réglages : Mots de passe Google, 3 entrées prêtes, Importer ; à droite, le fichier .csv de Chrome lu et chiffré ici en XChaCha20-Poly1305 avec la clé UK, et trois blocs que le serveur ne sait pas ouvrir. 11, rien en clair : Ctrl L, l’écran de déverrouillage « Déchiffré sur cet appareil. Le serveur ne voit que des blocs chiffrés. » ; à droite, UK et AK effacées, le presse-papiers vidé, Serenity qui n’écoute que 127.0.0.1:8080, joint par un chemin privé en HTTPS, rien sur Internet sauf l’agent vers Pwned Passwords, et ce que le serveur garde face à ce qu’il ne voit jamais. En bas, onze tuiles servent de sommaire et gardent une coche une fois vues.

02 Les écrans

Voici Serenity sur un téléphone, écran par écran. Tout ce que tu vois ici est déchiffré et calculé sur l'appareil : le serveur ne reçoit que des blocs qu'il ne sait pas ouvrir, sauf ceux que tu confies à l'agent.

Huit écrans de Serenity sur téléphone, en deux rangées de quatre, chacun avec sa légende. Première rangée : le déverrouillage, avec le logo, « Bon retour, Tristan. » et le champ du mot de passe maître, dérivé sur l’appareil ; le coffre, avec l’anneau de santé à 100, « Tout va bien. », puis Banque, Netflix et Spotify sous « Protégé par toi » et la zone « Confié à l’agent » encore vide ; la fiche de Netflix, avec son identifiant, son mot de passe jugé faible, son code à usage unique 933 532, son site et le bouton « Confier à l’agent » ; les codes 2FA, calculés sur l’appareil même hors ligne, avec le code de Netflix. Seconde rangée : les fuites, avec la santé du coffre à 67, « Une chose demande ton attention. », les compteurs et l’alerte de Netflix vu dans une fuite ; l’agent, qui propose de changer le mot de passe de Netflix en quatre étapes, générer, changer, prouver, valider, avec les boutons Refuser et Approuver ; le kit de récupération, neuf blocs de quatre caractères affichés une seule fois à la création du compte ; les réglages, avec le verrouillage après 15 minutes, l’apparence, la veille, l’import et l’export, la corbeille, le journal, le kit et les appareils.

Sur ordinateur, Serenity tourne dans ton navigateur ou dans son appli de bureau pour Windows et Linux. C'est le même client dans les deux cas. La barre latérale, la palette (Ctrl K) et la barre d'état remplacent les onglets du téléphone, et la lumière derrière le coffre dit où il en est : bleue quand tout va bien, ambre quand un compte a fui, violette quand l'agent travaille, grise quand il est arrêté.

Six grands écrans de Serenity, chacun avec sa légende. En haut à gauche, en grand, l’appli de bureau pour Windows et Linux : sa propre barre de titre avec le logo, la recherche au centre, la cloche, le thème et les boutons réduire, agrandir et fermer ; en dessous, la barre latérale avec le coffre, les codes 2FA, les fuites, l’agent et les deux zones, la liste où Banque et Spotify sont protégés par toi et Netflix confié à l’agent, et la fiche de Banque ouverte à droite. À sa droite, dans un navigateur, l’écran de l’agent avec le kill switch en marche, l’activité du jour et la rotation de Netflix qui attend ton accord ; puis la palette de commandes ouverte par Ctrl K, avec ses suggestions et ses actions. En bas, trois fenêtres de navigateur : les fuites, avec la santé du coffre à 67, les compteurs, l’alerte de Netflix et l’explication de la veille ; les réglages, ouverts sur le verrouillage automatique après 15 minutes ; et le coffre en thème clair.

C'est un seul client web qui prend trois formes. Sous 900 px, tu as les cinq onglets en bas, sous le pouce. Au-delà, une barre latérale et la palette. Dans l'appli de bureau, la fenêtre dessine sa propre barre de titre et se verrouille avec ta session. Elle n'embarque aucun code du coffre : elle affiche la page que sert ton serveur, avec la même CSP et les mêmes clés en mémoire seulement.

Trois formes, un seul code. À gauche, la même appli dans ses trois formes, sur les vrais écrans. D’abord le téléphone, sous 900 px : le coffre, avec la santé à 67, Banque et Spotify protégés par toi, Netflix confié à l’agent, et cinq onglets en bas ; un toucher sur Codes ouvre les codes 2FA, avec le code de Netflix calculé sur l’appareil. Ensuite le navigateur, dès 900 px : une barre latérale de 248 px avec le logo, les quatre écrans, les deux zones, l’anneau de santé et l’agent ; la recherche en haut, la liste et la fiche de Banque côte à côte, la barre d’état en pied ; un clic sur la recherche, ou Ctrl K, ouvre la palette de commandes avec ses suggestions et ses actions. Enfin l’appli de bureau pour Windows et Linux : sa propre barre de titre avec le logo, la recherche au centre, la cloche, le thème, puis réduire, agrandir et fermer ; Ctrl Maj Espace la cache puis la ramène depuis n’importe où ; quand tu verrouilles ta session avec Win L, le coffre se verrouille aussi et affiche « Bon retour, Tristan. ». Sous la scène, une règle montre la largeur mesurée, 390 px puis 1280 px, face au seuil WIDE_FROM de 900 px. À droite, les lignes de Shell.tsx qui choisissent la forme : wide si la largeur atteint WIDE_FROM, isDesktop si window.serenityDesktop existe, puis mobile, web ou desktop, le mot de la forme en cours éclairé. Trois cartes se construisent : téléphone, moins de 900 px, cinq onglets en bas ; navigateur, dès 900 px, barre latérale, recherche qui ouvre la palette, barre d’état ; appli de bureau, Windows et Linux, sa barre de titre, le verrouillage avec la session, Ctrl Maj Espace, une seule instance. En bas, ce qui change : la mise en page selon la largeur, et dans l’appli de bureau le pont window.serenityDesktop pour les boutons de la fenêtre et l’ordre de verrouiller ; ce qui ne change pas : le même client web servi par ton serveur, la crypto dans la page, aucun code du coffre dans l’appli de bureau.

03 Installer

Il te faut une VM avec Docker, quatre commandes, et un chemin privé jusqu'à elle. Serenity n'écoute que sur 127.0.0.1 et ne se met jamais sur Internet : tu l'atteins depuis tes appareils par l'accès privé de ton choix, réseau maillé, VPN, tunnel SSH ou reverse proxy sur ton réseau local. Chez moi, c'est tailscale serve. La page infrastructure compare les quatre.

git clone https://github.com/Cybertrist/Serenity.git
cd Serenity
cp .env.example .env   # puis remplis les valeurs
make init
make up

Sur ton ordinateur, prends l'appli de bureau. Au premier lancement, elle te demande l'adresse de ton serveur, en https seulement, puis s'ouvre directement sur ton coffre. Les deux boutons mènent à la dernière Release ; la première arrive avec la v0.1.0, et d'ici là make desktop-dist construit l'installateur de ta plateforme.

Télécharger Serenity pour Windows, installateur ou version portable en .exe Télécharger Serenity pour Linux, en .AppImage ou en paquet .deb

Les installateurs ne sont pas encore signés : Windows SmartScreen affichera un avertissement au premier lancement. Sur ton téléphone, ouvre l'adresse de ton serveur dans le navigateur, puis « Installer l'application » : c'est une PWA, qui marche aussi hors ligne, en lecture seule.

Installer Serenity, en trois temps. À gauche, un terminal sur la VM : git clone du dépôt, cd Serenity et cp .env.example .env, nano .env pour TAILNET_HOST et SERENITY_SECRET_KEY, make init qui crée data/api, make up qui démarre les conteneurs api, agent, rotator et web, tous « Healthy », puis ss qui prouve que seul 127.0.0.1:8080 écoute. Ensuite le chemin privé : sudo tailscale serve --bg --https=443 vers 127.0.0.1:8080, et son statut, https://serenity.tail1234.ts.net, réservé au tailnet ; sous le terminal, tes appareils rejoignent tailscale serve en HTTPS, qui mène au conteneur web sur 127.0.0.1:8080, et rien n’est ouvert sur Internet, ni port sur ta box ni tailscale funnel. Puis l’appli de bureau au premier lancement : « Bienvenue. Indique l’adresse de ton serveur Serenity pour ouvrir ton coffre. » ; une adresse en http est refusée avec « Il faut une adresse en https, par exemple https://coffre.exemple.fr. », l’adresse https://serenity.tail1234.ts.net est acceptée, « Connexion… », puis l’écran de connexion. Enfin le téléphone : le navigateur ouvre la même adresse sur l’écran de connexion, le menu propose « Installer l’application », la fenêtre de confirmation montre Serenity et son adresse, l’icône arrive sur l’écran d’accueil, et l’appli s’ouvre sans barre d’adresse. À droite, quatre étapes numérotées s’allument tour à tour : sur ta VM, Docker Compose, make up crée les clés, construit et démarre, rien n’écoute hors de 127.0.0.1:8080 ; un chemin privé, réseau maillé, VPN, tunnel SSH ou reverse proxy local, en HTTPS, chez moi tailscale serve ; l’appli de bureau, qui demande l’adresse au premier lancement, en https seulement sauf 127.0.0.1 et localhost ; le téléphone, qui installe la PWA depuis le navigateur. En bas, les Releases GitHub : pour Windows un installateur .exe et un .exe portable, pour Linux un .AppImage et un .deb, pas encore signés, donc SmartScreen avertit au premier lancement.

Toute la documentation est dans docs/, en français, une page par phase.

04 Le coffre à deux zones

Le coffre a deux zones, et c'est le choix qui commande tout le reste. Une entrée neuve va toujours dans ta zone, chiffrée par une clé qui ne quitte jamais tes appareils. La confier à l'agent est un geste, fait entrée par entrée, avec une confirmation qui dit ce que le serveur pourra faire. Ton appareil la déchiffre alors, la rechiffre avec la clé d'agent, et le serveur refuse le changement de zone sans confirm: true.

Le coffre à deux zones, et ce que fait « Confier à l’agent ». À gauche, le téléphone rejoue le geste sur les vrais écrans : le coffre, où Netflix est rangé sous « Protégé par toi » avec Banque et Spotify ; la fiche de Netflix, qui dit « Protégé par toi, l’agent te prévient en cas de fuite mais ne lit rien ici » ; le bouton « Confier à l’agent » ; la confirmation, qui dit que le serveur pourra déchiffrer l’entrée, surveiller ses fuites, changer son mot de passe et calculer son code à deux facteurs ; puis la fiche devenue « L’agent s’occupe de ce compte », et le coffre avec Netflix sous « Confié à l’agent ». À droite, deux cartes : la zone personnelle, en zéro connaissance, chiffrée par la clé UK qui ne quitte jamais tes appareils, lue par toi seul, où l’agent ne lit rien et prévient ; la zone agent, sur délégation explicite, chiffrée par la clé AK rangée deux fois, lue par toi et par l’agent sur le serveur, qui surveille et change le mot de passe. La tuile de Netflix passe de l’une à l’autre pendant que l’appareil travaille, en quatre étapes : le bloc chiffré par UK en révision 1, déchiffré dans le navigateur et en mémoire seulement, rechiffré par AK avec un nonce neuf en révision 2, puis envoyé par POST /delegate avec confirm: true, que le serveur exige, et une ligne au journal. En bas, trois règles : toute nouvelle entrée va dans la zone personnelle ; on confie une entrée à la fois, avec confirmation ; reprendre efface l’historique lisible par l’agent, et l’appli conseille de changer le mot de passe.

05 La cryptographie

Tout part de ton mot de passe maître, et il ne quitte jamais ton appareil. Argon2id en tire une clé maître qui ne vit que le temps de donner deux sous-clés : l'une ouvre ta clé utilisateur, l'autre est la seule envoyée au serveur, qui n'en garde qu'un hachage. Ta clé utilisateur ouvre la clé d'agent, et ton kit de récupération est une deuxième porte vers elle. Le serveur, lui, ne voit que des blocs chiffrés.

La hiérarchie des clés, construite pendant que le coffre s’ouvre. À gauche, le téléphone rejoue l’écran de déverrouillage : « Bon retour, Tristan. », le mot de passe maître tapé, le bouton « Déverrouiller », puis l’ouverture, où un voile bleu nuit monte et un faisceau de lumière traverse l’écran, et le coffre apparaît ; quinze minutes plus tard, il se verrouille de nouveau. À droite, deux colonnes. Sur ton appareil : le mot de passe maître, dans ta tête et jamais envoyé ; Argon2id v1.3 avec 64 Mio, 3 passes et un parallélisme de 1 ; MK, la clé maître de 32 octets, jamais stockée ni envoyée ; deux sous-clés, MEK par KDF srn-wrap, qui reste sur l’appareil, et la clé d’auth par KDF srn-auth, la seule envoyée ; UK, 32 octets au hasard ouverts par MEK ; RK, le kit de 20 octets, qui chiffre aussi UK par BLAKE2b puis KDF ; AK, 32 octets au hasard ouverts par UK ; la zone personnelle chiffrée par UK et la zone agent chiffrée par AK. Une légende distingue les clés en mémoire des clés effacées. Sur le serveur : le sel de 16 octets et les paramètres, publics ; les blocs des entrées, illisibles pour lui dans la zone personnelle ; le hachage Argon2id de la clé d’auth, qui ne remonte pas jusqu’à MK ; UK chiffrée deux fois, par MEK et par la clé du kit ; AK chiffrée par UK pour tes appareils ; AK scellée pour la clé serveur, pour le processus agent, la clé serveur étant un fichier root:root 0400 hors de la base. Les légendes racontent l’ordre : le sel revient du serveur, Argon2id tire MK en 0,6 à 1,3 s sur un téléphone, seule la clé d’auth part et le serveur la compare à son hachage, MEK ouvre UK et UK ouvre AK, puis MK, MEK et la clé d’auth sont effacées et seules UK et AK restent en mémoire, jusqu’au verrouillage après 15 minutes. En bas : le serveur ne voit jamais ton mot de passe maître, MK, MEK, UK, ton kit, ni une entrée personnelle en clair.

Chaque entrée est un bloc XChaCha20-Poly1305 lié à son contexte : ton compte, l'identifiant de l'entrée, sa zone et sa révision. Ce contexte n'est pas stocké, ton appareil le reconstruit. Si le serveur échange deux blocs, ressort une vieille version ou change une entrée de zone, le déchiffrement refuse, tout simplement. Python et TypeScript vérifient les mêmes vecteurs de test, y compris ceux qui doivent échouer.

Un bloc chiffré vu de près, et trois attaques d’un serveur malveillant. À gauche, le téléphone ouvre le coffre, touche Netflix et sa fiche se déchiffre ; puis il montre ce que chaque attaque laisse à l’écran : le coffre avec la note « 2 entrées illisibles : elles ne se déchiffrent pas avec tes clés et restent masquées. Si ça dure, préviens l’administrateur du serveur. », où seule Banque reste ; le coffre inchangé quand une vieille révision est ressortie ; puis la même note pour 1 entrée quand Netflix est déplacé de zone. À droite, en haut, les octets du bloc de Netflix, 298 octets tirés du vecteur item.json : la version 01 à l’octet 0, le type 01 pour XChaCha20-Poly1305 à l’octet 1, le nonce de 24 octets tirés au hasard à partir de l’octet 2, le texte chiffré de 256 octets, soit l’entrée JSON bourrée, à partir de l’octet 26, et le MAC Poly1305 de 16 octets à l’octet 282. Au milieu, les données associées reconstruites par l’appareil : 01 01 puis le contexte serenity/v1/item, l’identifiant de l’utilisateur 7b2c1a4e, celui de l’entrée 0f0c7a9e, la zone personal et la révision 3, sur deux rangs, ce qu’attend l’appareil et ce que contient le bloc. Tant qu’ils sont identiques, XChaCha20-Poly1305 avec la clé UK déchiffre et Netflix s’ouvre. Trois attaques suivent, chacune avec la case qui diffère en rouge : échanger deux blocs, le bloc de Spotify servi sous l’identifiant de Netflix, refusé car le MAC ne correspond pas ; rejouer la révision 2, refusée si elle est étiquetée 3, écartée si elle est étiquetée 2 car l’appareil a déjà vu la 3, garde sa copie et note le retour en arrière ; changer de zone, Netflix rangé en zone agent, si bien que l’appareil prend la clé AK alors que le bloc a été scellé en personal, refusé. En bas, les vecteurs de test partagés dans shared/test-vectors : Python et TypeScript vérifient les mêmes blocs, dont ceux qui doivent échouer, et en CI l’un chiffre pendant que l’autre déchiffre.

La spécification complète tient dans docs/crypto.md : les primitives, la hiérarchie des clés, les formats octet par octet, chaque flux et le modèle de menace.

06 Connexion et kit

Créer ton compte tient en trois écrans. Ton appareil tire tes clés au hasard et les chiffre avant tout envoi : le serveur ne reçoit que deux clés d'authentification, qu'il hache, et des blocs qu'il ne sait pas ouvrir. Ton premier code à six chiffres active le compte et ferme les inscriptions. Ton kit de récupération ne s'affiche qu'une fois : note-le.

La création du compte en trois étapes. À gauche, le téléphone rejoue les vrais écrans. Étape Compte, « Bienvenue. Crée ton coffre. Ton mot de passe maître ne quitte jamais cet appareil. » : l’identifiant tristan, le mot de passe maître, dont l’indice passe de « 12 caractères au moins » à « Solide. », sa confirmation, puis « Continuer ». Étape Vérification, « La double vérification » : la clé à saisir dans l’appli d’authentification, le bouton pour l’ouvrir, le code à 6 chiffres tapé case par case, puis « Vérifier », avec la note « Le code sera redemandé sur un nouvel appareil, puis tous les 60 jours. ». Étape Récupération, « Ton kit de récupération » : neuf groupes de quatre caractères, créé le 27 septembre 2026 et affiché une seule fois, les boutons Télécharger et Copier, trois rappels (il ne sera plus jamais affiché, le serveur n’en garde aucune copie lisible, avec ton code à deux facteurs il rouvre ton coffre), la case « Je l’ai noté dans un endroit sûr. » cochée, puis « Ouvrir mon coffre », l’ouverture et le coffre vide. À droite, deux cartes. Ton appareil tire au hasard user_id, un UUID v4, un sel de 16 octets, UK et AK de 32 octets chacune, et RK, ton kit de 20 octets, noté puis effacé de la mémoire ; il dérive MK, qui donne la clé d’auth et MEK, et tire du kit la clé d’auth de récupération, avant de les effacer. Le serveur reçoit l’identifiant, user_id et le sel, la clé d’auth et la clé d’auth de récupération, toutes deux hachées en Argon2id, et quatre blocs : UK chiffrée par MEK, UK chiffrée par le kit, AK chiffrée par UK, AK scellée. Entre les deux passent les clés, puis le secret TOTP en retour, puis le code. Deux états changent : le compte passe d’en attente à actif, les inscriptions d’ouvertes à fermées. Dessous, trois étapes : POST /api/auth/signup, où le serveur hache les deux clés d’auth, crée le compte en attente et renvoie le secret TOTP une seule fois ; POST /api/auth/signup/confirm, où le premier code active le compte et ferme les inscriptions, un seul compte en V1 ; le kit affiché une seule fois, téléchargeable, puis effacé de la mémoire une fois noté. En bas : jamais envoyés, ton mot de passe maître, MK, MEK, UK, ni ton kit.

Le code à six chiffres ne t'est demandé que sur un nouvel appareil, puis tous les 60 jours. Au quotidien, ton mot de passe maître suffit : le coffre reste ouvert 15 minutes, prolongées à chaque action, et tes clés ne vivent qu'en mémoire. Sans réseau, tu peux toujours le lire. Si tu oublies ton mot de passe, ton kit et un code te laissent en choisir un nouveau, et tu repars avec un kit neuf.

Se connecter, jour après jour. À gauche, un téléphone passe les trois portes sur les vrais écrans. Connexion complète sur un nouvel appareil : l’écran « Connexion, sur un nouvel appareil, ou tous les 60 jours », l’identifiant tristan, le mot de passe maître tapé, « Continuer », puis « La double vérification » et ses six cases remplies, « Déverrouiller ». L’ouverture suit : un voile bleu nuit monte, le ruban gonfle, un faisceau de lumière traverse l’écran, et le coffre apparaît avec Banque, Spotify et Netflix. Quinze minutes plus tard, l’écran de verrouillage « Bon retour, Tristan. » revient ; le mot de passe seul le rouvre, hors ligne cette fois, et le coffre s’affiche avec la note « Hors ligne : tu peux lire ton coffre, mais rien n’y est modifiable tant que le serveur n’est pas joignable. ». Puis l’appli est fermée, on touche « Oublié ? Utilise ton kit » : l’écran « Récupération » demande l’identifiant, la clé de récupération et un code, et se termine sur « Ton nouveau kit », avec la note « Tes autres appareils ont été déconnectés. » et neuf nouveaux groupes. À droite, trois portes : la connexion complète, mot de passe et code sur un nouvel appareil puis tous les 60 jours, qui ouvre une session d’appareil de 60 jours par POST /api/auth/login ; le déverrouillage, mot de passe seul, 15 minutes prolongées à chaque action, en lecture seule hors ligne, par POST /api/auth/unlock ; la récupération, kit et code, nouveau mot de passe et nouveau kit, l’ancien ne valant plus rien, par POST /api/auth/recover. Dessous, l’état de cet appareil : la session d’appareil, aucune, puis ouverte pour 60 jours, puis nouvelle après la récupération, qui ferme toutes les autres ; le coffre, verrouillé, déverrouillé 15 minutes avec une barre qui se vide, hors ligne en lecture seule ; et ce qui est en mémoire, UK et AK seulement quand le coffre est ouvert, rien sinon. Des légendes racontent chaque moment, dont le code à usage unique même dans ses 30 secondes. En bas, trois règles : l’ouverture, où le voile monte, un faisceau traverse l’écran en 1,25 s et le coffre se monte derrière avant que le voile s’ouvre ; trop d’essais, où après 5 échecs viennent une pause de 1 minute puis 2, 4, 8 jusqu’à 24 heures, avec des compteurs qui survivent à un redémarrage ; pas d’énumération, où un compte inconnu reçoit un faux sel stable et le serveur calcule quand même un Argon2id.

07 La veille des fuites

Pour savoir si un mot de passe a fuité, ton appareil calcule son empreinte et n'en envoie que les cinq premiers caractères. Pwned Passwords renvoie quelques centaines de suffixes qui commencent pareil, avec du bourrage, et la comparaison se fait chez toi. Ton serveur ne reçoit que le résultat : quelle entrée, quel type d'alerte. Jamais un mot de passe, ni un nom de site.

La veille des fuites, en k-anonymat. À gauche, le téléphone rejoue les vrais écrans : le coffre, où Netflix est confié à l’agent ; un toucher sur l’onglet Fuites ; l’onglet pendant la vérification, anneau de santé à 100 et « Tout va bien » ; puis le résultat : l’anneau tombe à 67, la lumière vire à l’ambre, les compteurs montrent une fuite et un mot de passe faible, et l’alerte Netflix arrive avec les pastilles « Vu dans une fuite », « Faible » et « Confié à l’agent », le texte « L’agent a préparé une rotation, elle attend ton accord » et le bouton « Voir la proposition ». À droite, ce que fait ton appareil pendant ce temps. Il prend un mot de passe d’essai, password123, et calcule son empreinte SHA-1 de 40 caractères, CBFDAC6008F9CAB4083784CBD1874F76618D2A97. Seuls les 5 premiers, CBFDA, partent vers api.pwnedpasswords.com, par GET /range/CBFDA avec l’en-tête Add-Padding. Le service renvoie les suffixes qui commencent pareil, environ 800, plus du bourrage, soit 100 ko. La comparaison se fait sur l’appareil : le suffixe C6008F9CAB4083784CBD1874F76618D2A97 est dans la liste, donc le mot de passe a été vu dans une fuite. Ce qui part vers ton serveur, par POST /api/watch/report : l’identifiant de l’entrée et le type d’alerte, pwned_password et weak, jamais un mot de passe ni un nom de site. En local, sur tout le coffre et à chaque ouverture, trois autres contrôles : réutilisé (le même mot de passe sur plusieurs entrées, que seul ton appareil peut voir), faible (moins de 12 caractères, moins de 60 bits ou moins de 5 caractères différents : password123 a 11 caractères et 57 bits, il est faible), ancien (pas changé depuis plus d’un an). Enfin le score de santé : chaque entrée pèse selon sa pire alerte, fuite 1, réutilisé 0,6, faible 0,5, ancien 0,3, et on retire 5 points par adresse e-mail touchée. Ici, trois entrées dont Netflix qui pèse 1 : 100 × (1 - 1/3) = 67. L’anneau est vert dès 85, ambre dès 60, rouge en dessous.

Les contrôles locaux ne coûtent rien : ils repassent sur tout le coffre à chaque ouverture de l'onglet. La question à Pwned Passwords, elle, pèse 100 ko par entrée, alors le serveur tient le calendrier : il ne la repose que pour une entrée neuve, un mot de passe changé, ou une vérification vieille de plus de 24 heures. Rouvre l'onglet dix fois, rien ne part sur le réseau.

La cadence de la veille. À gauche, la fenêtre de bureau rejoue une journée : l’écran Agent, un clic sur Fuites à 8 h, « Vérification en cours », puis « Dernière vérification à l’instant » ; retour à Agent et à Fuites vers 10 h ; encore Agent puis Fuites à 13 h ; enfin un clic sur « Vérifier maintenant » à 18 h. La lumière passe du violet de l’agent à l’ambre des fuites à chaque changement d’écran. À droite en haut, ce que retient le serveur dans la table item_scan, pour Banque, Netflix et Spotify : une révision et la date de la dernière question. Hier à 07:40, donc plus de 24 h ce matin : les trois sont « à demander ». Après 8 h, elles sont à jour. Quand le mot de passe de Spotify change, sa révision passe de 1 à 2 et il redevient « à demander », jusqu’à 13 h. À 18 h, tout est revérifié. GET /api/watch/plan répond ce qui reste à demander : une entrée jamais vérifiée, dont la révision a changé, ou vérifiée il y a plus de 24 h. En dessous, le rapport POST /api/watch/report porte deux listes : scanned, tout ce que le scan a regardé, toujours 3 ; pwned_scanned, ce qu’il a vraiment demandé au réseau : 3 le matin, 0 quand on rouvre, 1 à 13 h, 3 le soir. En bas, une journée de 6 h à 20 h sur deux lignes. Ton navigateur : 3 requêtes à 8 h, dix réouvertures sans aucune requête, 1 requête à 13 h, 3 requêtes à 18 h, et les contrôles locaux sur les 3 entrées à chaque ouverture, sans réseau. Ton serveur, sans toi : toutes les 6 h, à 6 h, 12 h et 18 h, il vérifie la zone agent seulement, ici Netflix. À droite, sur un coffre de 500 entrées : avant, 500 requêtes et 50 Mo à chaque ouverture de l’onglet ; maintenant, ce que dit le plan, souvent rien et jamais plus d’une fois par jour. Une réponse pèse 100 ko à cause du bourrage qui protège ta vie privée.

08 L'agent

Une fuite sur un compte confié à l'agent n'attend pas : dès le rapport, le serveur vérifie le kill switch et prépare la rotation. Tu reçois une notification, qui ne contient aucun nom (c'est ton appli qui l'écrit), tu vois les quatre étapes, et tu approuves ou tu refuses. Tes entrées personnelles, elles, ne sont jamais touchées : l'agent te prévient, c'est tout.

Ce que fait l’agent d’une fuite sur une entrée qui lui est confiée. À gauche, la fenêtre de bureau rejoue les vrais écrans. D’abord Fuites, lumière ambre : l’anneau à 67, l’alerte Netflix avec « Vu dans une fuite », « Faible » et « Confié à l’agent ». La cloche de la barre de titre reçoit son point, un clic l’ouvre : le centre de notifications montre trois lignes non lues sur Netflix, « Une rotation attend ton accord », « Mot de passe exposé, vu dans une fuite connue, change-le dès que possible » et « Mot de passe faible, trop court ou trop simple », avec la note : le serveur ne garde que le type de l’alerte et l’identifiant de l’entrée, jamais son nom ni son mot de passe. Un clic sur la première ouvre l’écran Agent, lumière violette : le kill switch en marche, « Changer le mot de passe de Netflix, proposée à l’instant, après une fuite », le rappel que le mot de passe actuel est apparu dans une fuite connue, les quatre étapes 01 Générer (un mot de passe neuf de 24 caractères, gardé en révision en attente), 02 Changer (ce site n’est pas dans l’allowlist, tu feras le changement toi-même, guidé), 03 Prouver (reconnexion avec le nouveau, l’ancien doit être refusé), 04 Valider (la révision devient la bonne, sinon retour à l’ancien), et les boutons Refuser et Approuver avec la touche Entrée. Un clic sur Approuver : le message « Approuvée, l’agent la joue à son prochain passage », et la carte « Netflix : approuvée, étape 1 sur 4 ». À droite, ce que veut dire chaque réponse. Approuver : la rotation passe à approved et l’exécuteur la joue au passage suivant de l’agent. Refuser : pas de rotation maintenant, la prochaine échéance est repoussée d’une période. Une fuite, une rotation : tant que l’alerte reste ouverte, l’agent ne repropose pas une rotation faite, refusée ou en échec. Ta zone personnelle : jamais concernée, l’agent ne peut pas la lire ; une fuite y est signalée, et l’échéance donne un rappel. En bas, ce que fait le serveur dans la foulée du rapport, en cinq étapes : 01 le rapport, POST /api/watch/report, ouvre l’alerte « mot de passe exposé » sur Netflix ; 02 le kill switch est relu avant toute action, et s’il est coupé rien n’est programmé et le journal le note ; 03 la rotation est programmée tout de suite, déclencheur « fuite », le passage horaire restant un filet ; 04 la notification ne porte qu’un type et un identifiant, l’appli écrit le nom elle-même ; 05 l’exécuteur, une fois la rotation approuvée, la joue au passage suivant : générer, changer, prouver, valider.

09 La rotation

Quand tu approuves, l'agent ne touche pas au site tout de suite. Il range d'abord le nouveau mot de passe dans ton coffre, en révision « en attente », pendant que l'entrée active garde l'ancien. Le rotateur, le seul conteneur qui a un navigateur, se connecte, change, puis se reconnecte de zéro. Seule cette preuve fait de la nouvelle révision la bonne.

Une rotation qui réussit : le coffre est servi avant le site. À gauche, le téléphone rejoue les vrais écrans. L’écran Agent propose « Changer le mot de passe de Site de démo », proposée à l’instant après une fuite, avec la note « Le mot de passe actuel est apparu dans une fuite connue » et quatre étapes : générer un mot de passe neuf de 24 caractères gardé en révision en attente, se connecter à demo.serenity.test et remplacer le mot de passe, se reconnecter avec le nouveau pendant que l’ancien doit être refusé, valider ou revenir à l’ancien. Un doigt touche « Approuver ». Le message « Approuvée. L’agent la joue à son prochain passage. » s’affiche, puis la carte « Rotations en cours » dit « Site de démo : rotation en cours, changement sur le site, puis reconnexion pour preuve », sous la carte du kill switch en marche. Quand c’est fini, la cloche ouvre les notifications : « Site de démo, mot de passe changé par l’agent, preuve de connexion validée ». La fiche de Site de démo s’ouvre, confiée à l’agent ; l’œil révèle le nouveau mot de passe, 24 caractères jugés robustes, puis la fiche défile jusqu’à l’historique : mot de passe changé (version actuelle), confiée à l’agent (révision 2, gardée), entrée créée dans ta zone. À droite, trois colonnes avancent ensemble. Le coffre, zone agent : la révision 2 active, chiffrée par AK ; puis la révision 3 en attente, 24 caractères neufs, pas encore active, pendant qu’un appareil qui synchronise voit encore la 2 ; enfin la révision 3 active, prouvée par reconnexion, et la 2 dans l’historique. Au milieu, le conteneur rotator : le seul navigateur, aucune clé, aucun disque, une recette JSON par site, deux preuves signed_in et changed ; il fait POST /change pour se connecter puis changer, puis POST /verify pour se connecter de zéro. Le site de démo : connexion avec l’ancien (#signed-in est là), mot de passe changé (#changed est là), reconnexion avec l’ancien refusée (Identifiants incorrects), reconnexion avec le nouveau acceptée. Des légendes suivent chaque moment. En bas, cinq étapes : contrôles par le code (kill switch, zone, allowlist, plafond), 24 caractères tirés au hasard dans la mémoire de l’agent, bloc en attente en révision 3 sans toucher à l’entrée active, changer puis prouver par une reconnexion de zéro, valider : la 3 devient la bonne et la 2 part à l’historique.

Un site peut refuser, ne plus répondre, ou garder on ne sait quel mot de passe. Dans chaque cas, ton coffre garde celui qui ouvre le site : retour arrière si l'ancien passe encore, question posée au site si la réponse s'est perdue. Et si rien n'est sûr, l'agent ne devine pas : ta fiche te montre les deux mots de passe, et tu dis lequel marche.

Quand une rotation échoue, le coffre garde toujours le mot de passe qui ouvre le site. À gauche, le téléphone rejoue les vrais écrans, à droite trois cas s’allument l’un après l’autre. Cas 1, le nouveau ne passe pas mais le retour arrière marche : le bloc en attente en révision 3 pendant que l’entrée active reste la 2, la reconnexion refusée, l’ancien remis sur le site et vérifié, puis le coffre intact, bloc en attente supprimé ; statut rolled_back, notification rotation.failed. Le téléphone montre l’écran Agent avec la rotation en cours, puis un échec compté sur 30 jours et la prochaine rotation de Site de démo le 28 septembre, dans 1 jour ; la cloche ouvre la notification « Rotation annulée, rien n’a changé », et la fiche montre l’ancien mot de passe, la prochaine rotation le 28 septembre et la carte « Rotation demain, tous les 30 jours, avec ton accord ». Cas 2, la réponse au changement se perd (délai, erreur 5xx) : le site a peut-être pris le nouveau, alors l’agent lui demande, l’ancien puis le nouveau ; si l’ancien passe, retour arrière propre ; si le nouveau passe, la rotation va au bout ; si aucun ne passe, le bloc est gardé et tu tranches. Ici le nouveau passe, et le téléphone montre la notification « Mot de passe changé par l’agent, preuve de connexion validée ». Cas 3, le retour arrière échoue aussi : reconnexion refusée, l’ancien n’est pas confirmé non plus, le bloc est gardé et l’entrée signalée, statut failed, notification rotation.manual « Rotation à terminer toi-même ». La fiche affiche « Deux mots de passe pour cette entrée » : la rotation n’a pas pu être annulée, essaie de te connecter puis dis lequel marche, l’autre sera jeté ; celui du coffre et celui que l’agent a posé, en clair, chacun avec un bouton Garder. Le doigt garde celui de l’agent, le message « Gardé : celui de l’agent. » s’affiche, la fiche redevient normale et l’autre est jeté. En bas, trois règles : l’agent ne devine jamais, quand aucun mot de passe n’est confirmé il garde les deux et te demande ; après un échec il retente le jour suivant, pas toutes les heures ; chaque étape laisse une ligne au journal, jamais un mot de passe.

L'agent ne change que les sites qui ont une recette, un petit fichier JSON qui dit où sont les champs et ce qui prouve qu'une étape a marché. Pour l'instant, un seul en a une : le site de démo du dépôt, sur lequel la CI rejoue la rotation à chaque commit. make film l'enregistre de bout en bout, sans rien simuler que le rythme :

L'agent change un mot de passe sur le site de démo, de bout en bout : la veille trouve le mot de passe dans une fuite, l'agent propose, on approuve, le site change, et l'ancien mot de passe est refusé.

10 Les garde-fous

Avant chaque action, c'est du code qui décide, jamais un modèle de langage : kill switch, zone agent, plafond du jour, allowlist, puis une ligne au journal. Le kill switch se maintient une seconde. Relâché trop tôt, il revient ; tenu jusqu'au bout, l'agent s'arrête net et l'interface passe au gris. Certaines choses restent hors de portée pour de bon : pas de code par mail ni par SMS, pas de « mot de passe oublié », pas de CAPTCHA.

Le kill switch et les garde-fous, vérifiés par le code avant chaque action, jamais par un LLM. À gauche, le téléphone montre l’écran Agent : la carte du kill switch « En marche, l’agent veille, il surveille 1 entrée confiée et prépare les rotations », l’orbe violette, l’interrupteur Marche et Arrêt avec « Maintiens 1 s pour arrêter », et la phrase « Ce switch est relu avant chaque action : coupé, l’agent s’arrête net. Tes entrées ne changent pas. » Un doigt maintient l’interrupteur puis le lâche trop tôt : le remplissage recule et le pouce revient. Il le maintient une seconde entière : l’agent s’arrête, la carte dit « Arrêté, l’agent est arrêté, kill switch enclenché à l’instant, plus aucune veille ni rotation », l’orbe et la lumière passent au gris, la frise d’activité marque KILL SWITCH, et le message « Agent arrêté. Il ne fera plus rien jusqu’à ton feu vert. » s’affiche. Plus tard, le même geste le relance, avec « Maintiens 1 s pour relancer », et le message « Agent relancé. ». À droite, cinq contrôles dans l’ordre du code : le kill switch, relu en base avant chaque rotation ; la zone agent et ses trois verrous (l’échéancier, la création de rotation, une entrée reprise) ; le plafond quotidien, SERENITY_MAX_ROTATIONS_PER_DAY = 3 sur 24 heures ; l’allowlist, api/allowlist.yaml, avec demo.serenity.test seul par défaut, sous-domaines oui, faux suffixes non ; le journal d’audit, une ligne par décision, jamais un mot de passe. Quatre actions les traversent, et une carte dit ce qui se passe. La rotation de Site de démo passe les cinq contrôles. Pendant que l’agent est arrêté, son passage est arrêté au premier contrôle, journal skipped, kill_switch. Une quatrième rotation en 24 heures attend le lendemain, journal skipped, daily_limit. Une rotation vers netflix.com.evil.example est refusée par l’allowlist : avec netflix.com dans la liste, www.netflix.com passerait, netflix.com.evil.example non, car ce n’est pas un sous-domaine. Entre deux actions, la carte explique le geste : maintenir 1 s, relâché avant la fin il revient, tenu jusqu’au bout l’agent s’arrête ; le même geste relance, couper marche même coffre verrouillé mais relancer demande le coffre ouvert, donc le mot de passe maître, et les deux gestes vont au journal. En bas, ce que l’agent ne fera jamais : pas de code par mail ni SMS, pas de « mot de passe oublié », pas de CAPTCHA.

11 Codes et import

Les codes à deux facteurs se calculent sur ton appareil, à partir du secret rangé dans l'entrée : rien n'est demandé au serveur, et l'écran marche hors ligne. Un toucher copie le code, et Serenity vide le presse-papiers 30 secondes plus tard. Seule exception, voulue : une entrée confiée à l'agent, dont le serveur peut calculer le code lui aussi, pour se reconnecter pendant une rotation.

Les codes à deux facteurs, calculés sur ton appareil. À gauche, le téléphone rejoue les vrais écrans en temps réel : l’écran Codes 2FA, « Calculés ici, même hors ligne. Touche un code pour le copier. », avec son compte à rebours « Nouveaux codes dans 9 s » et la tuile de Netflix, marquée « Confié à l’agent » et « Le serveur peut aussi le calculer », qui affiche 098 234 ; un toucher copie le code, le bouton devient une coche et un message dit « Code de Netflix copié. Effacé dans 30 s. » ; les cinq dernières secondes passent en ambre, puis la fenêtre de 30 s bascule et le code devient 448 518 ; la fiche de Netflix, ouverte, montre le même code et le même compte à rebours ; puis retour à l’écran Codes, où une phrase dit que le serveur peut calculer les codes de la zone agent pour se reconnecter à ta place. À droite, le calcul RFC 6238 de web/src/lib/totp.ts avec un secret d’essai, rien demandé au serveur, ce qui marche hors ligne, en cinq cartes : le secret base32 JBSW Y3DP EHPK 3PXP, soit 10 octets ; le compteur, floor(t / 30), soit 59683681 pour t = 1790510430, écrit sur 8 octets ; le HMAC-SHA1 de Web Crypto avec le secret pour clé, 20 octets dont le dernier, 87, donne le décalage 7 ; la troncature, les 4 octets db ef e5 86 pris à ce décalage, sans le bit de signe, soit 1542448518 ; et les 6 derniers chiffres, 448 518. Les cartes s’allument l’une après l’autre quand le compteur avance. En bas, deux cartes : le presse-papiers, vide, puis le code copié avec « effacé dans 30 s » qui décompte, puis vidé 30 s après la copie, ou dès que la page reprend la main, et au verrouillage ; la zone agent, où ton appareil et le serveur calculent le même code, parce que le serveur lit le secret d’une entrée confiée, ce qui permet à l’agent de se reconnecter pendant une rotation.

Tu peux faire entrer tes mots de passe depuis Google ou Bitwarden, et tes codes depuis Google Authenticator. Le fichier est lu et chiffré sur ton appareil : le serveur ne reçoit que des blocs qu'il ne sait pas ouvrir, et tout arrive dans ta zone. Le format se reconnaît au contenu, pas au nom du fichier, et un code importé ne remplace jamais celui d'une entrée : il la rejoint si elle n'en a pas, sinon il devient sa propre entrée.

L’import, sans rien envoyer en clair. À gauche, la fenêtre de bureau rejoue trois imports dans Réglages, Import et export, dont le texte dit que le fichier est lu et chiffré sur cet appareil et que tout arrive dans « Protégé par toi » ; trois cartes : Mots de passe Google, Google Authenticator, Bitwarden. Premier import : un clic sur Mots de passe Google ouvre le sélecteur de fichiers, qui n’accepte que .csv et .json ; le fichier « Mots de passe Google.csv » est ouvert ; l’appli dit « 3 entrées prêtes à importer », puis « 3 entrées importées dans « Protégé par toi » ». Deuxième import : la carte Google Authenticator s’ouvre sur trois étapes, le lien otpauth-migration:// est collé puis lu ; l’appli trouve 2 codes, GitHub et Netflix, rappelle qu’un code rejoint l’entrée du même nom si elle n’en a pas encore, sinon devient sa propre entrée, et que rien n’est écrasé ; puis « 2 codes importés, dont 1 rattaché à une entrée existante ». Troisième import : un export Bitwarden chiffré, refusé par le message « Export chiffré : dans Bitwarden, choisis le format .json non chiffré, puis réessaie ». À droite, en haut, ce que contient la source : d’abord les trois sources lues ici ; puis le CSV, dont l’en-tête name, url, username, password, note se lit avant les lignes GitHub, Amazon et Forum, mots de passe masqués, avec deux constats : pas d’accolade au début, c’est le CSV de Google, et les colonnes se lisent d’après l’en-tête ; puis le lien de migration et le protobuf lu à la main, un compte de 38 octets, le secret de 10 octets, le nom Cybertrist, l’émetteur GitHub ; puis les deux comptes redevenus des liens otpauth://totp avec leurs secrets d’essai, SHA1, 6 chiffres, 30 secondes ; enfin le JSON Bitwarden, qui commence par une accolade, donc un export Bitwarden, mais dont encrypted vaut true, donc refusé. En dessous, la zone « Protégé par toi » se remplit : Banque et Spotify, puis GitHub, Amazon et Forum, puis GitHub gagne son code et Netflix, qui avait déjà le sien, devient une nouvelle entrée ; rien de plus après le refus. En bas, ce qui part vers le serveur, en quatre étapes qui s’allument à chaque import : lu ici, le fichier reste sur l’appareil, 5 Mo et 5 000 entrées au plus ; reconnu au contenu, un JSON est un export Bitwarden, le reste le CSV de Google ; chiffré sur place avec XChaCha20-Poly1305 et la clé UK, zone personnelle, révision 1 ; puis POST /api/vault/items, des blocs par lots de 500, le serveur en acceptant 1 à 1 000. L’export chiffré s’arrête en rouge à la deuxième étape.

12 La sauvegarde

Chaque nuit à 03:12, Serenity sauvegarde ce qui ne se reconstruit pas : la base, copiée par SQLite lui-même, et les deux clés du serveur, dans un instantané restic chiffré. Sans la clé serveur, la zone agent ne se rouvrirait jamais, alors elle part avec. Le mot de passe du dépôt, lui, se garde sur papier, jamais dans Serenity. Et comme une sauvegarde que personne n'a restaurée ne prouve rien, make backup-check détruit un coffre d'essai et le restaure, en CI, à chaque push.

La sauvegarde restic de chaque nuit, et l’exercice qui prouve qu’elle se restaure. À gauche, un terminal sur la VM rejoue trois commandes. D’abord make backup-now, qui lance sudo ops/backup.sh comme le timer de 03:12 : restic reprend l’instantané précédent, trouve 3 fichiers changés, enregistre l’instantané, applique la règle « garder 7 quotidiens, 4 hebdomadaires, 6 mensuels », en garde 15, en retire 1, puis affiche « backup: done ». Puis make restore-check, qui lance sudo ops/restore.sh --check : « base saine, 1 compte, 3 entrées, schéma 7 », et « vérification réussie, rien n’a été écrit hors du dossier temporaire ». Enfin make backup-check, qui lance scripts/backup-drill.sh : un coffre jetable avec un compte et deux entrées ; la sauvegarde, qui crée le dépôt, avec l’avertissement de restic sur le mot de passe dont la perte rend les données irrécupérables, lit 3 fichiers neufs et enregistre l’instantané ; « le coffre disparaît (disque perdu, VM effacée) » ; la restauration et la vérification, « base saine, 1 compte, 2 entrées, schéma 7 », les fichiers déposés dans un dossier temporaire, le rappel de la remise en place à la main, puis « base et clés restaurées, contenu vérifié ». À droite, en haut, chaque nuit à 03:12 : le timer serenity-backup.timer, à 5 minutes près, qui rattrape au démarrage si la VM était éteinte ; une copie cohérente de serenity.sqlite par VACUUM INTO, WAL compris, avec server.key, sans laquelle la zone agent ne se rouvre pas, et totp.key, pour les codes de connexion ; un instantané restic chiffré avant de quitter la VM, avec la rétention de 7 jours, 4 semaines et 6 mois dont les cases s’allument ; puis deux rappels : le mot de passe du dépôt se garde sur papier avec le kit, jamais dans Serenity, et la zone personnelle reste chiffrée par ta clé maître dans chaque instantané. En bas, l’exercice rejoué en CI à chaque push, en cinq étapes qui s’allument : coffre jetable, sauvegarde, destruction en rouge, restauration, vérification en vert ; et deux règles : rien n’est écrasé, la restauration dépose les fichiers dans un dossier et tu les remets en place la stack arrêtée ; la bonne clé ou rien, l’agent refuse de démarrer avec une clé serveur qui n’est pas celle de la base.

13 Architecture

Serenity tourne sur une VM Debian, en quatre conteneurs Docker, et un seul port est publié, sur 127.0.0.1. Ton téléphone y arrive par un accès privé en HTTPS, et n'envoie que des blocs déjà chiffrés. Seuls l'agent et le rotateur ont le droit de sortir sur Internet : l'un pour demander à Pwned Passwords, l'autre pour ouvrir le site dont il change le mot de passe.

L’architecture de Serenity, traversée par de vraies requêtes. À gauche, le téléphone : tu crées l’entrée GitHub, qui arrive dans « Protégé par toi » ; plus tard, la cloche annonce « Site de démo : rotation à valider » ; l’écran Agent montre les quatre étapes, générer, changer sur demo.serenity.test, prouver, valider ; tu approuves ; puis « Site de démo : mot de passe changé par l’agent ». Entre le téléphone et la VM, l’accès privé en HTTPS, tailscale serve chez moi. À droite, la VM Debian 13 sous Docker Compose, où rien n’écoute hors de 127.0.0.1. Quatre réseaux : edge, publié sur 127.0.0.1 ; internal et rotation, sans Internet ; egress, la seule sortie. Le conteneur web, nginx, sur edge et internal, sert l’appli et le proxy /api, publié sur 127.0.0.1:8080, le seul port de la stack. Le conteneur api, FastAPI, sur internal seulement, tient les comptes et le coffre, le flux /api/events et la seule clé totp.key. Le fichier serenity.sqlite, en mode WAL, garde les blocs chiffrés et les métadonnées, partagé par api et agent. Le conteneur agent, sur egress et rotation, ouvre la clé d’agent avec server.key, veille toutes les 6 heures et fait tourner l’échéancier toutes les heures. Le conteneur rotator, sur rotation et egress, est le seul navigateur, sans clé, sans base, sans port publié. Le site de démo, profil demo, sur edge et rotation, est le seul site de l’allowlist. Restic sauvegarde chaque nuit à 03:12 la base et les deux clés. En bas, Internet : un site de l’allowlist pour le rotateur, Pwned Passwords pour l’agent, qui n’envoie que 5 caractères d’empreinte ; ni web ni api ne sortent. Trois passages s’allument tour à tour : le bloc chiffré de la nouvelle entrée descend du téléphone à la base ; la veille passe de l’agent à Pwned Passwords puis remonte l’alerte au téléphone ; après ton accord, seul le chemin agent, rotator, site de démo s’allume, et le résultat remonte jusqu’au téléphone. Enfin, la sauvegarde de la nuit.

Voici ce qu'un attaquant lit selon ce qu'il obtient. Une sauvegarde sans son mot de passe, ou le réseau, ne lui donnent rien ; la base seule lui donne les métadonnées ; la base avec la clé serveur lui ouvre la zone agent. Le point dur est assumé : qui prend la VM prend la zone agent. Ta zone personnelle, elle, ne s'ouvre que dans un navigateur que tu as déverrouillé.

Le modèle de menace de Serenity, sans tableau. À gauche, six choses qu’un attaquant peut obtenir, tour à tour : une sauvegarde restic sans son mot de passe ; la base seule, une copie de serenity.sqlite ; la base et la clé serveur, qui vit hors de la base ; root sur la VM, en direct ; ton mot de passe maître ; le réseau entre ton téléphone et la VM. À droite, ton coffre tel qu’il vit sur la VM, en quatre cases : la zone personnelle, chiffrée par la clé UK qui ne quitte pas tes appareils, avec Banque et Messagerie ; la zone agent, chiffrée par la clé AK que la clé serveur ouvre, avec Netflix et Spotify ; les métadonnées, dates, zones, révisions et politiques de rotation ; l’appli servie, le code que web envoie au navigateur, sous une CSP stricte. Pour chaque cas, les cases qui s’ouvrent s’éclairent en ambre et montrent leur contenu, les autres restent du bruit chiffré. Une sauvegarde sans son mot de passe : rien, tout est chiffré une seconde fois par restic. La base seule : les métadonnées, aucun mot de passe, et chaque essai sur ta zone coûte un Argon2id à 64 Mio. La base et la clé serveur : la zone agent, pas la zone personnelle ; la parade est de changer la clé serveur, la clé d’agent et ces mots de passe. Root sur la VM : la zone agent, et il peut modifier le code servi pour attraper ton mot de passe maître à la prochaine saisie ; la zone personnelle tient tant qu’aucun appareil ne se déverrouille après sa prise de contrôle, et la V2 répond par une appli installée. Ton mot de passe maître : tout. Le réseau : rien, c’est du HTTPS sur un chemin privé. En bas, le point dur, assumé : qui prend la VM prend la zone agent, c’est le prix de l’agent ; la zone personnelle ne s’ouvre que dans un navigateur que tu as déverrouillé.

14 Les tests

Chaque push rejoue tout, en huit jobs. pytest passe 208 cas côté serveur et vitest 87 côté client. Un bloc chiffré par Python doit s'ouvrir en TypeScript, et l'inverse. Quinze parcours jouent le client contre le vrai serveur, Chromium parcourt tous les écrans sous une CSP stricte, et un coffre jetable est vraiment détruit puis restauré. Une route sans garde, ou un secret dans l'historique, et la CI casse.

Les tests de Serenity, qui tournent. À gauche, un terminal rejoue les cibles que la CI lance à chaque push : make test, 213 cas collectés, 208 passés et 5 laissés au site de démo ; make web-test, eslint, prettier, tsc et vite build propres, puis 87 tests vitest passés et 17 laissés à leur cible ; make crypto-interop, Python chiffre et TypeScript déchiffre, puis l’inverse ; make e2e, 15 parcours passés ; make ui-smoke, qui finit sur « UI smoke: OK » ; make rotation-demo, 5 tests passés ; make backup-check, le coffre disparaît puis revient, base et clés restaurées ; gitleaks, aucune fuite. À droite, ce que chacune prouve : les 18 fichiers de pytest qui se remplissent, de test_watch.py avec 28 tests à test_health.py avec 1, soit 188 fonctions de test ; test_route_guards, 46 routes, dont 9 publiques écrites une par une avec leur raison, 20 derrière une session et 17 derrière un coffre déverrouillé ; les 87 cas de vitest, par dossier, 33 dans src/vault, 15 dans src/lib, 13 dans src/crypto, 13 dans src/app, 13 dans src/features ; un bloc qui passe de Python, PyNaCl, à TypeScript, libsodium, puis revient, avec des nonces neufs ; les 15 parcours du vrai client contre la vraie API, 7 pour le compte, 5 pour le coffre, 2 pour la veille, 1 pour l’agent ; un téléphone qui parcourt les écrans comme ui-smoke, le coffre, la fiche Netflix, la confirmation « Confier à l’agent », la fiche confiée, le coffre délégué et l’écran Agent, avec les quatre passages, téléphone 390 × 844 avec 21 captures, bureau 1440 × 900 avec 24, thème clair avec 12, appli de bureau 1280 × 800 avec 5, soit 62 captures ; la rotation sur le site de démo, où le mot de passe change pour de vrai, où un refus du site ramène tout en arrière, où un site hors allowlist n’est jamais touché, et où l’inspection d’une page exige le jeton ; l’exercice de sauvegarde, un coffre jetable sauvegardé, détruit puis restauré avec ses deux clés ; gitleaks sur tout l’historique. En bas, les 8 jobs de ci.yml passent au vert l’un après l’autre : Backend, Frontend, Crypto interop, End-to-end, UI smoke, Rotation, Sauvegarde, Secret scan.

15 Versions et licence

La V1 est construite, mais rien n'est encore publié. Avant le premier tag, il reste à sortir le dépôt de sauvegarde de la VM, à faire relire la conception par quelqu'un d'extérieur, puis à poser la v0.1.0. Ensuite viendront l'appli Android, la rotation sur tes vrais sites et un agent plus autonome, sans date promise. La cryptographie et la licence, elles, ne bougent pas.

La feuille de route de Serenity, empilée. En bas, le socle qui ne bouge pas : la cryptographie, libsodium des deux côtés, Argon2id, XChaCha20-Poly1305, deux zones, des clés en mémoire et aucune primitive maison ; et la licence AGPL v3, qui garde le code ouvert même servi par le web, l’appli donnant le lien vers ses sources, hors ligne compris. Dessus tombe la V1, Coffre maison, construite mais rien de publié : coffre chiffré, API et appli web installable ; import Bitwarden, veille des fuites, délégation ; rappels, notifications maison, rotation sur le site de démo ; sauvegardes restic et appli de bureau Windows et Linux. Au-dessus, la v0.1.0, le premier tag, et ce qui la bloque encore : les clés de développement renouvelées le 20 septembre, cochées ; le dépôt restic à déporter hors de la VM ; une relecture extérieure, crypto.md d’abord ; et le tag lui-même. Tant qu’il manque, le CHANGELOG range tout sous « Non publié ». Puis arrivent, en pointillés et sans date, la V2, appli Android native en Kotlin avec libsodium, notifications avec Approuver et Refuser, sans service tiers ni icône permanente, et dont le code installé ne vient plus de la VM ; la V3, tes vrais sites, avec une recette par site pour le rotateur, l’inspection de page qui aide à l’écrire, une extension navigateur et avec elle une défense contre l’hameçonnage ; la V4, un agent LLM plus autonome, toujours encadré par des règles vérifiées par le code, jamais par un LLM, avec le kill switch avant chaque action. À gauche, la frise : socle, fait, ensuite, plus tard.

Serenity n'a pas encore été audité. N'y mets pas de comptes réels avant la version 0.1.0 et une relecture extérieure. La page sécurité dit ce qui a été vérifié et ce qui reste ouvert.

Pour signaler une faille, passe par SECURITY.md, jamais par une issue publique. Le périmètre est tenu court, alors ouvre une issue avant d'écrire du code : CONTRIBUTING.md explique comment lancer le projet et ce qu'il faut vérifier avant une pull request.

Serenity est conçu et développé par Tristan Joncour (@Cybertrist). Le code est publié sous licence GNU AGPL v3 ou ultérieure, copyright (C) 2026 Tristan. C'est le choix habituel pour un serveur auto-hébergé, celui du serveur Bitwarden entre autres : qui fait tourner une version modifiée de Serenity pour d'autres personnes doit en publier le code. Les polices Geist, Geist Mono et Syne sont sous licence SIL Open Font, les icônes Phosphor sous licence MIT. Serenity est distribué sans aucune garantie.


Les images de cette page ne sortent d'aucun logiciel de dessin. Les bandeaux et les boutons sont des pages HTML que Chrome capture ; les schémas sont des SVG animés écrits par script, qui redessinent les vrais écrans de l'appli. Tout est dans docs/tools, et bash docs/tools/tout.sh refait tout, dans les deux langues.

About

Gestionnaire de mots de passe auto-hébergé, chiffré de bout en bout, avec un agent qui surveille les fuites et change les mots de passe qu'on lui confie.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages