跳到正文
dsh-market 浏览插件 GitHub EN

nengong-ai/dsh-keychain-credentials

纯 JavaScript 实现的 macOS Keychain 凭据提供器,替换 DeepSeek Harness 的明文 .credentials.yaml 存储,完整支持 refs 和 records,无需原生构建、代码签名或 Xcode。

Star 数 ★ 0 分类 安全与权限 收录于 2026-09-24

安装

在 DeepSeek Harness 里通过 dsh-market 安装

dsh plugin --profile web add dshmarket

或使用命令行

dsh plugin --profile web add github:nengong-ai/dsh-keychain-credentials

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。

README

该插件的 README 只有英文版本。

macOS Keychain credentials provider for DeepSeek Harness (dsh).

The stock provider (dsh-credentials-local) stores secrets in $DSH_HOME/.credentials.yaml with 0600 permissions. As its own README states: that file is protected from other OS users, but not from the model — tool processes (bash, filesystem tools) run as the same user and can read it like any other file.

This provider moves secrets into the macOS login keychain. A file that does not exist cannot be cat-ed; nothing lands in backups, sync folders, or git.

What this buys you

  • Fixed: the secret is no longer a file. The stock file is reachable by every read primitive the model has — read, cat, grep, tar, node -e …, in-process or subprocess. With this provider, values live in the login keychain.
  • Not fixed (be honest): while the login keychain is unlocked (the default once you are logged in), any same-user process can read the item by invoking /usr/bin/security — the CLI is ACL-trusted and no prompt appears. An agent with an unrestricted shell that knows to ask the keychain can still exfiltrate. Pairing with a sandbox that constrains tool subprocesses narrows this; a hard boundary needs OS-level separation (a signed broker with a private access group, e.g. keyringseam, or a separate OS user).

Net: the bar rises from "any read primitive" to "must exec /usr/bin/security as the same user" — a real improvement, not a complete boundary.

Why this one, not the others

  • Full seam coverage: implements both halves of ctx.credentials — refs (API keys) and records (structured credentials like client-connection/browser-session grants). Several other providers implement only the refs half, which means DSH's own session records fall back to the plaintext file.
  • Zero build step: pure JavaScript, no Swift, no code-signing certificate, no Xcode. Install and go.
  • No auth prompts: reads and writes never ask the user for Touch ID / password. (That is a deliberate trade: keyringseam offers device-owner authentication at the cost of a prompt on every operation.)

Requirements

  • macOS (/usr/bin/security)
  • DeepSeek Harness ≥ 0.1.0-rc.6 (peer ranges cover both the 0.1.x line and 0.2.0-rc.1)

On DSH 0.2 and newer the plugin must be mounted as a package — installed into the profile, or linked into its node_modules — and referenced by package name. A row that points at an absolute index.js path does not work there, for two reasons: the runtime applies its peer-compatibility gate to plugin rows (ranges that do not admit the running version disable the row), and a module loaded from an absolute path gets no host peer resolution (the harness's own packages live inside app.asar, so there is no on-disk copy to resolve).

Install

dsh plugin --profile <profile> add dsh-keychain-credentials
# or from source:
dsh plugin --profile <profile> add github:<you>/dsh-keychain-credentials

Then the bundle's cordis.patch.yml disables the stock provider and mounts this one. If you prefer to wire it by hand, add to your patch layer (~/.dsh/profiles/<profile>/cordis.patch.yml or --patch <file>):

- id: credentials
  disabled: true
- insert:
    - id: credentials-keychain
      name: dsh-keychain-credentials     # 包名,不是绝对路径;插件须已装/链接进该 profile
      config:
        servicePrefix: dsh-credentials   # keychain service prefix
        account: dsh                     # keychain account name

Usage

Store a secret (account = credential reference name):

security add-generic-password -U -s dsh-credentials -a DEEPSEEK_API_KEY -w 'sk-…'

The LLM adapter resolves the reference per request — no restart needed after rotation.

Semantics kept from the seam contract:

  • Process environment shadows the keychain (per-run operator intent wins).
  • An empty stored value counts as absent.
  • set/unset reject while a read-only source (the environment) shadows the ref.
  • describe checks existence without reading the value — a status query never pulls plaintext into the agent process.
  • Errors from the security CLI are sanitized: exit code and stderr only, never the command line (which would carry the secret on a failed set).
  • Secrets are always fed to security over stdin, never argv, so they never appear in ps.
  • Values longer than 128 bytes are written through security -i rather than the stdin password prompt: that prompt stores at most 128 bytes and truncates anything longer silently. Interactive mode has no such limit but reports no failure exit code, so those writes are confirmed by reading the value back. (The account token DSH stores as a records entry is one such value.)

Test

npm install   # or pnpm install — devDependencies supply the seam packages
npm test

test/test-provider.mjs exercises the real macOS login keychain with throwaway probe entries under the dsh-credentials-test service prefix (it writes, reads back, and deletes them; it never touches a real key). test/test-e2e.mjs checks the peer ranges against the installed harness and, given a profile name (node test/test-e2e.mjs desktop), the profile's mount shape.

License

MIT

内容来自项目 README(GitHub)↗

评论

评论存放在 GitHub Discussions。用 GitHub 账号登录后可发表评论或点表情。