Skip to content

fix(keychain): prefer file-based master key to fix background/automat…#1059

Closed
Zhang-986 wants to merge 1 commit into
larksuite:mainfrom
Zhang-986:fix/keychain-background-context
Closed

fix(keychain): prefer file-based master key to fix background/automat…#1059
Zhang-986 wants to merge 1 commit into
larksuite:mainfrom
Zhang-986:fix/keychain-background-context

Conversation

@Zhang-986

@Zhang-986 Zhang-986 commented May 24, 2026

Copy link
Copy Markdown
Contributor

背景

Closes #1038

macOS 上在后台/自动化场景跑 auth status --verify 会报 "keychain not initialized",但同一个 profile 在交互式终端里完全正常。

原因是 platformSet 优先用 system keychain 的 master key 来加密凭据,而 system keychain 在没有 GUI session 的情况下根本访问不了。用户在交互式终端里 config init 存的密钥,到了自动化脚本里就读不出来了。

改了什么

1. platformSet 里调换了 key 优先级

原来:读已有 file key → system keychain(允许创建)→ 创建 file key
现在:读已有 file key → 创建 file key → system keychain(允许创建)

这样新存的凭据一律用 file key 加密,后台和交互式都能读。

2. platformGet 里加了自动迁移

platformGet 通过 system keychain 成功解密后,会自动用 file key 重新加密。老用户不需要重新配置,下次读取就自动迁移了。

What changed

1. Swapped key priority in platformSet

Before: existing file key → system keychain (allow create) → create file key
After: existing file key → create file key → system keychain (allow create)

New credentials are now always encrypted with the file-based key, which works in both interactive and background contexts.

2. Added automatic migration in platformGet

When a secret is successfully decrypted using the system keychain key, it gets re-encrypted with the file-based key. Existing users are migrated on their next read — no manual reconfiguration needed.

测试

  • go test ./internal/keychain/... -race — 5 个测试全过
  • go vet / gofmt — 没问题
  • TestPlatformSetCreatesFileMasterKeyFirst — 验证新凭据用 file key,不碰 system keychain
  • TestMigrateToFileMasterKey — 验证迁移:system keychain 解密 → file key 重加密 → 断掉 system keychain 后仍能读取
  • 在 macOS 自动化环境里手动验证

Testing

  • go test ./internal/keychain/... -race — all 5 tests pass
  • go vet / gofmt — clean
  • TestPlatformSetCreatesFileMasterKeyFirst — new credentials use file key, not system keychain
  • TestMigrateToFileMasterKey — migration works: decrypt with system key → re-encrypt with file key → read works without system keychain
  • Manual verification in a macOS automation context

Summary by CodeRabbit

  • New Features

    • Secrets stored in macOS system keychain are automatically migrated to local file-based storage on first access.
  • Bug Fixes

    • Improved master key creation logic for local file-based storage when system keychain is unavailable.
  • Tests

    • Added tests for file-based master key creation and automatic secret migration.

Review Change Stack

Copilot AI review requested due to automatic review settings May 24, 2026 02:35
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds automatic migration of secrets from the macOS system keychain to file-based master key storage. When platformGet successfully decrypts a value using the system keychain, it transparently re-encrypts and writes the plaintext to the file-based keychain. The platformSet function is adjusted to prioritize file-based master key creation before falling back to system keychain access.

Changes

macOS Keychain Secret Migration

Layer / File(s) Summary
Secret migration helper and platformGet integration
internal/keychain/keychain_darwin.go
New migrateToFileMasterKey helper encrypts plaintext with the file-based master key and persists it via atomic temp-file rename; platformGet calls this helper after successful system-keychain decryption to transparently migrate secrets to file-based storage.
File-based master key priority in platformSet
internal/keychain/keychain_darwin.go
platformSet master-key selection changes to attempt getFileMasterKey first before falling back to system keychain getMasterKey when initial file-based key load fails.
Migration and master-key behavior tests
internal/keychain/keychain_darwin_test.go
TestPlatformSetCreatesFileMasterKeyFirst verifies file-based master key creation without keychain access; TestMigrateToFileMasterKey verifies automatic migration from system keychain to file-based storage on first read and persistence despite keychain unavailability.

Sequence Diagram

sequenceDiagram
  participant platformGet
  participant systemKeychain as System Keychain Master Key
  participant migrateToFileMasterKey
  participant fileMasterKey as File-Based Master Key
  participant filesystem as File System
  platformGet->>systemKeychain: decrypt ciphertext
  systemKeychain-->>platformGet: plaintext
  platformGet->>migrateToFileMasterKey: plaintext
  migrateToFileMasterKey->>fileMasterKey: get or create
  fileMasterKey-->>migrateToFileMasterKey: file-based key
  migrateToFileMasterKey->>migrateToFileMasterKey: encrypt plaintext (AES-GCM)
  migrateToFileMasterKey->>filesystem: write temp file
  filesystem-->>migrateToFileMasterKey: temp path
  migrateToFileMasterKey->>filesystem: atomic rename to final path
  filesystem-->>migrateToFileMasterKey: success
  platformGet-->>caller: plaintext
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • larksuite/cli#285: Directly builds on file-based master key fallback logic introduced in this PR.
  • larksuite/cli#173: Prior Darwin keychain master-key handling changes that establish the foundation for this PR's migration and priority adjustments.

Suggested labels

size/M

Suggested reviewers

  • liangshuo-1

Poem

