Skip to content

keychainsy - #1280

Merged
shunkakinoki merged 2 commits into
mainfrom
keychainsy
Mar 26, 2026
Merged

keychainsy#1280
shunkakinoki merged 2 commits into
mainfrom
keychainsy

Conversation

@shunkakinoki

Copy link
Copy Markdown
Owner
  • feat: added cliproxyapi sync and bd dependency
  • feat(cliproxyapi): added overrideable security path

- Wired keychain sync into cliproxyapi launchd and systemd services.
- Added the @beads/bd package to the dependency manifest.

Entire-Checkpoint: 1d8625226079
- Made the keychain lookup honor a SECURITY override and default to /usr/bin/security.
- Added a dedicated shell spec for keychain sync behavior.
- Extended coverage checks to require the new script and spec.

Entire-Checkpoint: 5e4d60d743b6
Copilot AI review requested due to automatic review settings March 26, 2026 06:50
@mesa-dot-dev

mesa-dot-dev Bot commented Mar 26, 2026

Copy link
Copy Markdown

You do not have enough credits to review this pull request. Please purchase more credits to continue.

@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e751af38-c296-4c5c-af2c-2115fc66d30d

📥 Commits

Reviewing files that changed from the base of the PR and between 7a8b3ca and 2c8e7ce.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • home-manager/services/cliproxyapi/default.nix
  • home-manager/services/cliproxyapi/scripts/keychain-sync.sh
  • package.json
  • spec/cliproxyapi_keychain_sync_spec.sh
  • spec/coverage_spec.sh

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added automatic credential synchronization service that runs in the background on both macOS and Linux, periodically syncing OAuth tokens from local configuration sources into a shared directory.

Walkthrough

Added platform-specific background services to automatically synchronize OAuth token credentials from system keychain and local auth files into a centralized auth directory. Includes a new keychain-sync script for macOS Keychain integration, Darwin launchd agents, Linux systemd timer and service units, comprehensive test coverage, and a new npm dependency.

Changes

Cohort / File(s) Summary
Keychain Sync Service
home-manager/services/cliproxyapi/default.nix, home-manager/services/cliproxyapi/scripts/keychain-sync.sh
Added new bash script for syncing Claude and Codex OAuth tokens from macOS Keychain and local files into auth directory. Added Darwin launchd agents (keychain-sync, cliproxyapi-sync) and Linux systemd service/timer pair for periodic execution (every 5 minutes on Linux, every 300 seconds on Darwin).
Test Coverage
spec/cliproxyapi_keychain_sync_spec.sh, spec/coverage_spec.sh
Added comprehensive test suite validating keychain-sync script behavior for both Claude and Codex token synchronization, including mocked keychain interactions, token-change detection, and file generation logic. Updated coverage manifest to require spec for the new script.
Dependencies
package.json
Added @beads/bd (^0.62.0) to dependencies and trustedDependencies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A keychain dance in daemon's light,
OAuth tokens synced both day and night,
Launchd and systemd, hand in paw,
Auth files flourish without a flaw!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch keychainsy

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.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the cliproxyapi service by integrating robust authentication token management. It introduces a new mechanism to automatically extract and synchronize OAuth tokens for services like Claude Code and Codex CLI from local credential stores, ensuring cliproxyapi always has up-to-date authentication. Additionally, it establishes periodic synchronization of cliproxyapi's authentication files from S3 for both macOS and Linux environments, improving operational reliability. A new dependency, @beads/bd, was also added to support these features, alongside comprehensive test coverage for the new synchronization logic.

Highlights

  • New Dependency Added: The @beads/bd package was introduced as a new dependency, reflected in both bun.lock and package.json.
  • Keychain Synchronization for CLIProxyAPI: A new keychain-sync.sh script was added to automatically extract OAuth tokens for Claude Code (from macOS Keychain) and Codex CLI (from ~/.codex/auth.json) and store them in the cliproxyapi authentication directory.
  • Periodic Authentication Sync: Configurations were added to home-manager/services/cliproxyapi/default.nix to enable periodic synchronization of cliproxyapi authentication files from S3 for both macOS (via launchd) and Linux (via systemd).
  • Test Coverage for New Script: A new test file, spec/cliproxyapi_keychain_sync_spec.sh, was created to provide comprehensive test coverage for the new keychain synchronization logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@mesa-dot-dev

mesa-dot-dev Bot commented Mar 26, 2026

