fix(sync): never regenerate the device identity on a failed certificate load (#135)#138
Conversation
…te load (#135) The bridge used upstream LoadOrGenerateCertificate, which falls back to generating a fresh key pair whenever loading cert.pem/key.pem fails. On iOS a transient read failure (file protection before first unlock, corruption after a hard crash) therefore silently minted a new device ID — invalidating every peer pairing and surfacing server-side as an endless stream of new pending devices. A #135 field report burned five identities in one day during a crash loop. What could go wrong and why this is safe: the new loadOrCreateIdentity generates only when both identity files are verifiably absent (true first launch — unchanged behavior there). Corrupt, partial, or unreadable state now fails the engine start with an error instead of regenerating; the existing files are never rewritten, so recovery after a transient outage is automatic on the next start. The failure is user-visible through the established SyncUserError path ("Could Not Start Sync"), matching the manual-recovery doctrine (decision 002): a visible failed start is recoverable, a silently replaced identity is not. Stat errors are treated as unknown state, not as absence, so an iOS file-protection outage cannot trigger generation either. Regression tests (#135): identity stability across restarts, corrupt certificate, partial identity (either file missing), and an unreadable directory each fail closed without touching the surviving files, and the original identity loads again once access returns. Verified: cd go && make patch && go test -tags noassets ./bridge -count=1 green (56s), gofmt clean. Requires an xcframework rebuild before the next archive (macOS). Decision record: docs/decisions/026-device-identity-fail-closed.md.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📜 Recent review details⏰ Context from checks skipped due to timeout. (4)
📝 WalkthroughWalkthroughThe bridge now preserves device identity across restarts, generates certificates only on confirmed first launch, and fails startup instead of regenerating identities when stored certificate or key files are corrupt, partial, or unreadable. ChangesDevice identity continuity
Estimated code review effort: 3 (Moderate) | ~20 minutes Poem
🚥 Pre-merge checks | ✅ 7✅ Passed checks (7 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
* chore(release): prepare VaultSync 2.0.1 build 37 Version bump only: app + widget to 2.0.1/37 in ios/project.yml, and the CHANGELOG [Unreleased] section renamed to [2.0.1] — 2026-07-22. The only shipped change since 2.0.0 is the device-identity fail-closed fix (#135, merged as #138), which lives in go/bridge and therefore requires an xcframework rebuild before the archive — rebuilt and verified during this prep. What could go wrong and why this is safe: a skewed widget version or a missing CHANGELOG section would fail version-check.yml on the v2.0.1 tag; both were verified locally with the workflow's own checks (2x 2.0.1, single build number 37, section present). Full verification on this tree: Go bridge suite green (real Syncthing instances), gofmt clean, xcframework rebuilt from the patched tree, full Xcode plan 431/431 passed with zero failures/skips (xcresulttool-verified), strings key parity (884 keys) and design-token lint green. No StoreKit, entitlement, bundle-ID, or signing values were touched. * fix(deps): resolve GO-2026-5970 by bumping golang.org/x/text to 0.39.0 The merge-blocking govulncheck gate on PR #139 flagged GO-2026-5970 (infinite loop on invalid input in x/text's Unicode normalization, fixed in 0.39.0): the bridge reaches norm.Form via StartSyncthing -> suture.Supervisor.ServeBackground and syncthing.EnsureDir. x/sys 0.46.0 and x/tools 0.47.0 move along as transitive requirements of the bump. What could go wrong and why this is safe: x/text is an indirect, API-stable dependency of the embedded Syncthing; no code changes were needed. Verified with the pinned scanner and the full suites on this tree: govulncheck v1.6.0 reports 0 called vulnerabilities, Go bridge suite green (real Syncthing instances), gofmt clean, xcframework rebuilt, and the full Xcode plan re-run against that framework: 431/431 passed, 0 failed, 0 skipped (xcresulttool-verified). The CHANGELOG 2.0.1 section gains the Security entry; no StoreKit, entitlement, bundle-ID, or signing values were touched.
The bridge used upstream LoadOrGenerateCertificate, which falls back to
generating a fresh key pair whenever loading cert.pem/key.pem fails. On
iOS a transient read failure (file protection before first unlock,
corruption after a hard crash) therefore silently minted a new device
ID — invalidating every peer pairing and surfacing server-side as an
endless stream of new pending devices. A #135 field report burned five
identities in one day during a crash loop.
What could go wrong and why this is safe: the new loadOrCreateIdentity
generates only when both identity files are verifiably absent (true
first launch — unchanged behavior there). Corrupt, partial, or
unreadable state now fails the engine start with an error instead of
regenerating; the existing files are never rewritten, so recovery after
a transient outage is automatic on the next start. The failure is
user-visible through the established SyncUserError path ("Could Not
Start Sync"), matching the manual-recovery doctrine (decision 002): a
visible failed start is recoverable, a silently replaced identity is
not. Stat errors are treated as unknown state, not as absence, so an
iOS file-protection outage cannot trigger generation either.
Regression tests (#135): identity stability across restarts, corrupt
certificate, partial identity (either file missing), and an unreadable
directory each fail closed without touching the surviving files, and
the original identity loads again once access returns.
Verified: cd go && make patch && go test -tags noassets ./bridge
-count=1 green (56s), gofmt clean. Requires an xcframework rebuild
before the next archive (macOS).
Decision record: docs/decisions/026-device-identity-fail-closed.md.
Summary
Go bridge tests and formatting checks pass. An xcframework rebuild is required before the next macOS archive.