DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Key Panel — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
K

@moruteaven/dsh-key-panel

Key Panel

DSH Desktop 的托管密钥存储。由操作员持有的密钥会以 $DSH_* 变量的形式注入助手的 shell,并提供设置页面面板和三种由操作员控制的访问模式。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:MoruTeaven/dsh-key-panel#36fc41d9e7841a6c9f6d055fdd869592f6deb7fc
README兼容性版本

兼容性与来源证明

Key Panel 以 @moruteaven/dsh-key-panel 发布,当前版本为 1.0.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/9/24

版本

1.0.0stable
2026/9/24

相关插件

正在加载相关插件…

最新版
1.0.0
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
Apache-2.0
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/9/24
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 security-access 分类下经过校验的插件。

Doctor@linxin666/dsh-doctorDSH 配置档案的事务性救援模式,配备受监督的启动器、隔离的恢复容器、确定性修复、健康监控以及本地 Web 恢复控制台DSCODE@toddzheng024/dscode-bundle完整的 DeepSeek 编码代理,支持持久化 shell、Ultra 协作和自动权限审查。Codex Subscriptiondsh-codex-subscription通过 OAuth 在 DeepSeek Harness 中使用 ChatGPT 和 Codex 订阅,支持配额管理、安全重置、网页搜索、图像和快速模式Auto Reviewdsh-auto-review针对 DeepSeek Harness 审批请求的第二模型 AI 自动审查:只读审查子代理在审批应答链上决定允许或拒绝,并采用故障关闭回退机制和完整的会话日志审计。

README

dsh-key-panel

@moruteaven/dsh-key-panel on npm · English · 简体中文 · 日本語

A secret store for DSH Desktop. Keep API keys in one place, have them injected into the assistant's shell as $DSH_* variables, and decide — from a settings-page panel — exactly how much the assistant is allowed to do with them.

The problem this solves: an assistant that needs to call Cloudflare, or OpenAI, or your database needs a credential. Pasting it into the conversation puts it in the transcript forever. Sending it as a file means it lands in some directory you forget about. This puts it in one file, exposes it as an environment variable, and never lets the value enter the conversation at all.

Assistant needs to deploy a Worker
  → writes: wrangler deploy --api-token $DSH_CLOUDFLARE_TOKEN
  → the shell has it; the model's context never does

Features

  • Settings-page panel. Add, edit, reveal, copy and delete keys where you already manage the rest of the app. No config file editing.
  • $DSH_* injection. Every key becomes a variable in the assistant's shell. Changes take effect on the next command — no restart.
  • Three access modes, chosen by you and persisted: readonly (default) · write · edit.
  • Platforms and accounts. Most providers need two values (an account id and a token), and one provider often has several accounts. Group keys under a platform and an account; each slot becomes DSH_<PLATFORM>_<ACCOUNT>_ID and DSH_<PLATFORM>_<ACCOUNT>_KEY. Grouping is only a way to organise storage — the variable names stay flat, and so does everything that reads them.
  • Provenance tracking. Keys record whether you or the assistant created them, and a write cannot launder one into the other.
  • Recent activity. A log of what each shell command was handed, alongside the purposes the assistant chose to declare. The two are shown together, never paired. Names and timestamps only — no value is ever written to it.
  • Name scope (not exposed yet). The host can restrict the assistant to names matching DSH_AGENT_*, and does enforce a scope already in the store, but the panel field is hidden in this release.
  • Two-phase deletion. Deleting needs a second, confirmed call with a short-lived token — a single stray tool call cannot remove a key.
  • Values never reach the model. No tool returns a key value, in any mode.
  • Idempotent, hot-reload safe. The panel re-reads policy on every call.

Install

Settings → Plugins → search "dsh-key-panel" → Install → restart DSH Desktop

The panel then appears in Settings → Keys.

Other channels:

  • npm — npm i @moruteaven/dsh-key-panel, then add the bare package name @moruteaven/dsh-key-panel to dsh.profile.bundles in your profile's package.json. The bundles list takes bare package names only; a file: or path spec is rejected there (file: is fine in dependencies).
  • From source — see CONTRIBUTING.md.

Usage

Add a key

Settings → Keys → Add key.

FieldNotes
NameMust match DSH_[A-Z][A-Z0-9_]* — e.g. DSH_CLOUDFLARE_TOKEN
PurposeOptional. Shown to the assistant as the variable's description.
ValueThe secret. Stored plaintext; shown masked afterwards.

