Install
Inside DeepSeek Harness, with dsh-market
dsh plugin --profile web add dshmarket
Or from the command line
dsh plugin --profile web add github:nengong-ai/dsh-keychain-credentials
Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.
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) andrecords(structured credentials likeclient-connection/browser-sessiongrants). 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/unsetreject while a read-only source (the environment) shadows the ref.describechecks existence without reading the value — a status query never pulls plaintext into the agent process.- Errors from the
securityCLI are sanitized: exit code and stderr only, never the command line (which would carry the secret on a failedset). - Secrets are always fed to
securityover stdin, never argv, so they never appear inps. - Values longer than 128 bytes are written through
security -irather 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 arecordsentry 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
Comments
Comments live in GitHub Discussions. Sign in with GitHub to post or react.