🐰 A rabbit hops through keychains old and new,
Secrets packed up for the macOS to chew—
From system vaults to files so fine,
Each credential now has time to shine!
No more locked doors when the keyring's asleep,
The file-based keys our promises keep. 🔑

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is partially related to the main change but appears truncated mid-word ('automat…'), making it incomplete and difficult to fully understand the scope. Complete the truncated title to clearly state what fix is being applied, e.g., 'fix(keychain): prefer file-based master key to fix background/automation contexts'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive and covers all required template sections: summary, changes, test plan, and related issues with clear bilingual explanation of the problem and solution.
Linked Issues check ✅ Passed The PR directly addresses #1038 by implementing solutions to enable keychain access in background/automation contexts through file-based master key preference and automatic migration.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to the keychain master key selection and migration logic required to fix the background automation context issue; no unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size/M Single-domain feat or fix with limited business impact label May 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates the macOS keychain implementation to prefer a file-based master key for background/automation compatibility, and adds logic to migrate existing secrets from the system keychain master key on successful reads.

Changes:

  • Change platformSet fallback order to create/use a file-based master key before attempting system keychain access.
  • Add best-effort migration on platformGet to re-encrypt secrets with the file-based master key after decrypting with the system keychain key.
  • Add Darwin-specific tests covering “file key created first” and “migration on read” behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
internal/keychain/keychain_darwin.go Adds migration helper and adjusts master-key selection order to prefer file-based key.
internal/keychain/keychain_darwin_test.go Adds tests validating file-first behavior and migration away from system keychain.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/keychain/keychain_darwin_test.go
Comment thread internal/keychain/keychain_darwin_test.go
Comment thread internal/keychain/keychain_darwin.go
Comment thread internal/keychain/keychain_darwin.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/keychain/keychain_darwin.go (1)

278-298: 💤 Low value

Consider using defer for temp file cleanup to match platformSet pattern.

If WriteFile fails after partially creating the file, the temp file won't be cleaned up. While unlikely to cause issues (UUID-named files), using defer would be consistent with platformSet (line 323).

♻️ Suggested change for consistency
 	dir := StorageDir(service)
 	targetPath := filepath.Join(dir, safeFileName(account))
 	tmpPath := targetPath + "." + uuid.New().String() + ".tmp"
+	defer vfs.Remove(tmpPath)
 	if vfs.WriteFile(tmpPath, encrypted, 0600) != nil {
 		return
 	}
-	if vfs.Rename(tmpPath, targetPath) != nil {
-		_ = vfs.Remove(tmpPath)
-	}
+	_ = vfs.Rename(tmpPath, targetPath)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/keychain/keychain_darwin.go` around lines 278 - 298,
migrateToFileMasterKey currently creates a temp file tmpPath with vfs.WriteFile
and only removes it on Rename failure; add a defer immediately after creating
tmpPath (after the WriteFile succeeds) to ensure the temp file is removed on any
early return or error (similar to platformSet pattern). Specifically, after
vfs.WriteFile(tmpPath, ...) check for error, then set defer func() { _ =
vfs.Remove(tmpPath) }(), then proceed to vfs.Rename(tmpPath, targetPath) and
only cancel/remove the defer if Rename succeeds (or keep it and no-op on missing
file); reference migrateToFileMasterKey, tmpPath, vfs.WriteFile, vfs.Rename,
vfs.Remove and platformSet for locating the pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/keychain/keychain_darwin_test.go`:
- Around line 65-68: Remove the unused keychainKey variable and its population
loop: delete the declaration "keychainKey := make([]byte, masterKeyBytes)" and
the for loop that assigns to keychainKey (the byte-filling loop referencing
masterKeyBytes and keychainKey). This eliminates dead code in
keychain_darwin_test.go and keeps the test focused on verifying that the system
keychain is not accessed.

---

Nitpick comments:
In `@internal/keychain/keychain_darwin.go`:
- Around line 278-298: migrateToFileMasterKey currently creates a temp file
tmpPath with vfs.WriteFile and only removes it on Rename failure; add a defer
immediately after creating tmpPath (after the WriteFile succeeds) to ensure the
temp file is removed on any early return or error (similar to platformSet
pattern). Specifically, after vfs.WriteFile(tmpPath, ...) check for error, then
set defer func() { _ = vfs.Remove(tmpPath) }(), then proceed to
vfs.Rename(tmpPath, targetPath) and only cancel/remove the defer if Rename
succeeds (or keep it and no-op on missing file); reference
migrateToFileMasterKey, tmpPath, vfs.WriteFile, vfs.Rename, vfs.Remove and
platformSet for locating the pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bfd21dc6-b966-4862-8293-b517a47d6a87

📥 Commits

Reviewing files that changed from the base of the PR and between 6e3e120 and 6e1076e.

📒 Files selected for processing (2)
  • internal/keychain/keychain_darwin.go
  • internal/keychain/keychain_darwin_test.go

Comment thread internal/keychain/keychain_darwin_test.go
@albertnusouo

Copy link
Copy Markdown
Collaborator

@Zhang-986
Thanks for taking this on — #1038 is a real pain point. For context, we ended up shipping an alternative for the same bug via #1085 (merged): a lark-cli config keychain-downgradesubcommand. The two approaches differ on the implicit/explicit axis:

This PR auto-migrates inside platformGet / platformSet — transparent, zero user action.
#1085 adds an explicit subcommand the user runs once interactively; the migration is then durable.
We landed on the explicit design for two reasons:

Discoverability and reversibility — the user opts in knowingly, the OS Keychain entry is preserved as a cold backup, and the action appears in shell history.
Surfaceable security trade-off — auto-migration on read silently demotes the storage from Keychain (per-app ACL) to a 0600 file (same-user readable). An explicit command lets us document and disclose that delta in --help and in the runtime hint.
The analysis in this PR is sound — closing in favor of #1085 is one option, but feel free to leave it open if there's a remaining gap we missed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

auth status --verify fails in macOS automation context: keychain Get failed: keychain not initialized

3 participants