Then tell the assistant to use it:

Deploy the worker, the token is in $DSH_CLOUDFLARE_TOKEN.

Platform and account

When a provider wants two values — an account id and a token, say — and you have more than one account with them, the flat list gets hard to read. Add platform and Add account group those keys and name them for you:

You enterYou get
platform CF, account WORKDSH_CF_WORK_ID and DSH_CF_WORK_KEY

The panel shows both names before you commit, so you can see what will land in the shell. Identifiers are uppercase letters, digits and underscore; lowercase is rejected rather than silently uppercased, because a name you did not ask for is worse than one you have to retype.

Fill in the values from the account itself. Once the account exists, its two variable names are already decided — they are derived from the identifiers, not chosen by you. So the account row carries a Fill in keys button that opens a popup collecting both values at once and files them under that account. The derived names are shown in the popup but never typed, which is the point: making you carry a name the plugin computed up to the flat Add key card, once per field, was transcription.

Each slot shows whether it holds a value, and a slot can be left blank for now. Add key is still there for ungrouped keys, which have no account to be filled in from.

Each platform and account also takes a display name, which is what the panel shows. It is separate from the identifier on purpose: identifiers are baked into variable names and cannot be changed afterwards, while display names are yours to edit at any time. If you want a platform to read as "Cloudflare" while its variables stay DSH_CF_*, that is exactly what the two fields are for.

If the names an account would produce are already taken — you added DSH_CF_WORK_KEY by hand before creating that account — the panel says so and asks before continuing, naming the keys involved. Saving over them would replace their values, so it is not done silently.

Two things worth knowing:

  • Grouping is organisation, not security. The variables are ordinary flat DSH_* names — that is the whole point, since a shell has no nesting. What grouping buys you is a readable panel and, incidentally, a scope that lines up with one account (see below).
  • Deleting a platform or account will not take your secrets with it. If keys are still filed under it the delete is refused; you clear or re-file them first. If you ask it to go ahead anyway, the keys are un-filed — moved back to the ungrouped list — and their values are untouched.

Keys you never filed stay exactly as they were. Nothing has to be reorganised for this feature to be useful, and an existing store keeps working unchanged.

Recent activity

The panel keeps two records, and puts them side by side:

RecordWritten byContains
declaredthe assistant, calling key_panel_intenta timestamp it says a purpose, and the names it means to use
handed to commandthe plugin itself, every time a shell command is resolveda timestamp and the names that went into that command

They are shown together, not paired. An intent and a use that sit near each other in the list may belong to the same task, but nothing in the data says so, and joining them would be a guess presented as a fact.

What "handed to command" does not mean. The host resolves the whole $DSH_* environment before every shell command and cannot see what the command does with it. So a row means those names were in scope at that moment — never that the command read them, and never what it did with them. Treat the log as a signal about which credentials a workflow depends on, not as an audit trail.

Two practical consequences of that:

  • The assistant may declare nothing. key_panel_intent is optional and skipping it costs nothing, so expect undeclared activity. The plugin asks for intent where it is cheap, but never requires it — a required step in front of every command would turn into a reflex and stop carrying information.
  • Costs are off the command path. Records are buffered in memory and written in batches, so logging never slows down a command. The log is capped (oldest entries are dropped) and a write failure is swallowed — losing the tail of this file costs a trend, not a secret.

The log lives beside the key store in usage.jsonl and holds names and timestamps only. No value is ever written to it.

Access modes

ModeAssistant canAssistant cannot
readonly (default)use keyschange anything — no model-facing tool is registered at all
writeadd keys; replace keys it createddelete anything; edit your keys
editadd, change, delete— (delete still needs confirmation)

Set it in the panel. It applies to the next tool call, no restart.

Start at readonly. Move up only when you actually want the assistant adding its own credentials — that is the one workflow the other two modes exist for.

Restrict by name

Not exposed in the panel yet. The scope itself works — it is validated, persisted, and enforced on every model call, and a scope already in the store keeps applying. Only the field that sets it is hidden for now, behind the SHOW_SCOPE_UI flag in lib/client.js. Set it by hand in the store if you need it; flipping the flag brings the field back.

This has a security consequence worth knowing before you pick a mode: with no scope set, the access mode is the only thing limiting which keys the assistant can reach. An unset scope means no restriction. If you want a narrower boundary, set one — in the store by hand for now.

