Vos notes restent dans votre poche. Chiffrées, et hors ligne.
v2.0.0 — Août 2026 · Politique de confidentialité · CGU · Sécurité
Application Android Flutter de prise de notes Markdown chiffrées,
100 % locale, zéro permission Internet. Interface bilingue FR / EN.
Coffres par dossier (passphrase Argon2id ou PIN Keystore-bound), recherche
plein-texte FTS5, dictée vocale Whisper on-device, backlinks [[note]],
mode panique multi-step.
L'IA embarquée est retirée. La recherche sémantique (MiniLM) et le Q&A « Demander à mes notes » (Gemma 3 1B) disparaissent. L'APK arm64 passe de ~127 Mo à 26,9 Mo. La recherche reste plein-texte FTS5 + bm25 : rapide et exacte, mais elle ne devine pas les synonymes.
Bump majeur et non mineur pour trois raisons :
- l'app perd ses deux fonctions phares, ce qu'un
1.2.0ne signalerait pas à l'utilisateur ; - l'APK est divisé par près de cinq, visible dès l'installation ;
- la migration est à sens unique : la base passe en schéma v9 et la
v1.1.6 publiée n'a pas de
onDowngrade, donc réinstaller une 1.x ne peut plus ouvrir la base.
Cette version inaugure aussi les APK splits par ABI sans universel.
Les deux familles ont des versionCode incompatibles — l'offset
+1000 × index de Flutter place tout universel sous n'importe quel
split de même version, et Android refusait alors l'installation en
downgrade. Transition à sens unique : ne pas réintroduire d'universel.
Corrections notables du même cycle :
- supprimer un dossier détruisait la clé Keystore avant la base, ce qui rendait un coffre définitivement illisible si la base échouait ;
- le mode panique annonçait « effacement terminé » même en cas d'échec ;
- les migrations de schéma n'avaient jamais tourné — les tests
créaient des bases neuves, donc
onUpgraden'était jamais appelé ; - le titre d'une note de coffre pouvait partir en clair.
Pour penseurs, thérapeutes, étudiants, chercheurs, écrivains et journalistes qui veulent prendre des notes sensibles ou denses sans qu'elles ne quittent jamais leur téléphone.
Différenciateur unique vs Notesnook / Obsidian / Bear / Logseq : aucune permission Internet — l'application est techniquement incapable d'envoyer quoi que ce soit, et c'est vérifiable dans son manifeste.
- Aucune permission
INTERNETdans le manifeste — vérifiable à l'œil nu (AndroidManifest.xml). Les 7 permissions transitives (INTERNET, ACCESS_NETWORK_STATE, WAKE_LOCK, RECEIVE_BOOT_COMPLETED, FOREGROUND_SERVICE, FOREGROUND_SERVICE_DATA_SYNC, POST_NOTIFICATIONS) sont neutralisées viatools:node="remove". - Seule permission runtime :
RECORD_AUDIOsi vous activez la dictée. - Aucun compte, aucune inscription, aucun tracker, aucune publicité, aucune télémétrie.
- Open source Apache 2.0, code intégral vérifiable.
allowBackup=false+dataExtractionRulescomplet (pas d'exfiltration via Smart Switch ou Android Backup).- Modèle Whisper importé via SAF — jamais bundlé, jamais téléchargés en réseau par l'app.
- Création / édition / auto-save debounced
- Épingler / favoris / archives / corbeille (rétention 30 j)
- Mode clair / sombre / système (palette GitHub)
- Tri configurable (modifié, créé, titre)
- Mode passphrase — Argon2id (m=64 Mo, t=3) + AES-256-GCM, KEK enveloppée par la clé maître Keystore (hardware-backed).
- Mode PIN — 4 à 6 chiffres, dérivation Argon2id allégée (m=32 Mo, t=2) + clé Keystore-bound dédiée par coffre, auto-wipe à 5 tentatives échouées (atomique, repris au boot si crash en cours).
- AAD partout :
folder_idlié au wrap KEK,note_idlié au contenu chiffré — anti rejeu / anti substitution. - HMAC verifier en temps constant pour détecter passphrase incorrecte sans déchiffrer toutes les notes.
- Auto-lock configurable (15 min par défaut, ou au pause).
- FTS5 instantané (tokenizer
unicode61, diacritiques normalisés).
- Whisper on-device via le module sibling
files_tech_voice. - Modèles Whisper Base q5_1 (57 Mo) ou Tiny q5_1 (32 Mo), importés via SAF (téléchargement par le navigateur système, pas par l'app).
- Vérification SHA-256 systématique avant chargement, cache TTL 30 j.
- Audio jamais persisté (tmp + delete dans tous les chemins).
- Liens
[[Titre]], auto-complétion, panneau Mentions / liens sortants. - Réindexation différée 2 s : seule la note modifiée est retraitée (O(1) par save, batching transparent).
- Liens fantômes auto-résolus à la création / au renommage de la cible.
- Export d'une note :
.mdavec frontmatter YAML compatible Obsidian, Logseq, Bear, Foam, Dendron. - Export ZIP global : arborescence par dossier + README d'export.
- Encodage en isolate (
compute()), nom de fichier durci anti-path- traversal et anti-Unicode-bidi.
- Réglages → Mode panique → Tout effacer maintenant.
- Confirmation par mot tapé (
EFFACER). - Séquence ordonnée et best-effort (un step qui throw n'interrompt
pas les suivants) :
FLAG_SECUREforcé ON- Capture micro coupée
foldersLockAll— verrouille tous les coffres ouvertspinKeysWipe— supprime toutes les clés Keystore PIN (deleteKeysWithPrefixcôté Kotlin)kekDestroy— détruit la clé maître Keystore (DB instantanément illisible)- Pause des background workers
dbWipe— écrase header SQLCipher 16 Mo + delete + sidecars- Effacement Whisper, préférences, tmp
- Activé par défaut : pas de capture d'écran ni d'aperçu dans les apps récentes.
- DB SQLCipher chiffrée AES-256-GCM, clé maître scellée par AndroidKeystore (hardware-backed sur S24).
- KEK Keystore-bound CSPRNG 32 octets, dérivation Argon2id côté passphrase, scellage Keystore direct côté PIN.
- AAD partout :
folder_idpour le wrap KEK,note_idpour le contenu — empêche la réutilisation d'un blob chiffré dans un autre contexte. - HMAC verifier en temps constant pour détecter une mauvaise passphrase / un mauvais PIN sans test exhaustif des notes.
- Mode PIN avec auto-wipe : 5 tentatives, flag prefs atomique, reprise au boot si interruption.
- Mode panique ordonné :
foldersLockAll → pinKeysWipe → kekDestroy → dbWipe, garantit que la KEK disparaît avant la base. - Wipe DB header 16 Mo (la KEK destroy précédente garantit déjà le
secret ; l'écrasement complet n'apporte rien sur eMMC moderne avec
wear-leveling — décision de design, voir
SECURITY.md). setUserAuthenticationRequired(false)sur la clé Keystore PIN : le PIN applicatif est l'unique facteur, le doubler avec biométrie l'exposerait à la contrainte (clé biométrique survit au reboot).- FLAG_SECURE par défaut.
allowBackup=false,dataExtractionRulesdurci.
Voir SECURITY.md pour le modèle de menace complet et
la procédure de signalement de faille.
| Permission | État | Usage |
|---|---|---|
INTERNET |
REMOVED (tools:node="remove") |
aucun |
ACCESS_NETWORK_STATE |
REMOVED | aucun |
WAKE_LOCK |
REMOVED | aucun |
RECEIVE_BOOT_COMPLETED |
REMOVED | aucun |
FOREGROUND_SERVICE |
REMOVED | aucun |
FOREGROUND_SERVICE_DATA_SYNC |
REMOVED | aucun |
POST_NOTIFICATIONS |
REMOVED | aucun |
RECORD_AUDIO |
runtime, opt-in | uniquement si dictée Whisper activée |
À auditer sur chaque release via aapt dump permissions.
Deux options :
- APK publiée : récupérer le split correspondant à votre appareil
sur GitHub Releases
—
arm64-v8acouvre la quasi-totalité des téléphones depuis 2016 — vérifier le SHA-256 publié dans les notes de release, side-loader. Ces APK sont construites et signées par GitHub Actions (.github/workflows/release.yml), déclenchées au push d'un tagv*. - Build local (recommandé pour audit) — voir section suivante.
Pas de Play Store : distribution side-load uniquement (cohérent avec la promesse de confidentialité — aucun compte requis pour installer).
flutter pub get
flutter analyze
flutter test
flutter build apk --release --split-per-abi --obfuscate \
--split-debug-info=build/symbolsPour une release strictement signée (pas de fallback debug), créer
android/key.properties :
storeFile=/chemin/absolu/vers/votre.jks
storePassword=...
keyAlias=...
keyPassword=...
APK release arm64 : ~27 Mo (SQLCipher + Whisper.cpp — le modèle de dictée est téléchargé séparément, non bundlé). L'IA embarquée a été retirée en v2.0.0 : elle pesait 100 Mo pour une fonctionnalité que presque personne ne pouvait atteindre sans permission Internet.
Pré-requis :
- Flutter 3.x (Dart
^3.11.5) - Android SDK + NDK installés via Android Studio
- Module sibling
files_tech_voiceà../files_tech_voice(clone le repo à côté denotes_tech/)
lib/
├── main.dart # bootstrap parallèle + DI Provider
├── app.dart # MaterialApp
├── core/ # constants, exceptions, theme
├── data/
│ ├── models/ # Note, Folder, NoteLink,
│ │ NoteChangeEvent
│ ├── db/ # SQLite (FTS5 + sqlcipher), DAOs
│ └── repositories/ # façades + streams typés
├── services/
│ ├── security/ # VaultService (KEK Keystore +
│ │ passphrase/PIN), PanicService
│ ├── secure_window_service.dart # FLAG_SECURE via MethodChannel
│ ├── voice/ # VoiceService (Whisper, sibling)
│ ├── backlinks_service.dart # parsing [[]], reindex différé
│ ├── note_actions.dart # actions UI réutilisables
│ └── settings_service.dart
├── ui/
│ ├── screens/ # home, editor, search, settings,
│ │ about, vault_unlock
│ └── widgets/ # NoteCard, BacklinksPanel, ...
└── utils/ # debouncer, text_utils, error_localize
- Flutter 3.x / Dart
^3.11.5 sqflite_sqlcipher(SQLite chiffré AES-256 + FTS5)flutter_secure_storage(KEK scellée AndroidKeystore)cryptography(Argon2id RFC 9106 + AES-GCM, Dart pur)crypto(SHA-256 streaming pour vérification modèles)files_tech_voice(sibling, Whisper STT)provider,shared_preferences,archive,share_plus,url_launcher- Aucune dépendance réseau
- Samsung Galaxy S24 / S24 FE (validés)
- Samsung S9, POCO C75 (validés en mode dégradé)
- minSdk 23 (Android 6+)
Apache License 2.0 — voir aussi NOTICE et
THIRD_PARTY_NOTICES.md.
Notes Tech fait partie de la suite Files Tech (toutes 100 % locales) :