DeepSeek Harness plugin

dsh-ssh-bridge

Local SSH bridge for the DeepSeek Harness (DSH): user types the router password in a browser page, agent runs commands via a localhost API — the password never enters the chat.

Jump to install

Source facts

Repository
lance-kanglu/dsh-ssh-bridge
Latest update
Aug 16, 2026
Category
Workflow & Automation
GitHub stars
0
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/lance-kanglu/dsh-ssh-bridge
Plugin: dsh-ssh-bridge
Author: lance-kanglu

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

dsh-ssh-bridge

中文

A local SSH bridge service: enter the target host (user@host[:port]) and password in a browser page → local bridge process → encrypted SSH channel → target host, the password never enters the chat history. It is primarily used as a DSH (DeepSeek Harness) plugin: the agent runs remote commands from the local machine through tools like router_exec, against any SSH target with no host preset; the bridge core does not depend on DSH and can also run standalone.

> Current version: v0.4.2

Deployment

Dependencies

ProgramVersionDescription
---:-:---
DSH0.1.0-rc seriesHost (plugin mode)
pnpmDependency install (forwarded by dsh plugin)
Node.js≥18Runtime
ssh2^1.17SSH client library (installed automatically by npm install)
@deepseek-ai/schemastery^3.18Plugin config schema (installed automatically by npm install)
@deepseek-ai/cordis^4.0DSH host runtime (peer dependency, provided by the host, optional)

Steps

# 1. Install the plugin (from the npm registry)
dsh plugin --profile web add dsh-ssh-bridge

# 2. Restart DSH
dsh web

> Note: pnpm 11 blocks the native build scripts of ssh2/cpu-features (node-gyp) by default, so dsh plugin add exits non-zero and the plugin is not mounted even though the dependency is installed. Allow them in the profile's pnpm-workspace.yaml first, then re-run the install: > > ``yaml > allowBuilds: > cpu-features: true > ssh2: true > ``

Other install sources (GitHub / local tgz):

# GitHub (tag matches the npm version)
dsh plugin --profile web add github:lance-kanglu/dsh-ssh-bridge#v0.4.2

# Local tgz (downloadable from GitHub Releases)
dsh plugin --profile web add /path/to/dsh-ssh-bridge-0.4.2.tgz

dsh plugin add appends the package to the profile's dsh.profile.bundles list; the plugin mounts automatically after a DSH restart, with no config file edits. Tools are registered in the global layer, visible to every session's agent.

Verify the deployment: the composed tree of dsh --profile web --dump-config should contain:

- id: ssh-bridge
  name: dsh-ssh-bridge/plugin
  config:
    basePort: 23991

Manual approach (local development, bypassing the bundle): place the package under the deployment's profiles/node_modules (a junction pointing to this project works), then add one row to a cordis patch / preset under $DSH_HOME:

- id: ssh-bridge
  name: dsh-ssh-bridge/plugin
  config:
    basePort: 23991

> Tip: for presets, agentPresets.standingKeyFor('<preset-id>') mount-validates (it really mounts the whole plugin tree).

Standalone run (optional)

The bridge core does not depend on DSH and can run standalone:

# 1. Install dependencies
npm install

# 2. Start the service
npm start
# equivalent to: node bridge.cjs

# 3. Open the page
# visit http://127.0.0.1:23991 in a browser

Verify the deployment: the startup log prints the line below and the page opens in a browser:

[ssh-bridge] 已启动: http://127.0.0.1:23991

Usage

Browser page

1. Open http://127.0.0.1:23991, enter the target host (user@host[:port]) and the password, then click "Connect" or press Enter; 2. Once the status turns to "Connected", the input box below becomes the target's interactive terminal (with ANSI color rendering), and a "Disconnect" button appears in the header; 3. Typing exit in the terminal ends the whole SSH session (same as a regular SSH client); the "Disconnect" button works as well; 4. After disconnecting, the target host is kept and the password is cleared, so you can reconnect with the password; 5. The log panel at the bottom shows connection and request status in real time, with details when something fails.