The scope limits the assistant to matching names:

ValueEffect
(blank)no restriction
DSH_AGENT_*only names with that prefix
DSH_CF_*only that platform's keys
DSH_CF_WORK_*only one account's credentials

A single * is the only metacharacter. It cannot express a path or a regex.

Configuration

SettingWhereDefault
Access modePanelreadonly
Name scopeStore only (field hidden)unrestricted
Store location$DSH_HOME/key-panel/keys.json~/.dsh/key-panel/keys.json
Activity log$DSH_HOME/key-panel/usage.jsonl~/.dsh/key-panel/usage.jsonl

Storage format

{
  "version": 2,
  "policy": { "accessMode": "readonly", "scopePattern": null },
  "platforms": {
    "CF": { "label": "Cloudflare", "createdAt": 1758428400000 }
  },
  "accounts": {
    "CF/WORK": { "platform": "CF", "identifier": "WORK", "label": "Work", "createdAt": 1758428400000 }
  },
  "keys": {
    "DSH_CF_WORK_TOKEN": {
      "value": "…",
      "description": "Cloudflare Workers deploy token",
      "origin": "operator",        // "operator" | "model"
      "platform": "CF",            // optional grouping
      "account": "WORK",
      "field": "key",              // "id" | "key"
      "createdAt": 1758428400000,
      "updatedAt": 1758428400000
    }
  }
}

Writes go through a temp file + fsync + rename, so a crash cannot leave a renamed-but-empty file behind — which would read back as "all keys deleted".

A version 1 file loads as-is: the grouping fields are optional, so keys written before platforms existed simply read back as ungrouped. No migration step.

usage.jsonl

Activity records live in a separate file, one JSON object per line:

{"t":1758428400000,"kind":"intent","names":["DSH_CF_WORK_TOKEN"],"note":"deploy staging"}
{"t":1758428450000,"kind":"use","names":["DSH_CF_WORK_TOKEN"]}
FieldMeaning
tepoch milliseconds
kindintent (assistant-declared) or use (handed to a command)
namesDSH_* names involved, deduplicated. Never a value.
noteintent only — the purpose, trimmed to 500 characters

It is appended in batches and capped at the most recent 1000 entries, so it cannot grow without bound. Unlike keys.json it is not written atomically and not fsynced per line: a hard kill can truncate the final line, which readers skip. That is the correct trade here — this file is a signal, not a secret, and paying for durability would slow down every command.

Because the two kinds are written by different parties at different moments, they are stored as separate entries and correlated by timestamp when displayed. The plugin never joins them.

Security

Values are stored in plaintext. This is deliberate and explained in full in SECURITY.md, along with the trust model, the invariants, and the non-goals.

The short version:

  • Anything running as your OS user can read the store. Treat it like an .env.
  • The assistant can never read a key value into its transcript, in any mode.
  • The assistant can never change the access mode or the scope — those are operator-only.
  • In readonly, the model-facing tools do not exist rather than refusing — the capability is absent, not argued about.

Development

npm test            # both suites — 427 assertions
npm run test:host   # host half: policy, store, tools, gateway (298)
npm run test:client # client bundle: contract, slots, RPC, dictionaries (129)

The client suite loads lib/client.js the way the real frontend does — a fake window.__ModuleLoader__, seed-word-only require — and asserts the bundle registers without side effects, that only seed modules are required, and that every RPC endpoint the panel calls matches the gateway.

No build step. Plain ESM on the host, a CJS factory string in the browser.

lib/
  policy.js   access modes, scope glob, decision function
  store.js    persistence, provenance, atomic write
  tools.js    model-facing tools, confirmation ledger
  index.js    host half — Typert gateway, shellEnv registration
  client.js   browser bundle — settings panel

See CONTRIBUTING.md for the conventions this repo enforces and how to run the suites.

Compatibility

  • DSH Desktop 2.0.11+ (dsh 0.1.5-rc.1)
  • Node 20+
  • Client half: web platform only

License

Apache License 2.0 · attribution in NOTICE

Apache-2.0 was chosen over MIT/BSD for this project because it adds two clauses that matter for a tool handling credentials:

  • A patent grant (§3), which MIT and BSD lack entirely — and it obliges contributors to grant the same.
  • A trademark limitation (§6), so the authors' names cannot be used to endorse a fork. That is the same protection BSD-3-Clause's endorsement clause gives.

Everything else is permissive: use it commercially, modify it, ship it closed.