Copy link
Copy Markdown

Mesa Description

TL;DR

Introduced cliproxyapi synchronization with a new dependency and added an overrideable security path for cliproxyapi.

What changed?

  • Added cliproxyapi synchronization and bd dependency.
  • Added an overrideable security path specifically for cliproxyapi.

Description generated by Mesa. Update settings

@shunkakinoki
shunkakinoki merged commit 50a982b into main Mar 26, 2026
32 of 33 checks passed
@shunkakinoki
shunkakinoki deleted the keychainsy branch March 26, 2026 06:51

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new keychain-sync.sh script and integrates it into the cliproxyapi service for both macOS (via launchd) and Linux (via systemd). The script is designed to synchronize authentication tokens from Claude Code (macOS Keychain) and Codex CLI (~/.codex/auth.json) into a common authentication directory. Additionally, it sets up a periodic sync for S3 auth files on both operating systems and adds the @beads/bd dependency. Review feedback suggests improving error handling in the keychain-sync.sh script, making the email and keychain_account configurable, using a persistent logging location instead of /tmp/, and implementing atomic write operations for authentication tokens to prevent data corruption.

#
# On first run, macOS will prompt to allow keychain access — click "Always Allow".
# shellcheck source=/dev/null
set -euo pipefail

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The script uses set -euo pipefail. According to the general rules, "Do not use set -e in PAM execution scripts. These scripts must handle failures gracefully (e.g., during credential decryption or in retry loops) to avoid blocking the user's login." While this script isn't strictly a PAM script, its role in syncing authentication tokens suggests that failures should be handled gracefully rather than causing an immediate exit, which could impact user experience or automated processes. Consider replacing set -e with explicit error handling for critical commands.

References
  1. Do not use set -e in PAM execution scripts. These scripts must handle failures gracefully (e.g., during credential decryption or in retry loops) to avoid blocking the user's login.

);

