Skip to content

Commit aed1e86

Browse files
Patclaude
andcommitted
release: v1.0.7 — hardening 4-axes (perf/qual/sécu/UI) + icône Files Tech
Audit expert 4 sous-agents (security/code/perf/UI) sur la base v1.0.6. 22 améliorations livrées, format DB inchangé, 64/64 tests verts, flutter analyze 0 erreur 0 warning. ## Performance - C1 IndexingService → embedAsync MiniLM (worker isolate, main thread fluide pendant l'indexation initiale de 500+ notes) - H1 EmbeddingsDao.deleteOrphans via SQL `NOT IN` (-200 Ko Dart alloc) - H2 Pré-charge liveMap dans _indexAll (-3 à -8 s sur 1000 notes) - M1 _writeDebounce 1s → 3s (anti CPU permanent pendant frappe) - M2 PRAGMA cache_size 8 → 32 Mo + mmap_size 64 Mo - M3 ListView cacheExtent 600 + ValueKey(note.id) sur NoteCard - M5 FlutterGemma.initialize() parallélisé (-50 à -150 ms cold start) - L5 android.enableR8.fullMode=true (-5-15% DEX) ## Qualité code - C1 dispose MiniLM dans EmbedderCoordinator._downgrade (fuite 25 Mo + isolate) - H1 _ChatTurn.dispose() + helper _disposeAllTurns (fuite ValueNotifier) - H2 Flush save dispose chaîné sur _pendingSave (anti 2 UPDATE concurrents) - H3bis Atomic tmp+rename _ensureModelOnDisk + sidecar `.size` - H4 Fast-path sidecar AVANT rootBundle.load (pic RAM 25 Mo évité) - M1 Worker MiniLM shutdown propre (ACK + session.release avant kill) ## Sécurité (0 Critical / 0 High résiduel) - M-01 BacklinksService skip isLocked + purge note_links (anti leak titre via index target_title / backlinks entrants) - M-02 Mask titre RAG chip pour notes locked (défense en profondeur) - M-03 setUnlockedDeviceRequired(true) Keystore API 28+ - M-05 Lockout exponentiel PIN monotonique (1/2/4/8/16/30 s, anti observation passive) — VaultLockoutInProgressException avec remainingMs pour UI ## UI / Design / A11y - I1 SecureWindow dynamique (pattern Pass Tech v2.3.8) — refcount Kotlin + Dart SecureWindowGuardMixin appliqué sur vault sheets (PIN + passphrase create/unlock), ai_chat_screen, note_editor en mode vault. FLAG_SECURE forcé sur les écrans sensibles même si la pref globale est désactivée. - I2 fontSize raw → theme.textTheme.bodyMedium/titleLarge dans vault sheets + warning banner (respect MediaQuery.textScaler pour a11y) ## Icône - Remplacement par notes-tech.png (Files Tech assets), mipmaps régénérées via flutter_launcher_icons. ## Reportés v1.1 (format-breaking, inchangé) F16 AAD Keystore wrap · F6 race aliveIds vs write définitive F3 chiffrement title vault · A5 Argon2id calibration runtime Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 45b6d71 commit aed1e86

33 files changed

Lines changed: 519 additions & 111 deletions

android/app/src/main/kotlin/com/filestech/notes_tech/KeystoreBridge.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,15 @@ class KeystoreBridge(@Suppress("unused") private val ctx: Context) : MethodCallH
145145
.setKeySize(256)
146146
.setRandomizedEncryptionRequired(true)
147147
.setUserAuthenticationRequired(false)
148+
// v1.0.7 sécu M-03 — défense en profondeur : exige que l'écran
149+
// soit déverrouillé pour utiliser la clé (API 28+). Empêche
150+
// qu'un attaquant avec un debug bridge USB sur un device verrouillé
151+
// ne puisse exercer la clé pour brute-forcer le PIN du coffre
152+
// hors-bande. Idempotent : ne change pas les clés existantes,
153+
// seules les nouvelles créations bénéficient du flag.
154+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
155+
b.setUnlockedDeviceRequired(true)
156+
}
148157
if (strongBox && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
149158
b.setIsStrongBoxBacked(true)
150159
}