Command API (for agents / scripts)

A random token is generated on every start and printed in the startup log ([ssh-bridge] token=xxxx); every call below must carry it:

EndpointMethodParamsDescription
---:-:------
/api/authPOST{password, target?}Submit the target and password to establish an SSH connection (password is never logged or persisted; target format user@host[:port])
/api/execPOST{cmd, token}Run one command on the target host, returns {code, stdout, stderr}
/api/inputPOST{line, token}Write one line to the interactive terminal (writing exit ends the whole session)
/api/pingPOST{token}Query connection status {ok, connected}
/api/disconnectPOST{token}Disconnect the SSH connection (same as the "Disconnect" button)
/api/reconnectPOST{token}Reconnect using the credentials saved during page authentication; returns 409 if never authenticated

Example (PowerShell):

$body = @{ token = '<token from the startup log>'; cmd = 'uname -a' } | ConvertTo-Json
Invoke-RestMethod -Uri http://127.0.0.1:23991/api/exec -Method Post -ContentType 'application/json' -Body $body

> Security: the service listens on 127.0.0.1 only, unreachable from the outside network; the password flows only between the browser → local process → encrypted SSH channel. > Note: if port 23991 is taken, the service rolls forward automatically (23992, 23993...), as printed in the startup log. > Tip: after updating the page code, hard-refresh (Ctrl+F5) to avoid stale cache; the web terminal echoes Enter only.

DSH plugin integration

Mechanism

A DSH plugin is a cordis plugin (npm package): the loader (@deepseek-ai/cordis-plugin-loader) imports the plugin by package name and applies its config, with rows declared in cordis.yml or patch files; model tools are surfaced through the tools registry, with schemas auto-injected into the prompt (tool-pwsh, tool-bash use the same mechanism).

Plugin adapter

plugin/index.cjs is a cordis plugin object (calibrated against the actual DSH 0.1.0-rc runtime source):

  • Config (schemastery) declares config with default-value validation — cordis only reads the Config field, schema is ignored;
  • inject: ['tools'] declares the hard dependency, otherwise property access like ctx.tools throws cannot get property ... without inject;
  • Registers executors via tools.register() only (the definition must include output: { schema, render }, otherwise register throws a TypeError). Tool schemas are injected into the prompt automatically by DSH's ToolRuntime.wireSchemas from the registry — do not also call systemPrompt.tools(), or the model request fails with Tool names must be unique (INVALID_REQUEST 400);
  • Cleanup is registered through ctx.effect() — cordis invokes a function-declaration apply with new, so the apply return value is discarded as a class instance and cannot be used for disposal.

Three tools are registered:

