kiterunner1/dsh-plugin-audit

dsh-plugin-audit

Agent Skill for DeepSeek Harness (DSH):装第三方插件前的静态安全审计 —— 扫出 eval、patch 层 !!js、安装期脚本、开机自启、凭据复制、出网域名与提示词注入面。Audit third-party DSH plugins before installing. Works with DSH, Claude Code, Codex, Cursor & any Agent Skills compatible agent.

Uncategorizedagent-skillagent-skillsai-agent

plugin-audit

中文 · English

Before you install a plugin, look at what it will touch.

An Agent Skill for DeepSeek Harness (DSH): a static security audit that surfaces eval, patch-layer !!js, install-time scripts, autostart entries, credential copies, network egress hosts, subprocess calls and prompt-injection surfaces — reported in three tiers: hard vetoes, capabilities to confirm, and things to merely know.

A third-party plugin is someone else's code running in the same process with the same privileges as your harness. Before installing, almost nobody can see what it does; after installing, you may not be able to undo it. This skill turns "take a look first" into a single command.

Note: This is a community project. It is not affiliated with, authorized by, or endorsed by DeepSeek.

Why it exists

DSH has production-grade protection for model behaviour (sandboxing, permissions, approvals) but no security design for plugin code. The four facts below come from the official Discussion #454 and third-party plugin security audit reports:

FactConsequence
Plugins run in the same process and with the same privileges as the hostWhatever DSH can do, the plugin can do
The file sandbox blocks writes, not readsYour ~/.dsh, .credentials.yaml, and .ssh are all readable
Patch-layer !!js expressions run at load timeNothing looks wrong in package.json — the code runs while loading
A malicious bundle executes on installAnd a later remove does not undo a backdoor already dropped

So with an unknown plugin the risk is not "it might not work well" — it is "you have no idea what it did".

What it checks

Everything is pattern matching with context filtering (comment-only lines are skipped; one rule counts once per file):

Hard vetoes (VETO) — any single one is enough to stop

