DeepSeek Harness Plugin Hub

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

探索

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

社区

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

相关链接

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

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

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

dsh-canary

Canary

DeepSeek Harness 插件:在模型可见上下文中植入金丝雀字符串,并拒绝任何回显该字符串的工具参数或出站 URL

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

npx -y @deepseek-ai/dsh plugin --profile web add github:jwilson411/dsh-canary#d21f85e2ed3e36b4963bc968c9db639d9aee7b54
README兼容性版本

兼容性与来源证明

Canary 以 dsh-canary 发布,当前版本为 0.1.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
any
发布来源
github
Registry 更新时间
2026/8/31

版本

0.1.0stable
2026/8/31

相关插件

正在加载相关插件…

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

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

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

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

认领这个 Plugin →
报告问题

相关插件

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

Doctor@linxin666/dsh-doctorDSH 配置档案的事务性救援模式,配备受监督的启动器、隔离的恢复容器、确定性修复、健康监控以及本地 Web 恢复控制台Pocketdsh-pocket把 DeepSeek Harness 装进你的口袋:一个包、一个设置页,手机扫码即同步访问电脑上的 DSH(局域网 + 公网,实时同屏)。DSCODE@toddzheng024/dscode-bundle完整的 DeepSeek 编码代理,支持持久化 shell、Ultra 协作和自动权限审查。Auto Reviewdsh-auto-review针对 DeepSeek Harness 审批请求的第二模型 AI 自动审查:只读审查子代理在审批应答链上决定允许或拒绝,并采用故障关闭回退机制和完整的会话日志审计。

README

dsh-canary

A DeepSeek Harness function plugin that plants a canary string in model-visible context and denies any tool call whose arguments or outbound URL echo it back. The denial happens before the tool runs, carries code: 'CANARY_TRIP', and appends one JSONL line that does not contain the canary.

The plant looks like this — the tag, then 32 hex characters:

DSHCANARY_…

Nothing legitimate in a session ever carries that value. It is not an argument to anything, not an identifier, and not data. So a tool call holding it is not ambiguous: something read the context and tried to send it somewhere.

What it is and is not

This detects echo-exfil of this planted canary. It does not detect prompt injection in general, and it is not a red-team product.

  • Not a prompt-injection detector. It catches one behaviour: the planted canary being repeated into a tool argument or a URL. An injection that exfiltrates your actual data, edits a file, or persuades the model to skip a step is invisible here — the canary was never involved. A clean incident log means this canary was not echoed, and nothing more.
  • Not a red-team product. There are no attack payloads in this repository, no jailbreak corpus, no injection generator, and nothing that targets any harness. It ships a tripwire and a guard; producing something to trip them is not its job and not in the tree.
  • Not a classifier. The check is a substring match against one exact value. There is no model, no scoring, no entropy heuristic, and no threshold to tune. A near miss — a truncated canary, a different case, the value split across two fields — does not trip it, on purpose: a false trip denies a legitimate call, which is the failure mode that gets a plugin like this switched off.
  • Not a covert control. The canary is planted in a tool description the model can read, together with the instruction not to echo it and the statement that echoing it will be denied. A model that trips this has been told, in the same context, exactly what would happen.
  • Not data-loss prevention. It watches one seam — the arguments handed to a wrapped tool, and URLs you check — and nothing else. It does not watch files, egress, or a mail flow.

A trip is strong evidence that context reached an outbound argument. No trip is not evidence that nothing did.

Install

dsh plugin --profile default add github:jwilson411/dsh-canary

dsh plugin forwards to pnpm inside $DSH_HOME/profiles/default, then reconciles the profile: because this package's manifest declares dsh.bundle.patch, it is appended to the profile manifest's ordered dsh.profile.bundles list and its cordis.patch.yml becomes a layer. Remove it the same way, with remove in place of add.

Pinned DSH release candidate

This package is written and tested against the pinned release candidate 0.1.1-rc.2 — @deepseek-ai/dsh-tools@0.1.1-rc.2 is pinned exactly in devDependencies so tests run against one known API, and the peer range is ^0.1.1-rc.2, matching how the harness's own tool packages declare it.

The two seams this RC does not have, and what is done instead

The pinned release candidate exposes no system-prompt inject seam and no tool-execute middleware seam. This package does not invent either one, and reaches into no private API.

what is neededwhat the RC offerswhat this package does
put the canary where the model reads itno prompt inject seamregisters canary_context, whose description carries the canary. A tool description is model-visible by construction.
deny a call before it executesno execute middleware seamexports wrapExecute, applied by the host at its own call site.

The plant is therefore exactly as durable as the tool registry: registration happens inside apply, so the Cordis fiber owns it, and stopping, updating, or reloading the plugin retires the plant with no bookkeeping.

What it registers

Cordis plugin idcanary (the row id in cordis.patch.yml)
Injectstools — a hard dependency; the plugin waits rather than degrading
Toolscanary_context (the plant), canary_status (the tally)
Argumentsneither tool takes any

canary_context returns { planted, prefix, reminder, plugin }. canary_status returns { planted, prefix, incidents, lastTool, lastWhere, lastAt, log, plugin }.

Neither tool ever returns the canary. prefix is the first 12 characters — the ten-character tag plus two hex digits, which is enough to confirm which plant is live and 120 bits short of enough to reconstruct it. A status tool the model can call is a status tool an injected instruction can call, so there is nothing there worth calling it for.

The library API

The tools are the visible half. The guard is the product:

import { plantCanary, wrapExecute, CanaryTripError } from 'dsh-canary'

const state = plantCanary()                       // or plantCanary({ canary })

const guarded = wrapExecute(tool.execute, state, { tool: tool.name })

try {
  await guarded(argsFromTheModel, exec)
} catch (error) {
  if (error instanceof CanaryTripError) {
    log.warn({ code: error.code, where: error.where, tool: error.tool })
    return
  }
  throw error
}

On a hit the inner execute is never called, one incident line is appended, and the error propagates. On a miss the arguments are passed through untouched — this plugin redacts nothing and rewrites nothing.

Two narrower entry points, for a host that owns a different call site:

import { assertNoCanary, assertNoCanaryInUrl } from 'dsh-canary'

assertNoCanary(requestBody, state, { tool: 'http' })   // throws on a hit
assertNoCanaryInUrl(candidate, state, { tool: 'fetch' })

Both throw and write nothing; wrapExecute owns the incident so that one denial is one line whichever a host reaches for. scan(value, canary) is the same walk exposed on its own, returning { where, path } or null.

What counts as an echo

The walk covers strings, arrays, and plain objects, and it scans object keys as well as values — a payload keyed by the canary leaks it as surely as one that holds it. Cyclic structures are walked once, not forever.

where is reported as url when the hit is:

  • under a key named url, uri, or href (case-insensitively), at any depth;
  • inside a string that is an absolute URL — one beginning with a scheme — wherever in the arguments that string sits;
  • in the path, query, fragment, userinfo, or host of such a URL;
  • in any of the above after percent-decoding, up to two passes — a canary pasted into a query string arrives encoded, and that is the exfil working.

and args otherwise — a URL embedded in a longer piece of prose is denied like anything else, and recorded as args, because where names the field the echo sat in rather than scraping URLs out of text. A url hit anywhere in the walk wins over an args hit, because the outbound surface is the one worth naming.

Absolute-URL detection here is deliberately shape-based rather than a fetchable check: data: and javascript: are URLs for this purpose. Whether a URL may be opened is a different question, for a different plugin.

The error

error instanceof CanaryTripError
error.code    // 'CANARY_TRIP' — always
error.where   // 'args' | 'url'
error.tool    // the tool whose call was denied
error.prefix  // the first 12 characters of the canary, never more
error.path    // dotted path to the offending field, or null
error.message // human-readable, and canary-free

The message carries the prefix and never the value. An error message is the thing most likely to be logged, rendered, or handed straight back to the model that just tried to exfiltrate the canary; putting the canary in it would hand over the answer.

The incident log

Append-only JSONL, created 0600, one line per denied call:

{"ts":"2026-08-31T12:00:00.000Z","tool":"http_get","where":"url"}

Three fields, and the record is constructed from three scalars rather than spread from a caller's object, so there is no path by which the canary reaches the file. where is validated against args / url on the way in and on the way back out, so a log appended to by something else cannot smuggle a value into canary_status.

The whole canary is emitted in exactly one place, once, at plant time: the canary_context description. Not the log, not the status tool, not an error.

A write failure is deliberately not swallowed — a tripwire whose audit trail silently stops is worse than one that fails loudly.

Config

keytypedefault
canarystringgenerateda fixed plant, DSHCANARY_ + 32 lowercase hex
incidentLogstring.dsh-canary.jsonlpath of the append-only JSONL log

Set them from the profile's own cordis.patch.yml — note that an id-targeted patch replaces the row's whole config, so restate every field you mean to keep:

- id: canary
  config:
    incidentLog: /var/log/dsh/canary.jsonl

Resolution order is patch config, then environment, then generated: a patch row is the deployment's stated intent, so it is not silently overridden by an ambient variable. The environment fallbacks are DSH_CANARY and DSH_CANARY_LOG, each used only when the patch row omits the key entirely.

Leave canary unset in production. A fresh 128-bit canary is minted on every apply, so a plant lasts one plugin lifetime and nobody gets a second session to work on the same value. Fix it only when the plant must be predictable:

  • a test suite that has to assert on the value — this repository's own suite is the first consumer of the key, and every test below plants a fixed canary so the assertions are deterministic;
  • a fleet of workers that must share one plant.

A configured value that is not canary-shaped is rejected at apply time with an InvalidCanaryError rather than quietly replaced by a random one: a silent substitution makes every downstream expectation wrong, and a short one would match half the arguments in the session.

Layout

package.json          manifest + `dsh.bundle.patch` — what makes this a bundle
cordis.patch.yml      the bundle's patch layer: one insert, one plugin row
src/canary.js         the pure half: mint, scan, deny, append
src/index.js          the plugin: `name`, `inject`, `apply(ctx, config)`, the tools
test/                 offline tests: the walk, a stub context, a hygiene scan
package-lock.json     the pinned dependency tree `npm ci` installs in CI

Tests

npm install
npm test

Offline by construction, and not merely by intention: every case is a string walk, apply is handed a stub context that records registrations, tool results are validated against the real @deepseek-ai/dsh-tools pinned to 0.1.1-rc.2, and every incident log is written under the OS temp directory and removed afterwards. test/hygiene.test.js asserts that the shipped source opens no socket, no fetch, and no subprocess, that the tree carries no machine names, mount paths, or credential-variable names, and that CI needs no credentials.

CI runs the same two commands on Node 22.x and 24.x with contents: read and no secrets.

Version

Initial release, 2026-08-31. MIT.