ToolPurpose
router_execRun one command on the target host; the description carries the bridge page URL and explicitly tells the agent not to solicit or repeat login info from the chat
router_disconnectDisconnect the SSH connection (same as the page's "Disconnect")
router_reconnectReconnect using the credentials saved during page authentication (password stays in process memory only)

Three contract/e2e test suites are included: node test-plugin.cjs (cordis inject/Config/register contract, including the "no duplicate schema registration" regression assertion), node test-mount.cjs (real SystemPrompt/ToolRuntime + scoped ctx end-to-end, asserting the three tools enter the scoped layer, appear exactly once in the prompt assembly, and tool names are globally unique), and node test-e2e.cjs (a real local ssh2 server: auth → exec → disconnect → reconnect → exit ends the session).

#### Config options

OptionDefaultDescription
---:-:---
bindHost127.0.0.1Bind address; keep it loopback
basePort23991Starting port; rolls forward when taken
execTimeoutMs60000Per-command timeout

> Note: the target host is not preset; enter user@host[:port] during page authentication. In CLI mode, environment variables can provide a default target that only pre-fills the page.

Note: the plugin ABI is currently in the 0.1.0-rc series with no public third-party docs; this adapter is calibrated against the contract above and tested.

Code structure

FilePurpose
server.cjsBridge core createBridge(), shared by the CLI and the plugin
bridge.cjsCLI entry, supports environment-variable config (SSH_BRIDGE_PORT, SSH_BRIDGE_ROUTER, etc.)
page.jsBrowser page script (ANSI rendering)
plugin/index.cjsDSH plugin adapter
test-parser.cjsANSI renderer test (npm test)
test-plugin.cjsPlugin cordis contract test (node test-plugin.cjs)
test-mount.cjsPlugin scoped-mount e2e test (node test-mount.cjs)
test-e2e.cjsBridge-core e2e test (local ssh2 server, node test-e2e.cjs)

FAQ

  • dsh plugin add fails with ERR_PNPM_IGNORED_BUILDS: pnpm 11 blocks the native build scripts of ssh2/cpu-features by default, so add exits non-zero and the plugin is not mounted. Configure allowBuilds: { cpu-features: true, ssh2: true } in the profile's pnpm-workspace.yaml and retry.
  • The page does not respond / clicking "Connect" does nothing: hard-refresh (Ctrl+F5); make sure the service is running.
  • Raw escape codes like [1;34m appear in the terminal output: stale cache — hard-refresh (an ANSI renderer is built in).
  • /api/exec returns not connected: authenticate in the page first, or ask the agent to run router_reconnect.
  • The page still shows connected after typing exit: old-version behavior; since v0.4 exit ends the whole session — hard-refresh (Ctrl+F5) if the page cached the old script.
  • /api/auth returns missing target: the request body must carry target in user@host[:port] format; in plugin mode with no default target configured, it is required.
  • Port taken: the service rolls forward automatically, per the startup log.
  • Can the password leak?: No. The password lives in process memory only — never printed, never persisted; the token is local-only and prevents cross-site requests.

Changelog

v0.4.2

Docs:

  • README restructured to be plugin-first: the deployment section is now the dsh plugin add install, and the standalone run moved to an optional subsection;
  • Documents that pnpm 11 requires allowing the ssh2/cpu-features native builds in pnpm-workspace.yaml.

v0.4.1

Release:

  • Declares a dsh.bundle.patch manifest, so dsh plugin add dsh-ssh-bridge installs it in one step;
  • README documents the npm / GitHub / local tgz install methods.

v0.4

Connection management:

  • The page gained a "Disconnect" button; typing exit in the terminal now ends the whole SSH session (like a regular SSH client), no longer stuck at "connected without a shell";
  • Added /api/disconnect and /api/reconnect, plus the agent-side router_disconnect and router_reconnect tools, so the agent can disconnect/reconnect on request (reconnect reuses the credentials saved during page authentication; the password stays in process memory only);
  • Tool descriptions carry the bridge page URL and explicitly require the agent not to solicit or repeat login info from the chat, and not to assume a specific host; the page placeholder is now the neutral user@192.168.x.x;
  • Fixed a connection-lifecycle race: after disconnect/reconnect, stale close/shell-close events from the old connection no longer corrupt or kill the new connection;
  • Added test-e2e.cjs (real local ssh2 server end-to-end).

v0.3

Generalization:

  • The target host is no longer preset; it is entered as user@host[:port] in the browser page, so any SSH target works;
  • /api/auth gained a target parameter; the plugin config dropped routerHost/routerUser/routerPort;
  • The plugin adapter was calibrated to the DSH 0.1.0-rc contract: Config/inject/output {schema, render}/ctx.effect();
  • Fixed duplicate tool registration: the plugin registers via tools.register() only, no longer calling systemPrompt.tools() — registry tools are injected into the prompt by ToolRuntime.wireSchemas, and duplication made the model request fail with Tool names must be unique;
  • Added the two plugin contract tests test-plugin.cjs and test-mount.cjs.

v0.2

Added:

  • The DSH plugin adapter plugin/index.cjs, registering the router_exec tool (experimental);
  • Core refactored into server.cjs (createBridge()), shared by the CLI and the plugin;
  • CLI environment-variable config; added LICENSE (MIT).

v0.1

First release. Core capabilities: browser password authentication, interactive terminal (ANSI color rendering), local exec API, standalone tests (npm test).