Skip to content

feat: unlock macOS keychain and fix fish cursor rendering#6

Open
cadl wants to merge 10 commits into
Fishason:mainfrom
cadl:codex/macos-keychain-fish-terminal
Open

feat: unlock macOS keychain and fix fish cursor rendering#6
cadl wants to merge 10 commits into
Fishason:mainfrom
cadl:codex/macos-keychain-fish-terminal

Conversation

@cadl

@cadl cadl commented Jul 11, 2026

Copy link
Copy Markdown

中文

背景

当 DSSH 连接到 macOS 时,SSH 本身可以正常认证并进入远端 shell,但 Claude Code 的登录 credential 保存在当前 macOS 用户的登录 Keychain 中。SSH 会话建立后,登录 Keychain 并不会自动解锁,因此 Claude Code 无法读取已有 credential,会认为当前没有登录,并要求用户再次手动 login。

要让 Claude Code 直接读取并复用已有登录状态,需要先在当前 SSH 用户的交互式会话中解锁登录 Keychain。本次改动让 DSSH 在连接 macOS 后自动完成 Keychain 解锁和验证,再返回正常的 shell;之后启动 Claude Code 时即可读取原有 credential,无需重复登录。

另外,在某些 shell 和终端协议组合下,DSSH 存在终端渲染问题:用户输入会一直显示在屏幕第一行,原有内容不能正确刷新,并出现显示错乱。以 fish 为例,它会在显示提示符时启用 Kitty 键盘协议并发送 CSI = 5 u;DSSH 之前将所有以 u 结尾的 CSI 序列都误认为“恢复光标”,导致光标回到默认位置 (1,1)。本次改动修正了协议解析,并补充了终端查询响应和正确的初始 PTY 尺寸。

改动内容

  • 新增可选配置 macos_keychain_password
  • 通过已有的交互式 PTY 解锁当前 SSH 用户的 macOS 登录 Keychain
  • 检测到 security 密码提示后才发送密码,并使用 show-keychain-info 验证解锁结果
  • SSH 初始化完成后清除 DSSH 内存中的密码副本
  • 在启动远端 shell 前设置真实的 3DS 终端尺寸
  • 响应终端状态、光标位置和设备属性查询
  • 区分 fish/Kitty 键盘协议(例如 CSI = 5 u)与 ANSI 光标恢复指令
  • 新增配置解析和终端协议的主机端回归测试
  • 在中英文文档中说明 SD 卡明文保存密码的安全风险

实现说明

通过独立 exec channel 执行 security unlock-keychain 会在 macOS 上返回 User interaction is not allowed。因此 DSSH 使用当前的交互式 PTY,等待 security 显示密码提示后再发送密码。密码不会出现在命令参数或 shell history 中。成功后通过 show-keychain-info 验证解锁状态,并隐藏初始化日志。

使用方式

macos_keychain_password = "macOS 登录密码"

密码会以明文保存在 SD 卡中,因此该功能默认关闭,文档中也明确标注了安全风险。

验证

  • 已在 Nintendo 3DS 真机连接 macOS 测试通过
  • 已验证 Claude Code 可以读取 Keychain 中已有的登录 credential,无需再次 login
  • 已验证终端输入不再停留在第一行,fish 提示符和内容刷新正常
  • bash tools/test_config.sh
  • bash tools/test_terminal.sh
  • bash tools/test_ime.sh
  • 配置及终端解析器通过 ASan/UBSan 主机测试
  • 通过 devkitPro 完整交叉编译并生成有效的 .3dsx

English

Background

When DSSH connects to macOS, SSH authentication itself succeeds and the remote shell is available. However, Claude Code stores its login credential in the current macOS user's login Keychain. Establishing an SSH session does not automatically unlock that Keychain, so Claude Code cannot read the existing credential, treats the session as logged out, and asks the user to log in again manually.

To let Claude Code read and reuse its existing login state, the login Keychain must first be unlocked inside the current SSH user's interactive session. This change makes DSSH unlock and verify the Keychain after connecting to macOS, then return to the normal shell. Claude Code can subsequently read the stored credential without requiring another login.

There was also a terminal rendering issue under some shell and terminal-protocol combinations: typed input remained on the first screen row, existing content did not refresh correctly, and the display became corrupted. Fish, for example, enables the Kitty keyboard protocol at its prompt and sends CSI = 5 u. DSSH previously interpreted every CSI sequence ending in u as cursor restore, which moved the cursor to the default position (1,1). This change corrects that protocol parsing and also adds terminal query responses and the correct initial PTY dimensions.

What changed

  • add an optional macos_keychain_password setting
  • unlock the current SSH user's macOS login Keychain through the existing interactive PTY
  • wait for the security password prompt before sending the password, then verify the result with show-keychain-info
  • clear the password from DSSH memory after connection bootstrap
  • request the real 3DS terminal dimensions before starting the remote shell
  • answer terminal status, cursor-position, and device-attribute queries
  • distinguish fish/Kitty keyboard protocol sequences such as CSI = 5 u from ANSI cursor restore
  • add host-side config and terminal protocol regression tests
  • document the plaintext-password risk in the English and Chinese READMEs

Implementation notes

Running security unlock-keychain through a separate exec channel fails on macOS with User interaction is not allowed. DSSH therefore uses the existing interactive PTY, waits for the security password prompt, and only then sends the password. The password is not placed in process arguments or shell history. DSSH verifies the result with show-keychain-info and hides successful bootstrap diagnostics.

Usage

macos_keychain_password = "macOS login password"

The password is stored in plaintext on the SD card, so the setting is disabled by default and the documentation explicitly calls out the security risk.

Validation

  • tested successfully on a physical Nintendo 3DS connecting to macOS
  • verified Claude Code can read its existing Keychain credential without another login
  • verified terminal input no longer remains on row 1 and fish prompt rendering refreshes correctly
  • bash tools/test_config.sh
  • bash tools/test_terminal.sh
  • bash tools/test_ime.sh
  • ASan/UBSan host tests for the config and terminal parsers
  • clean devkitPro cross-build producing a valid .3dsx

@cadl cadl changed the title [codex] unlock macOS keychain and fix fish cursor rendering feat: unlock macOS keychain and fix fish cursor rendering Jul 11, 2026
@cadl cadl marked this pull request as ready for review July 11, 2026 10:06
@cadl

cadl commented Jul 11, 2026

Copy link
Copy Markdown
Author

修改前终端渲染

IMG_1292_github.mp4

修改后终端渲染

IMG_1293_github.mp4

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