keychainSyncScript = pkgs.replaceVars ./scripts/keychain-sync.sh {
email = "shunkakinoki@gmail.com";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The email for keychainSyncScript is hardcoded. It would be more flexible and maintainable to make this configurable, perhaps by deriving it from config.home.username or allowing it to be passed as an argument, especially if this configuration is intended for multiple users or environments.


keychainSyncScript = pkgs.replaceVars ./scripts/keychain-sync.sh {
email = "shunkakinoki@gmail.com";
keychain_account = "shunkakinoki";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The keychain_account is hardcoded. Similar to the email, this should ideally be configurable to support different user accounts or to avoid exposing sensitive details directly in the configuration.

Comment on lines +133 to +134
StandardOutPath = "/tmp/cliproxyapi-keychain-sync.log";
StandardErrorPath = "/tmp/cliproxyapi-keychain-sync.error.log";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Logging to /tmp/ is generally not persistent across reboots and can be cleared by the system. For important service logs, consider using a more permanent location, such as a subdirectory within the user's home directory (e.g., ~/.local/state/cliproxyapi/logs) or a system-wide log directory if appropriate permissions are managed.

Comment on lines +156 to +157
StandardOutPath = "/tmp/cliproxyapi-sync.log";
StandardErrorPath = "/tmp/cliproxyapi-sync.error.log";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the cliproxyapi-keychain-sync agent, logging to /tmp/ for cliproxyapi-sync is not ideal for persistence. Please consider a more permanent logging location.

existing_at=$($JQ -r '.access_token // empty' "$dest" 2>/dev/null) || true
fi

if [ "$access_token" != "$existing_at" ]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Writing directly to $dest using printf '%s' "$new_json" >"$dest" is not atomic. If the script is interrupted during the write operation, the destination file could be corrupted or left in an incomplete state. For sensitive data like authentication tokens, it's safer to write to a temporary file first and then atomically move it to the final destination.

Suggested change
if [ "$access_token" != "$existing_at" ]; then
printf '%s' "$new_json" >"${dest}.tmp" && mv "${dest}.tmp" "$dest"

existing_at=$($JQ -r '.access_token // empty' "$dest" 2>/dev/null) || true
fi

if [ "$access_token" != "$existing_at" ]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the Claude sync function, this write operation is not atomic. Consider using a temporary file and then atomically moving it to prevent data corruption in case of interruption.

Suggested change
if [ "$access_token" != "$existing_at" ]; then
printf '%s' "$new_json" >"${dest}.tmp" && mv "${dest}.tmp" "$dest"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new cliproxyapi token sync path that pulls credentials from local stores (macOS Keychain for Claude Code; ~/.codex/auth.json for Codex) into the cliproxyapi auth cache, along with wiring for periodic sync and a new Node dependency.

Changes:

  • Add home-manager/services/cliproxyapi/scripts/keychain-sync.sh to generate cliproxyapi auth JSON files from local credential sources.
  • Wire up periodic sync: a new Launchd agent on Darwin for keychain sync, plus a systemd timer/service on Linux for periodic S3 hydration.
  • Add @beads/bd dependency (and lockfile updates) and add ShellSpec coverage for the new script.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
spec/coverage_spec.sh Adds coverage expectations for the new keychain-sync.sh script/spec.
spec/cliproxyapi_keychain_sync_spec.sh New ShellSpec suite covering keychain and codex auth sync behaviors.
package.json Adds @beads/bd dependency and marks it trusted.
bun.lock Lockfile updates for @beads/bd.
home-manager/services/cliproxyapi/scripts/keychain-sync.sh New token sync script producing cliproxyapi auth JSON files.
home-manager/services/cliproxyapi/default.nix Adds Launchd agent for keychain sync (Darwin) and periodic hydrate timer/service (Linux).

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

fi

if [ "$access_token" != "$existing_at" ]; then
printf '%s' "$new_json" >"$dest"

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

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

Same as above for the Codex auth file: writing secrets via direct redirection can create overly-permissive permissions and is not atomic, which can race with the backup watcher reading/syncing. Prefer restrictive permissions (0600) and an atomic write pattern (temp file + rename).

Suggested change
printf '%s' "$new_json" >"$dest"
umask 077
local tmp
tmp="$(mktemp "${dest}.tmp.XXXXXX")"
printf '%s' "$new_json" >"$tmp"
chmod 600 "$tmp" 2>/dev/null || true
mv "$tmp" "$dest"

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +39
rm -f "$TEMP_HOME/.mock-keychain-data" 2>/dev/null || true
rmdir "$TEMP_HOME/.cli-proxy-api/objectstore/auths" "$TEMP_HOME/.cli-proxy-api/objectstore" "$TEMP_HOME/.cli-proxy-api" "$TEMP_HOME/.codex" "$TEMP_HOME" 2>/dev/null || true

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

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

The cleanup() here uses rmdir on $TEMP_HOME, but the test creates auth JSON files under $TEMP_HOME/.cli-proxy-api/objectstore/auths, so the directories won’t be empty and the temp tree will be left behind. For consistency with other specs (e.g. cliproxyapi_backup_spec.sh), prefer rm -rf "$TEMP_HOME" (and any other temp artifacts) so tests don’t leak temp dirs/files.

Suggested change
rm -f "$TEMP_HOME/.mock-keychain-data" 2>/dev/null || true
rmdir "$TEMP_HOME/.cli-proxy-api/objectstore/auths" "$TEMP_HOME/.cli-proxy-api/objectstore" "$TEMP_HOME/.cli-proxy-api" "$TEMP_HOME/.codex" "$TEMP_HOME" 2>/dev/null || true
rm -rf "$TEMP_HOME" 2>/dev/null || true

Copilot uses AI. Check for mistakes.

cleanup() {
rm -f "$TEMP_HOME/.codex/auth.json" 2>/dev/null || true
rmdir "$TEMP_HOME/.cli-proxy-api/objectstore/auths" "$TEMP_HOME/.cli-proxy-api/objectstore" "$TEMP_HOME/.cli-proxy-api" "$TEMP_HOME/.codex" "$TEMP_HOME" 2>/dev/null || true

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

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

Same temp cleanup issue in this cleanup(): since the script writes auth JSON files, rmdir won’t remove non-empty directories and $TEMP_HOME will remain on disk. Prefer removing the entire temp directory tree with rm -rf to avoid leaking files across test runs.

Suggested change
rmdir "$TEMP_HOME/.cli-proxy-api/objectstore/auths" "$TEMP_HOME/.cli-proxy-api/objectstore" "$TEMP_HOME/.cli-proxy-api" "$TEMP_HOME/.codex" "$TEMP_HOME" 2>/dev/null || true
rm -rf "$TEMP_HOME" 2>/dev/null || true

Copilot uses AI. Check for mistakes.

# Convert epoch ms to ISO 8601
if [ -n "$expires_at" ] && [ "$expires_at" != "null" ]; then
expires_at=$(date -u -r "$((expires_at / 1000))" +%Y-%m-%dT%H:%M:%S+00:00 2>/dev/null) || expires_at=""

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

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

expiresAt conversion uses date -u -r <epoch> which is BSD-date syntax, but this agent’s PATH includes pkgs.coreutils (GNU date), where -r means “reference file” and the conversion will fail (leaving expired empty). Consider either using a GNU-compatible conversion (date -d "@...") or explicitly calling the system /bin/date (and avoiding GNU date shadowing) so the field is populated reliably.

Suggested change
expires_at=$(date -u -r "$((expires_at / 1000))" +%Y-%m-%dT%H:%M:%S+00:00 2>/dev/null) || expires_at=""
expires_at=$(/bin/date -u -r "$((expires_at / 1000))" +%Y-%m-%dT%H:%M:%S+00:00 2>/dev/null) || expires_at=""

Copilot uses AI. Check for mistakes.
local access_token refresh_token account_id
access_token=$($JQ -r '.tokens.access_token // .OPENAI_API_KEY // empty' "$auth_file" 2>/dev/null) || true
refresh_token=$($JQ -r '.tokens.refresh_token // empty' "$auth_file" 2>/dev/null) || true
account_id=$($JQ -r '.account_id // empty' "$auth_file" 2>/dev/null) || true

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

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

Codex account_id is extracted from .account_id, but the Codex auth format used elsewhere (and in this repo’s spec fixtures) nests it under .tokens.account_id. This will always write an empty account_id in the generated cliproxyapi auth JSON. Update the jq path to read the correct field (optionally supporting both layouts for compatibility).

Suggested change
account_id=$($JQ -r '.account_id // empty' "$auth_file" 2>/dev/null) || true
account_id=$($JQ -r '.tokens.account_id // .account_id // empty' "$auth_file" 2>/dev/null) || true

Copilot uses AI. Check for mistakes.
fi

if [ "$access_token" != "$existing_at" ]; then
printf '%s' "$new_json" >"$dest"

Copilot AI Mar 26, 2026

Copy link

Choose a reason for hiding this comment

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

Auth JSON files containing access tokens are written with the process umask and via direct redirection. On many systems this can create world-readable files (e.g. mode 0644) and it’s also non-atomic (a watcher could read a partially-written file). Consider setting a restrictive umask / chmod to 0600 and writing via a temp file + atomic rename (mv) to avoid token exposure and partial reads.

Suggested change
printf '%s' "$new_json" >"$dest"
# Write via a temp file with restrictive permissions, then atomically rename
local tmp_dest
tmp_dest=$(mktemp "${dest}.tmp.XXXXXX")
printf '%s' "$new_json" >"$tmp_dest"
chmod 600 "$tmp_dest"
mv -f "$tmp_dest" "$dest"

Copilot uses AI. Check for mistakes.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="spec/cliproxyapi_keychain_sync_spec.sh">

<violation number="1" location="spec/cliproxyapi_keychain_sync_spec.sh:39">
P2: Cleanup uses `rmdir` on directories that can contain generated auth files, so temp directories are not reliably removed.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


cleanup() {
rm -f "$TEMP_HOME/.mock-keychain-data" 2>/dev/null || true
rmdir "$TEMP_HOME/.cli-proxy-api/objectstore/auths" "$TEMP_HOME/.cli-proxy-api/objectstore" "$TEMP_HOME/.cli-proxy-api" "$TEMP_HOME/.codex" "$TEMP_HOME" 2>/dev/null || true

@cubic-dev-ai cubic-dev-ai Bot Mar 26, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Cleanup uses rmdir on directories that can contain generated auth files, so temp directories are not reliably removed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/cliproxyapi_keychain_sync_spec.sh, line 39:

<comment>Cleanup uses `rmdir` on directories that can contain generated auth files, so temp directories are not reliably removed.</comment>

<file context>
@@ -0,0 +1,166 @@
+
+cleanup() {
+  rm -f "$TEMP_HOME/.mock-keychain-data" 2>/dev/null || true
+  rmdir "$TEMP_HOME/.cli-proxy-api/objectstore/auths" "$TEMP_HOME/.cli-proxy-api/objectstore" "$TEMP_HOME/.cli-proxy-api" "$TEMP_HOME/.codex" "$TEMP_HOME" 2>/dev/null || true
+}
+
</file context>
Fix with Cubic

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.

2 participants