ItemWhat matches
Dynamic evaluationeval( / new Function / vm.runIn*
Patch injection!!js (executes at load time)
Install-time scriptspostinstall / preinstall / prepare / prepublish
AutostartRegistry Run / schtasks / LaunchAgents / systemd units
Credential copyingCopying credential / .env / .ssh / id_rsa somewhere else

Capabilities to confirm (RISK) — not accusations, just what it is allowed to do

ItemWhat matches
Network egressfetch / axios / WebSocket / net.connect
Telemetry exporttelemetry / otel / exporter
Remote endpointsHardcoded public URLs (a separate host list is produced)
Credential readsprocess.env / .credentials.yaml / .npmrc / id_rsa
Command executionchild_process / spawn / execFile / taskkill
Startup pathTouching cordis.patch.yml / dsh.profile.bundles
Instruction surfaceRegistering system prompts, agent/pre-step, injecting session messages
Untrusted content into promptsThe "read external content → concatenate into a prompt" shape
File writeswriteFile / appendFile / rmSync / unlinkSync
Supply chaingit+ / github: / fetching remote code at runtime
Official package pathsReferencing or modifying node_modules/@deepseek-ai

Observations (INFO) — good to know

Persistent polling / heartbeats / watchdogs (setInterval / watchdog); self-update and hot-reload (self-update / autoUpdate).

Reading the output

  • VETO: confirm line by line whether it is real behaviour or a false positive — comment-only lines are skipped, but string constants and documentation text can still match.
  • RISK: focus on the egress host list and separate the annotated documentation/namespace hosts (w3.org, github.com, json-schema.org, …) from real connection targets.
  • INFO: normal behaviour; just be aware.

One line to remember: a match is not malice, and zero matches are not safety. The script tells you where to look; the conclusion comes from reading the code.

Install

The skill is named plugin-audit (this repository is named dsh-plugin-audit). Pick either route:

# Route 1: the skills CLI (DSH is not registered with the CLI, so borrow cline's
# directory mapping, which resolves to ~/.agents/skills/)
npx skills add kiterunner1/dsh-plugin-audit -g -a cline -y

# Route 2: clone by hand — the target directory must use the skill name plugin-audit
git clone https://github.com/kiterunner1/dsh-plugin-audit.git ~/.agents/skills/plugin-audit

Skill directories per agent:

AgentProjectUser
DeepSeek Harness<project>/.dsh/skills/ (rank 100) or <project>/.agents/skills/ (rank 200)~/.dsh/skills/ (rank 400) or ~/.agents/skills/ (rank 500)
Claude Code<project>/.claude/skills/~/.claude/skills/
Codex<project>/.codex/skills/~/.codex/skills/
VS Code Copilot<project>/.agents/skills/~/.agents/skills/
Other compatible agentsFollow that agent's skill directory conventionSame as above

No restart needed: DSH watches these directories, so a new skill enters the catalog on the next model step. To verify, ask your agent "is this plugin safe to install" — the skill should trigger. In DSH you can also load it directly with skill(plugin-audit).

Usage

The script lives inside the skill directory; resolve it against the skill's own base directory:

# Audit a directory
pwsh -File "<base>/scripts/audit-plugin.ps1" -Path <plugin-directory>

# Audit by package name: resolved under <DSH_HOME>/profiles/*/node_modules and npm global
pwsh -File "<base>/scripts/audit-plugin.ps1" -Path <package-name>

# Several targets: comma-separated (repeating -Path raises a binding error)
pwsh -File "<base>/scripts/audit-plugin.ps1" -Path ./plugin-a,./plugin-b

# Machine-readable
pwsh -File "<base>/scripts/audit-plugin.ps1" -Path <directory> -Json

With a package name you do not need the full @scope/name — name also finds @scope/name. The script is read-only: it changes nothing and installs nothing.

Sample output

(Format illustration; the figures come from a real run, with the package name, repository and line numbers anonymised.)

═══ 插件安全审查:<plugin-name> ═══
  目录    : ~/.dsh/profiles/web/node_modules/<plugin-name>
  包/版本 : <plugin-name>@1.2.3   license=MIT
  规模    : 源文件 25  依赖 1  开发依赖 3  .git=False
  dsh 字段: bundle, client, compatibility

  结论:未命中硬否决项 —— 请核对下方能力/域名清单后决定
  硬否决项 0 条 · 需确认能力 6 类 · 观察项 0 类

  ── 能力清单(不是指控,是"它有权做什么")──
    [出网能力] 2 个文件命中 — 发起网络请求(看下方域名清单:连了谁、传了什么)
        lib/client.js:3  fetch(   (x1)
    [供应链] 1 个文件命中 — 非固定来源依赖 / 运行时拉远端代码
        package.json:96  git+   (x1)
    [凭据读取] 4 个文件命中 — 读取环境变量/凭据(与出网组合即为外传面)
        lib/config.js:77  process.env   (x1)
    [启动路径] 1 个文件命中 — 触碰 DSH 启动组合(patch/bundles):写坏即起不来
        package.json:21  cordis.patch.yml   (x2)
    [文件写入] 2 个文件命中 — 写/删文件(看写到哪:自己目录内 vs 全盘)
        lib/store.js:130  writeFileSync(   (x1)

(The report itself is Chinese-only for now; -Json gives you a locale-neutral structure.)

What the script cannot replace

Pattern matching only narrows the field. These four must be read by a human:

  1. package.json → scripts (especially postinstall / prepare) and the dsh field.
  2. cordis.patch.yml in full — above all, whether it contains !!js.
  3. The "read external content → concatenate into a prompt" shape in the entry file: that is the main prompt-injection channel.
  4. Dependency sources: pinned versions, or git+ / github: / remote code fetched at runtime.

After the audit: trial install in isolation

Do not install straight into the profile you use daily. Use a throwaway profile:

dsh plugin --profile tmp-audit add <package or github:owner/repo#<commit>>
dsh --profile tmp-audit --dump-config     # check that the composition resolves

Two rules: stop the running DSH before installing plugins, otherwise profiles/<name>/node_modules gets deleted half-way and you are left with a profile that runs now and dies on restart; and pin dependencies to a commit (github:owner/repo#<sha>) instead of letting them drift with the default branch.

Platform support

  • Requires PowerShell 7+ (pwsh) on Windows, macOS, or Linux.
  • Uses only standard PowerShell cmdlets — no Node, no Python, no third-party modules.
  • Path resolution goes through DSH_HOME / USERPROFILE / HOME; it assumes nothing about your username or install location.

FAQ

Q: Can it miss things? Yes. This is static pattern matching, so it only sees code that looks like a dangerous behaviour. Obfuscation, dynamically assembled strings, and runtime downloads all get past it. Its purpose is to make blind installs less likely — not to replace an audit.

Q: Does a VETO hit mean the plugin is malicious? Not necessarily. A plugin with !!js inside a documentation string will match too. Treat VETO as "stop and confirm by hand", not "guilty".

Q: Why is reading cordis.patch.yml still necessary? Because patch-layer !!js executes at load time, sits outside the normal JS entry point, and is easy to miss in a static scan — and a broken patch can stop DSH from starting at all.

Q: Does it work for non-DSH plugins? Yes. It scans for what code touches, which is host-agnostic. Only a few rules (dsh field, cordis.patch.yml, @deepseek-ai paths) are DSH-specific and simply will not match elsewhere.

Repository layout

dsh-plugin-audit/
├── SKILL.md                # skill entry: when to use, how to run, how to read, trial-install discipline
├── README.md / README.en.md
├── LICENSE                 # MIT
├── .gitattributes
└── scripts/
    └── audit-plugin.ps1    # the audit script (read-only)

Contributing

  • A new rule must name the real risk it prevents — otherwise leave it out. A gate that flags everything is not a gate.
  • Follow the Agent Skills constraints: name in kebab-case; a tight description (DSH truncates catalog descriptions at 500 characters by default); progressive disclosure in the body.
  • PRs welcome: new rules, false-positive fixes, skill directories for more agents, and English polish.

License

MIT — see LICENSE.