Tighten parsing, error handling, and Keychain hygiene#3
Merged
felizvida merged 2 commits intoMay 25, 2026
Conversation
- help_search_service: add 120s timeout to OpenAI response body read
(same bug the notebook_search_service fix already addressed)
- backup_models.BackupRunManifest.fromJson: raise a clear FormatException
when createdAt is missing or non-string instead of throwing TypeError;
reuse the parsed value for completedAt fallback
- backup_models.RenderNotebook.fromJson: tolerate missing/invalid createdAt
using DateTime.tryParse + epoch fallback
- backup_models.RenderNode/RenderPart.fromJson: null-safe id casts so
a corrupt render_notebook.json no longer breaks the whole load
- labarchives_client.downloadNotebookBackup: wrap partial.delete() cleanup
in try/catch so a failed unlink can't replace the original download
error with an unrelated cleanup error
- backup_service._classifyBackupError: replace lower.contains('auth')
(which also matched 'author', 'authority') with the narrower 'authoriz',
'auth_code', and 'auth code' tokens
- backup_service._withBackupLock: wrap lock.unlock() so a failure can't
skip lock.close() and leak the file handle
- ios AppDelegate: pin kSecAttrSynchronizable=false so credentials never
sync to iCloud Keychain
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third audit pass focused on three classes of issue: defensive JSON parsing for ledger/render files, error propagation through cleanup paths, and a Keychain hygiene fix on iOS.
Safety / correctness
help_search_service.dart— the OpenAI body read had no timeout; only the header exchange was time-bounded. Mirrors the fix already innotebook_search_service.dart(PR Fix 12 issues from second code audit #2) — the same caller pattern is duplicated here. Adds a 120 s body-read timeout.backup_models.BackupRunManifest.fromJson— hard castjson['createdAt'] as StringraisedTypeErroron a corrupt or older manifest. Now raises a clearFormatException("Backup run manifest is missing required datecreatedAt") and re-uses the parsed value for thecompletedAtfallback so the field is only parsed once.backup_models.RenderNotebook.fromJson— same hard-cast pattern oncreatedAt; replaced withDateTime.tryParse+ epoch fallback so an old render file no longer prevents the rest of the index from loading.backup_models.RenderNode.fromJson/RenderPart.fromJson—json['id'] as intis the only remaining hard cast for required fields; switched toas int? ?? 0for parity with the other safe casts in the same constructors.labarchives_client.downloadNotebookBackup— in the catch block,partial.delete()runs unwrapped. If the unlink fails (e.g. transient FS error on Windows after a connection drop), the unlink exception replaces and hides the original download error, so the user sees a misleading message andrethrowis never reached. Wrap the cleanup in try/catch.backup_service._classifyBackupError— the finallower.contains('auth')branch was meant to catch authorization-related errors but also matches"author","authority","co-author", etc. Replaced with the narrower'authoriz','auth_code', and'auth code'tokens.backup_service._withBackupLock— iflock.unlock()throws (rare but possible on remote filesystems),lock.close()is skipped and the file handle leaks. Wrap unlock in try/catch so close always runs.ios/Runner/AppDelegate.swift— explicitly pinkSecAttrSynchronizable = falseon every Keychain query. The default on iOS is already non-synchronizable, but pinning it makes the read/write/delete queries unambiguous: BenchVault credentials are never eligible for iCloud Keychain sync, and a synchronizable item under the same service/account from older code (or another app) would not silently shadow the local one.Test plan
flutter analyzeis cleanTimeoutExceptioninstead of hangingbackups/.../run.jsonby removingcreatedAt, reload manifests — confirm aFormatExceptionwith a clear message, not aTypeErrorBackupFailureCategory.authorization🤖 Generated with Claude Code