DeepSeek Harness plugin

dsh-plugin-security-review

Static bundle form of the DSH plugin-install security gate: reviews cordis_define/cordis_run with a fail-safe policy, plus review/audit tools. Loads automatically from the profile layer stack.

Jump to install

Source facts

Repository
ShanHaiFish/dsh-plugin-security-review
Latest update
Aug 21, 2026
Category
Security & Permissions
GitHub stars
2
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/ShanHaiFish/dsh-plugin-security-review
Plugin: dsh-plugin-security-review
Author: ShanHaiFish

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer4 files
README.en.mdSource · read only
README language

Plugin Install Security Review (v1.8.0)

![npm version](https://www.npmjs.com/package/dsh-plugin-security-review) ![npm downloads](https://www.npmjs.com/package/dsh-plugin-security-review) ![License: MIT](LICENSE)

A security gate for DSH plugin installation. It statically reviews the source of dynamic plugins being installed or run via cordis_define / cordis_run, and blocks them under a security-first (fail-closed) policy. It also ships two review tools: plugin_security_review and plugin_security_audit.

Since v1.5.0 this is a static bundle plugin that loads automatically from the profile layer stack — no need to define/run it again after each DSH restart.

Repository Contents

PathDescription
package.json + cordis.patch.yml + lib/Static bundle (recommended): auto-starts after dsh plugin add
manifest.json + package-source.jsDynamic-plugin fallback form (v1.4.0): for profiles without bundle support, loaded per the recovery flow below
tests/gate.test.mjsBehavior tests for the dynamic form (node tests/gate.test.mjs)
tests/static.test.mjsBehavior tests for the static form (node tests/static.test.mjs)

Installation (static bundle, recommended)

dsh plugin --profile web add dsh-plugin-security-review

> Published on npm (registry.npmjs.org/dsh-plugin-security-review, latest v1.8.0). Use the command above to install from the registry in production; the file: local install is only for unpublished or offline debugging.

For a local, unpublished copy, point file: at this repository (the path must not contain spaces):

dsh plugin --profile web add file:/path/to/dsh-plugin-security-review

After restarting dsh web the gate is active automatically: plugin_security_audit (no arguments) should show 「守卫: 运行中 (v1.8.0)」.

Installation (dynamic plugin, fallback)

Only for profiles without bundle support:

1. Have the agent read package-source.js and manifest.json. 2. cordis_define: plugin: { kind:"new", idPrefix:"secur" }, take name/purpose from manifest.json, and take code.host from the part of package-source.js after return { ... }. 3. cordis_run to activate; verify with plugin_security_audit.

> The dynamic form does not survive DSH process restarts and must be reloaded each time; the static bundle form has no such limitation.

What's New in v1.8.0 (vs. v1.7.0)

  • New "Agree + Allowlist" option in the popup. The dialog is now [Reject] [Agree+Allowlist] [Agree]. Clicking Agree + Allowlist (outcome approveTrusted) — in addition to writing the code fingerprint into approved — writes the plugin family into trusted-local (a define of a new plugin adds the idPrefix to prefixes; an existing/run adds the pluginId to pluginIds). Install and development iterations of the same plugin (where each code change produces a new fingerprint) no longer re-prompt. Agree (outcome approve) still approves only that code fingerprint (invalidated on any code change). The allowlist write is best-effort: a failure (permissions/corrupt file) is logged and does not block the fingerprint approval; the read-side fail-closed semantics are unchanged.
  • Fixed: clicking Agree could retry in the wrong conversation. The old implementation kept a single browser-side sessionId captured from any session/subscribed envelope, but DSH's mux stream is all-session aggregated — with several conversations open the last subscriber overrode the value and session.prompt landed the retry in another working conversation. The gate now records the origin session id (exec.agent.sessionId) into each pending entry's agentId at interception time, and the popup injects the retry to that origin session via connection.api.sessions.prompt; if agentId is missing or the target can't be prompted it silently falls back to manual retry.
  • Popup hint/button copy updated; new tests T19 (origin session id exposure) and T20 (agree+allowlist writes the allowlist and later family variants don't re-prompt).
  • The dynamic fallback form is version-synced to v1.8.0 but does not provide the popup (a browser-GUI feature, static bundle only; its ASK still uses the seam).

What's New in v1.7.0 (vs. v1.6.0)

  • Approval popup (agree/reject, new — static bundle): ASK verdicts no longer route through the official approval seam (which in approval-disabled deployments is auto-rejected into the the user rejected tool dead end). Instead the gate parks the approval + denies + shows a browser popup. Clicking Agree persists the code-fingerprint approval, injects a session message so the agent auto-retries, and the install/run then succeeds; clicking Reject records and abandons it. The popup is driven by the gate's own HTTP routes (/security-gate/approvals/pending, /security-gate/approvals/decide) with polling, rendered in the shell.overlay layer; the gate now ships a client/client.js client half (dsh.client declaration + exports["./client"]).
  • askMode config: <DSH_HOME>/storages/plugin-security-gate/config.json with {"askMode":"seam"} falls back to the official approval UI; the default (absent/corrupt) is popup.
  • Popup styling & theme adaptation: a centered modal with a semi-transparent mask (replacing the earlier side floating card to avoid occlusion); every color uses real dsw design-system tokens (--dsw-alias-bg-overlay / --dsw-alias-button-primary-fill / --dsw-alias-label-primary-foreground / --dsw-alias-bg-mask-3 / --dsw-alias-state-error-primary / --dsw-alias-state-warn-label, etc.) so light/dark themes adapt automatically and text/panel readability is preserved.
  • Audit status: the overview now shows askMode and pending-approval count; gateStatus() gains matching fields.
  • The dynamic fallback form is version-synced to v1.7.0 but does not provide the popup (a browser-GUI feature, static bundle only; its ASK still uses the seam) — documented in the README.

What's New in v1.6.0 (vs. v1.5.0)

  • Operator allowlist trusted-local (new): a file at <DSH_HOME>/storages/plugin-security-gate/trusted-local.json declares locally trusted plugins (prefixes / pluginIds / fingerprints). A matching cordis_define/cordis_run is allowed directly and recorded in the audit history as [trusted-local]. The trust anchor is the operator with file permissions, not agent self-attestation; when the file is absent or corrupt, behavior is unchanged (fail-closed). The cordis_stop/cordis_undefine self-protection is not affected by the allowlist.
  • Fixed the audit lossless-JSON error: plugin_security_audit without includeBundles returned bundles: undefined, which violates the dsh-tools lossless-JSON output check and made the tool fail; it now defaults to null.
  • BLOCK denial wording corrected: BLOCK has no human-approval channel, so the message no longer claims approval can unblock it — it now points to the trusted-local allowlist. Composition rules (HOST_EXFIL_CRED/CLIENT_COOKIE_EXFIL, etc.) are marked [not declarable], i.e. they are not reduced by CAPABILITIES: declarations.
  • The dynamic fallback form (package-source.js/manifest.json) is synced to v1.6.0.

What's New in v1.5.0 (vs. the v1.4.0 dynamic form)

  • Static bundle: named exports name/inject/apply; tools are registered via ctx.tools.register(defineTool(...)); interception happens via ctx.on('tools/pre-execute'); auto-starts on boot.
  • Self-upgrade exemption removed: the static form upgrades via dsh plugin update; cordis_define/cordis_run now review all dynamic plugins with no secur prefix or lineage-token exemptions; unreviewable source always fails closed (ask).
  • The review engine, scoring model, capability declarations, cross-session approval persistence, and inventory audit remain unchanged from v1.4.0.

Decision Policy (security-first)

VerdictConditionBehavior
BLOCKcritical>0 or high≥2 or score≥100Refuse install/run, with a full report (no popup)
ASKhigh≥1 or score≥40askMode=popup (default): park the approval + show a Reject / Agree+Allowlist / Agree dialog; agree persists the fingerprint and auto-retries (to the origin session), agree+allowlist additionally writes trusted-local; askMode=seam: route to the official approval service (equivalent to reject when approvals are disabled)
WARNscore≥10Allow, with the review report attached to the card
ALLOWotherwiseAllow

Risk weights: critical=100, high=40, medium=15, low=4, with a total score cap of 300; declared capabilities score at half weight (minimum 1).

Review Coverage

  • Host rules: unsafe process execution (exec/execSync/shell:true — critical), VM escape, Node internal APIs, constructor-chain escape, host process termination (critical); subprocess execution capability (spawn/fork — high), prototype pollution, dynamic code, credential access, approval tampering, dynamic module loading (high); static module loading, process signals, filesystem, network, global settings, sandbox, tool intervention, session reads (medium); ordinary env reads, timers (low).
  • Credential-shaped env vars: process.env.X whose name matches *_KEY/_TOKEN/_SECRET/_PASSWORD/_CREDENTIAL/_AUTH/_COOKIE/_PRIVATE is scored high on its own.
  • Client rules: document.cookie, innerHTML XSS, browser eval, dynamic module loading (high); import()/require, storage, network, navigation, postMessage, Service Worker (medium); host.call, timers (low).
  • 15 service capability surfaces: precise detection of ctx.get('shell'/'subprocess'/'credentials'/'approval'/'dynamicCordisRunner'/...).
  • Composition rules (after reduction): credential-shaped data + network = critical; Cookie + network = critical; file reads + network = high; dynamic code + network = high; local storage + network = low (informational).
  • Obfuscation detection: high-entropy long strings, \x/\u escapes, atob.

Capability Declaration (optional)

Append to the end of purpose in cordis_define:

CAPABILITIES: spawn,network,env,fs

Keywords: spawn exec module network env fs credentials approval shell subprocess runner settings sandbox sessions llm process eval vm proto storage cookie dom redirect postmessage serviceworker rpc timer obfs. Rules covered by a declaration score at half weight (marked [declared] in the report).

Cross-Session Approval Persistence

  • After a human-approved ASK executes successfully, its code fingerprint (sha256 of host+client) is written to <DSH_HOME>/storages/plugin-security-gate/state.json.
  • Subsequent define/run calls matching the same fingerprint are auto-approved; any code change invalidates it. The history tail is persisted as well.

Locally Trusted Development: the trusted-local Allowlist (v1.6.0)

Background: the gate reviews every cordis_define/cordis_run, so legitimate local development plugins can also be stuck by BLOCK/ASK — e.g. a plugin that reads DEEPSEEK_API_KEY and calls the official API triggers the HOST_EXFIL_CRED critical composition, and BLOCK has no human-approval channel. We deliberately do not add "locally created ⇒ auto-allow": the DSH tool contract carries no provenance field, so "locally authored" and "prompt-injection forgery" are indistinguishable at the call surface, and auto-allow would re-introduce the exact exemption-forgery class fixed in v1.4.0. Allow-listing must be anchored to something other than agent self-attestation — the operator file.

Format (created by the operator with file permissions; absent/corrupt file = no allowlist, behavior unchanged):

{
  "prefixes": ["dev", "local"],
  "pluginIds": ["devtool-1", "legit-llm-client"],
  "fingerprints": ["<sha256 fingerprint of host+client source; use the sha field from plugin_security_review>"]
}

Match rules (any match allows directly and is recorded in the audit history as [trusted-local]):

EntryMatches
prefixesprefix of the define idPrefix (kind:new) or pluginId (kind:existing)
pluginIdsexact pluginId of define / run (does not depend on source retrievability)
fingerprintsexact sha256 of host+client (code changes invalidate it)

Boundary notes:

  • The allowlist also lets through BLOCK-level plugins — that is the intent of explicit operator trust.
  • Adding the gate's own id/prefix to the allowlist disables its self-protection (the operator could uninstall the gate with file access anyway; their risk).
  • cordis_stop/cordis_undefine self-protection is not affected by the allowlist.
  • In full-access deployments the agent can also write this file — consistent with the trust level the agent already has for all file access; the allowlist guards against high-risk installs the operator has not confirmed, not against the agent itself.

Approval Popup: Reject / Agree+Allowlist / Agree (v1.8.0, static bundle)

Why: when an ASK verdict routes through the official approval seam in an approval-disabled deployment (policy auto-rejects), it dies as the user rejected tool "cordis_define". Since v1.7.0 the static bundle ships its own popup, so ASK has a real human exit.

Flow:

1. cordis_define/cordis_run hits an ASK verdict → the gate parks an approval record (persisted in state.json, including the origin-session id agentId) and returns a deny whose reason says an approval dialog was raised. 2. The browser half (client/client.js, registered in the shell.overlay layer) polls GET /security-gate/approvals/pending and shows a card: plugin name, verdict/score, risk distribution, plus [Reject] [Agree+Allowlist] [Agree]. 3. Clicking AgreePOST /security-gate/approvals/decide {sha, outcome:'approve'} → the gate writes the code fingerprint into approved → it injects a retry message to the origin session via session.prompt → the agent auto-retries → the same fingerprint now passes. 4. Clicking Agree + AllowlistPOST .../decide {sha, outcome:'approveTrusted'} → as step 3, plus it writes the plugin family into trusted-local (prefixes/pluginIds), so later installs/dev iterations of the same family (even with changed code) pass without re-prompting. 5. Clicking Reject → records human rejected via popup history and clears the pending record; retrying the same code is still blocked.

Config askMode (<DSH_HOME>/storages/plugin-security-gate/config.json):

{ "askMode": "seam" }
  • popup (default; also the fallback when absent/corrupt): gate-owned dialog; bypasses the official approval UI.
  • seam: route through the official approval service (requires a deployment with approvals enabled, otherwise it degrades to rejection).

Boundaries:

  • The popup is a browser-GUI feature: only the web profile's static bundle form provides it; CLI/headless sessions and the dynamic fallback form keep the seam for ASK (the deny text still points to the trusted-local manual path).
  • Agree approves only that code fingerprint (invalidated on any code change) and does not add the plugin to trusted-local; only Agree + Allowlist writes trusted-local.
  • The retry is always routed to the origin session (entry.agentId, recorded by the gate from exec.agent at interception time), independent of which conversation is currently displayed — with several sessions open it no longer lands in the wrong one.
  • BLOCK remains a hard refusal and never opens the popup (security-first unchanged).

Profile Bundle Inventory Audit

plugin_security_audit includeBundles=true: scans dependencies under <DSH_HOME>/profiles/* that declare dsh (skipping official @deepseek-ai/* packages), reads their entry artifacts through the same static review, and outputs a per-package verdict. Informational audit: bundle installation (pnpm/npm) does not pass through cordis_define/cordis_run, so the gate cannot hard-block that path — it only provides pre/post-install visibility.

Tools

  • plugin_security_review: pre-install review of given host/client source; returns the verdict, score, per-item risks, and line numbers; purpose supports CAPABILITIES: declarations.
  • plugin_security_audit: with pluginId+packageId, emits a full report; with no arguments, emits a full overview + intercept/allow history + gate status; includeBundles=true appends the profile inventory audit.

Maintenance and Boundaries

  • Upgrade the gate (static): dsh plugin --profile web update dsh-plugin-security-review; after updating a local file: dependency, run dsh plugin add once more.
  • Self-protection boundary: the static bundle gate cannot be disabled via cordis_stop/cordis_undefine (it is not in the dynamic registry), but an operator with file permissions can uninstall it via dsh plugin remove — an inherent difference between the static and dynamic forms.
  • Known limitations: composition verdicts are static presence checks, not dataflow-confirmed, so false positives/negatives are possible; there are blind spots for deeply obfuscated code; this gate is an in-process interceptor (standard install path), not a security boundary against malicious actors who can disable it — real defense in depth still requires host-level isolation. The trusted-local allowlist and the approval popup are operator/user decision surfaces, not security boundaries against the agent itself; the popup is provided only by the static bundle form.