Skip to content

Keep the credential key with SwiftKeychain (0.15.0, breaking) - #4

Open
jpurnell wants to merge 2 commits into
mainfrom
feat/swiftkeychain
Open

jpurnell wants to merge 2 commits into
mainfrom
feat/swiftkeychain

Conversation

@jpurnell

Copy link
Copy Markdown
Owner

What

CredentialStoreKey now keeps its key through SwiftKeychain 0.1.1 (KeychainKey.loadOrCreate) instead of hand-written SecItem code. The design is unchanged: one key in the Keychain, credentials in the encrypted files. KeychainClientStorage is deliberately not used — that would strand every stored credential.

Compatibility (the whole risk)

The key is read from the exact item every earlier version wrote:

  • service com.swiftmcpclient.credential-store, account credential-encryption-key
  • .afterFirstUnlockThisDeviceOnly, not synchronizable, no access group
  • on macOS the login (file) keychain: SystemKeychain(dataProtection: false) — the old code never set kSecUseDataProtectionKeychain. On iOS the flag is ignored.

Pinned by tests against literal strings (not the type's own constants): CredentialStoreKey.item field by field, the default keychain is a SystemKeychain with dataProtection == false, and a key seeded at the old identity in InMemoryKeychain comes back byte for byte with nothing added.

Breaking (0.15.0)

  • Removed KeychainAccess, SystemKeychain, CredentialKeyError; SwiftKeychain's types replace them. No @_exported import — callers that name them depend on and import SwiftKeychain.
  • loadOrCreate() / delete() are throws(KeychainError); persistent() throws it too.
  • New public: CredentialStoreKey.item, CredentialStoreKey.keychain; CredentialStoreKey is Sendable.
  • CredentialStoreKey and MCPOAuthSession.persistent() are now macOS/iOS/visionOS only. They used to compile on tvOS/watchOS too; SwiftKeychain does not support those platforms.

CHANGELOG (Unreleased → Breaking) and a MigrationGuide section with an old → new table cover it.

Also

  • Key creation is race-safe: a new key is added, never stored over another.
  • Package.resolved gains only swiftkeychain (no traits). The product is linked with condition: .when(platforms: [.macOS, .iOS, .visionOS]); Linux compiles the key out.
  • MCPExplorer needed no code change: it only calls persistent() and logs the error generically.

Note for merging

The main checkout of this repo has uncommitted changes from 2026-10-08 (not part of this PR) touching CHANGELOG.md, MCPOAuthSession.swift, MigrationGuide.md, OAuthHTTP.swift, the transports and DocC guides. They will need merging with this branch later; expect conflicts in the CHANGELOG Unreleased section and around persistent().

Verification

  • swift test: 677 tests in 90 suites pass (macOS).
  • quality-gate: 0 errors / 0 warnings, no new suppressions.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PtLUw634m85L9ucJTrftoD

jpurnell and others added 2 commits October 28, 2026 16:42
CredentialStoreKey now reads and creates its key through SwiftKeychain's
KeychainKey.loadOrCreate, which adds a new key rather than storing over
one, so two processes starting at once agree on a key. The hand-written
SecItem code, KeychainAccess, SystemKeychain and CredentialKeyError are
gone in favour of SwiftKeychain's types.

The item is the one every earlier version wrote: same service and
account, after first unlock on this device only, not synchronized, no
access group, and on macOS the login keychain
(SystemKeychain(dataProtection: false)). Tests pin each of those and
read a key seeded at the old identity.

SwiftKeychain is depended on for macOS, iOS and visionOS only; Linux,
tvOS and watchOS compile the key out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PtLUw634m85L9ucJTrftoD
CHANGELOG Unreleased gains a Breaking section; the migration guide maps
each removed name to SwiftKeychain's and says stored credentials are
untouched; README and persistent()'s documentation say which platforms
keep the key in the Keychain and where it lives on macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PtLUw634m85L9ucJTrftoD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant