git.iko.soy/parseable-macos
on master help sign in
Fix keychain persistence by removing data-protection keychain requirement

The data-protection keychain requires keychain-access-groups entitlements which are incompatible with ad-hoc code signing (CODE_SIGN_IDENTITY: '-'). All Keychain operations were silently failing, so connections were never actually saved. Using the file-based login keychain (default) instead.

commit741d0462392daa3a6aba4a03dd1a2a82bba619f9 authoriko <mail@iko.soy> dateMon 11 May 2026, 06:19 (5 months ago) parent1facd74
4 files changed+6−19
CLAUDE.md+3−3 ParseableViewer.xcodeproj/project.pbxproj+1−1 ParseableViewer/ParseableViewer.entitlements+1−10 ParseableViewer/Services/KeychainService.swift+1−5
CLAUDE.md+3−3
@@ -31,7 +31,7 @@ CI builds on `macos-15` runners via `.github/workflows/build.yml`, producing arm
3131- **Concurrency**: Swift structured concurrency (`async/await`, `@MainActor`, strict `Sendable` conformance)
3232- **Networking**: `URLSession` async/await, no external dependencies
3333- **Project generation**: XcodeGen from `project.yml`
34−- **Persistence**: Keychain (data-protection) for connections and passwords; `UserDefaults` for saved queries
34+- **Persistence**: Keychain (file-based login keychain) for connections and passwords; `UserDefaults` for saved queries. Do not use `kSecUseDataProtectionKeychain: true` — it requires `keychain-access-groups` entitlements that are incompatible with ad-hoc code signing and causes writes to silently fail.
3535
3636### Source layout
3737
@@ -83,10 +83,10 @@ Live tail in Parseable uses gRPC Arrow Flight streaming. This app approximates i
8383- Models use defensive `try?` decoding for optional fields to tolerate API version differences
8484- Views follow the macOS pattern: `NavigationSplitView` sidebar + detail, with tab switching in the detail pane
8585- All API calls are `async` and dispatched from views via `Task { }`
86−- Connection metadata and passwords are stored in the Keychain (data-protection keychain, scoped by bundle ID to survive ad-hoc re-signing); `ServerConnection.password` is excluded from `Codable`
86+- Connection metadata and passwords are stored in the Keychain (file-based login keychain); `ServerConnection.password` is excluded from `Codable`. Do not add `kSecUseDataProtectionKeychain: true` — it requires `keychain-access-groups` entitlements that conflict with ad-hoc code signing and causes writes to silently fail.
8787- Stream names are SQL-escaped via `escapeSQLIdentifier` (double-quote wrapping) and URL-encoded via `encodePathComponent` for API paths
8888- Views clear stale data before loading new content (e.g., switching streams clears previous schema/stats)
89−- Entitlements: App Sandbox enabled with network.client for outbound connections
89+- Entitlements: none (App Sandbox is not enabled)
9090
9191## Common patterns
9292
ParseableViewer.xcodeproj/project.pbxproj+1−1
@@ -293,7 +293,6 @@
293293 };
294294 };
295295 buildConfigurationList = 91629F84D63DA522312E65D6 /* Build configuration list for PBXProject "ParseableViewer" */;
296− compatibilityVersion = "Xcode 14.0";
297296 developmentRegion = en;
298297 hasScannedForEncodings = 0;
299298 knownRegions = (
@@ -303,6 +302,7 @@
303302 mainGroup = D06ED124AC4DFCB31F6EF0C1;
304303 minimizedProjectReferenceProxies = 1;
305304 preferredProjectObjectVersion = 77;
305+ productRefGroup = D37830AB19DD594BCD2B5499 /* Products */;
306306 projectDirPath = "";
307307 projectRoot = "";
308308 targets = (
ParseableViewer/ParseableViewer.entitlements+1−10
@@ -1,14 +1,5 @@
11<?xml version="1.0" encoding="UTF-8"?>
22<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33<plist version="1.0">
4−<dict>
5− <key>com.apple.security.app-sandbox</key>
6− <true/>
7− <key>com.apple.security.network.client</key>
8− <true/>
9− <key>keychain-access-groups</key>
10− <array>
11− <string>$(AppIdentifierPrefix)com.parseableviewer.app</string>
12− </array>
13−</dict>
4+<dict/>
145</plist>
ParseableViewer/Services/KeychainService.swift+1−5
@@ -87,9 +87,6 @@ enum KeychainService {
8787
8888 // MARK: - Base queries
8989
90− /// Base query targeting the data-protection keychain.
91− /// Items stored here are scoped by bundle identifier rather than by
92− /// code-signing identity, so they survive ad-hoc re-signing across builds.
9390 private static func baseQuery(for connectionID: UUID) -> [String: Any] {
9491 baseQuery(for: connectionID.uuidString)
9592 }
@@ -98,8 +95,7 @@ enum KeychainService {
9895 [
9996 kSecClass as String: kSecClassGenericPassword,
10097 kSecAttrService as String: service,
101− kSecAttrAccount as String: account,
102− kSecUseDataProtectionKeychain as String: true
98+ kSecAttrAccount as String: account
10399 ]
104100 }
105101}