android/app/src/main/kotlin/com/filestech/notes_tech/MainActivity.kt

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import io.flutter.plugin.common.MethodChannel
1717
* démarrer.
1818
* - Le canal `notes_tech/secure_window` permet à Flutter de basculer
1919
* le flag dynamiquement quand l'utilisateur change le réglage.
20+
*
21+
* v1.0.7 UI I1 — refcount de "force enabled" (pattern Pass Tech v2.3.8) :
22+
* Certains écrans très sensibles (vault unlock sheets, ai_chat avec
23+
* contexte RAG, éditeur de note déchiffrée) doivent forcer FLAG_SECURE
24+
* même si l'utilisateur l'a désactivé globalement dans les préférences.
25+
* Le refcount permet d'imbriquer plusieurs forces (ex: vault sheet
26+
* pendant ai_chat) et de ne restaurer la pref qu'au dernier `restore()`.
27+
* Le flag effectif est : `forcedCount > 0 OR userPref`.
2028
*/
2129
class MainActivity : FlutterActivity() {
2230

@@ -28,13 +36,16 @@ class MainActivity : FlutterActivity() {
2836
private const val PREF_KEY = "flutter.secure_window_enabled"
2937
}
3038

39+
private var userPrefEnabled: Boolean = true
40+
private var forcedCount: Int = 0
41+
3142
override fun onCreate(savedInstanceState: Bundle?) {
3243
super.onCreate(savedInstanceState)
3344
// Lecture précoce — on respecte la préférence dès le 1er frame.
3445
// Activé par défaut tant que rien n'est encore persisté.
3546
val prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
36-
val enabled = prefs.getBoolean(PREF_KEY, true)
37-
applySecureFlag(enabled)
47+
userPrefEnabled = prefs.getBoolean(PREF_KEY, true)
48+
applyEffective()
3849
}
3950

4051
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
@@ -43,8 +54,25 @@ class MainActivity : FlutterActivity() {
4354
.setMethodCallHandler { call, result ->
4455
when (call.method) {
4556
"setEnabled" -> {
57+
// Met à jour la pref utilisateur (depuis Settings).
58+
// Le flag effectif tient compte du refcount forcé.
4659
val enabled = call.argument<Boolean>("enabled") ?: true
47-
runOnUiThread { applySecureFlag(enabled) }
60+
userPrefEnabled = enabled
61+
runOnUiThread { applyEffective() }
62+
result.success(null)
63+
}
64+
"forceEnabled" -> {
65+
// Refcount + force du flag pour la durée d'un écran
66+
// sensible. Idempotent côté caller via refcount Dart.
67+
forcedCount += 1
68+
runOnUiThread { applyEffective() }
69+
result.success(null)
70+
}
71+
"restore" -> {
72+
// Décrémente le refcount ; ne descend jamais < 0
73+
// pour tolérer un dispose double / pop forcé.
74+
if (forcedCount > 0) forcedCount -= 1
75+
runOnUiThread { applyEffective() }
4876
result.success(null)
4977
}
5078
else -> result.notImplemented()
@@ -54,9 +82,13 @@ class MainActivity : FlutterActivity() {
5482
registerKeystoreBridge(applicationContext, flutterEngine.dartExecutor.binaryMessenger)
5583
}
5684

57-
private fun applySecureFlag(enabled: Boolean) {
85+
private fun applyEffective() {
86+
// Flag effectif = pref utilisateur OU au moins un écran sensible
87+
// a demandé le force. Tant que `forcedCount > 0`, le flag reste ON
88+
// quelle que soit la préférence.
89+
val effective = userPrefEnabled || forcedCount > 0
5890
val flag = WindowManager.LayoutParams.FLAG_SECURE
59-
if (enabled) {
91+
if (effective) {
6092
window.setFlags(flag, flag)
6193
} else {
6294
window.clearFlags(flag)
-8.12 KB
Loading
-4.14 KB
Loading
-10.1 KB
Loading
-16.7 KB
Loading
-24.7 KB
Loading
-2.54 KB
Loading
-1.19 KB
Loading
-3.58 KB
Loading

0 commit comments

Comments
 (0)