git.iko.soy/parseable-macos
on master help sign in
Fix keychain persistence by removing data-protection keychain requirement
741d046iko
CLAUDE.md+3−3
@@ -31,7 +31,7 @@ CI builds on `macos-15` runners via `.github/workflows/build.yml`, producing arm
31- **Concurrency**: Swift structured concurrency (`async/await`, `@MainActor`, strict `Sendable` conformance)
31- **Concurrency**: Swift structured concurrency (`async/await`, `@MainActor`, strict `Sendable` conformance)
32- **Networking**: `URLSession` async/await, no external dependencies
32- **Networking**: `URLSession` async/await, no external dependencies
33- **Project generation**: XcodeGen from `project.yml`
33- **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.
35
35
36### Source layout
36### Source layout
37
37
@@ -83,10 +83,10 @@ Live tail in Parseable uses gRPC Arrow Flight streaming. This app approximates i
83- Models use defensive `try?` decoding for optional fields to tolerate API version differences
83- Models use defensive `try?` decoding for optional fields to tolerate API version differences
84- Views follow the macOS pattern: `NavigationSplitView` sidebar + detail, with tab switching in the detail pane
84- Views follow the macOS pattern: `NavigationSplitView` sidebar + detail, with tab switching in the detail pane
85- All API calls are `async` and dispatched from views via `Task { }`
85- 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.
87- Stream names are SQL-escaped via `escapeSQLIdentifier` (double-quote wrapping) and URL-encoded via `encodePathComponent` for API paths
87- Stream names are SQL-escaped via `escapeSQLIdentifier` (double-quote wrapping) and URL-encoded via `encodePathComponent` for API paths
88- Views clear stale data before loading new content (e.g., switching streams clears previous schema/stats)
88- 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)
90
90
91## Common patterns
91## Common patterns
92
92
ParseableViewer.xcodeproj/project.pbxproj+1−1
@@ -293,7 +293,6 @@
293 };
293 };
294 };
294 };
295 buildConfigurationList = 91629F84D63DA522312E65D6 /* Build configuration list for PBXProject "ParseableViewer" */;
295 buildConfigurationList = 91629F84D63DA522312E65D6 /* Build configuration list for PBXProject "ParseableViewer" */;
296− compatibilityVersion = "Xcode 14.0";
297 developmentRegion = en;
296 developmentRegion = en;
298 hasScannedForEncodings = 0;
297 hasScannedForEncodings = 0;
299 knownRegions = (
298 knownRegions = (
@@ -303,6 +302,7 @@
303 mainGroup = D06ED124AC4DFCB31F6EF0C1;
302 mainGroup = D06ED124AC4DFCB31F6EF0C1;
304 minimizedProjectReferenceProxies = 1;
303 minimizedProjectReferenceProxies = 1;
305 preferredProjectObjectVersion = 77;
304 preferredProjectObjectVersion = 77;
305+ productRefGroup = D37830AB19DD594BCD2B5499 /* Products */;
306 projectDirPath = "";
306 projectDirPath = "";
307 projectRoot = "";
307 projectRoot = "";
308 targets = (
308 targets = (
ParseableViewer/ParseableViewer.entitlements+1−10
@@ -1,14 +1,5 @@
1<?xml version="1.0" encoding="UTF-8"?>
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
3<plist version="1.0">
4−<dict>
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>
14</plist>
5</plist>
ParseableViewer/Services/KeychainService.swift+1−5
@@ -87,9 +87,6 @@ enum KeychainService {
87
87
88 // MARK: - Base queries
88 // MARK: - Base queries
89
89
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.
93 private static func baseQuery(for connectionID: UUID) -> [String: Any] {
90 private static func baseQuery(for connectionID: UUID) -> [String: Any] {
94 baseQuery(for: connectionID.uuidString)
91 baseQuery(for: connectionID.uuidString)
95 }
92 }
@@ -98,8 +95,7 @@ enum KeychainService {
98 [
95 [
99 kSecClass as String: kSecClassGenericPassword,
96 kSecClass as String: kSecClassGenericPassword,
100 kSecAttrService as String: service,
97 kSecAttrService as String: service,
101− kSecAttrAccount as String: account,
98+ kSecAttrAccount as String: account
102− kSecUseDataProtectionKeychain as String: true
103 ]
99 ]
104 }
100 }
105}
101}