cc9b66d963a4

fix the second, independent bug blocking Codex restore: a malformed regex in VaultRepository.restoreFromCodex - the pattern's trailing wrapper-object close-brace was unescaped (\s*}\s*$ instead of \s*\}\s*$), which Android's on-device regex compiler rejects as a syntax error while the host JVM's regex engine silently tolerates it. This meant restoreFromCodex has likely never worked on a real device regardless of decode success - confirmed via a temporary debug log showing UnlockResult.Error with the exact 'Syntax error in regexp pattern' exception, and confirmed the existing VaultRepositoryExtraTest covering this path passes on the host JVM (explaining why it was never caught) despite failing identically every time on-device. Escaped the brace. Full end-to-end live verification: generated a fresh Codex PDF from a 5-entry vault (Pro-gated feature, activated with the real license key), saved it, navigated to Restore from Codex, imported the exact same file - 76/76 codes decoded (building on the fork's earlier vector-rendering fix), entered the master password, tapped restore, relaunched the app fresh, unlocked, and confirmed all 5 original entries present by name (Recovery Notes, Fastmail, Old Bank, Google with TOTP, GitHub). Codex generate+restore is now fully confirmed working, closing out the last unverified vault import/export format.

time
2026-07-24 15:49 UTC · 3 months ago
machine
koh2
agent
claude-sonnet-5
session
critical-corrections-c1-c7
parent
c6e41d31ec0a
1 file app/src/main/java/com/peachpasswords/vault/VaultRepository.kt+7 −1
app/src/main/java/com/peachpasswords/vault/VaultRepository.kt+7 −1
@@ -97,7 +97,13 @@9797     suspend fun restoreFromCodex(vaultBlobBase64: String, password: String): UnlockResult = mutex.withLock {9898         return try {9999             val wrapperJson = String(Base64.getUrlDecoder().decode(vaultBlobBase64), Charsets.UTF_8)100-            val slotJson = Regex("\"a\"\\s*:\\s*(\\{.*\\})\\s*}\\s*$").find(wrapperJson)?.groupValues?.get(1)100+            // The trailing `}` (the wrapper object's own close-brace, after the101+            // captured inner slot object) must be escaped — Android's regex102+            // compiler rejects a bare, unescaped `}` outside a quantifier103+            // context as a syntax error, so this never matched anything and104+            // Codex restore always failed with a generic "invalid_codex_blob"105+            // regardless of whether the codes themselves decoded correctly.106+            val slotJson = Regex("\"a\"\\s*:\\s*(\\{.*\\})\\s*\\}\\s*$").find(wrapperJson)?.groupValues?.get(1)101107                 ?: return UnlockResult.Error("invalid_codex_blob")102108             val slotA = VaultCodec.decodeSlot(slotJson) ?: return UnlockResult.Error("invalid_codex_blob")103109             if (!store.writeSlotA(slotA)) return UnlockResult.Error("storage_write_failed")

view file