What it does
DSH Pentest installs a penetration-testing agent preset — 渗透模式, “penetration mode” — for recording, within an authorized scope, targets, exploration leads, verified results, assets and vulnerabilities, and showing them in the Web UI. The repository is owned by the GitHub user howmp and its description ends with @CloverSecLabs; no LICENSE, NOTICE or README statement describes that relationship further.
Where the capability lives matters most. The package registers bookkeeping tools, one prompt section and one session projection. It has no HTTP client, no socket and no process spawner: grepping the shipped lib/pentest.js for fetch(, http.request, net.connect, child_process, execSync and spawn( returns zero hits for each. The requests that touch a target come from the shell, which reaches the preset because the preset grants it — not because this plugin provides it.
It is not a scanner you point at a host. It is a record-keeping and visualisation layer for a pentest an agent carries out with tools it already had.
The exploration chain
Per src/dsh-pentest/src/spec.ts, a session holds one goal (target, objective, optional authorization). An intent anchors to the goal (spawns edge) or a fact (derived_from); an intent yields facts; a fact derives a further intent; an intent proves a finding. Findings require reproducibleSteps with at least one entry, and the store enforces it. Assets (root-domain, subdomain, ip, service, app, endpoint) link by parent edges. Edge kinds are a closed set of five.
Node and edge ids are deterministic (<kind>-<n>, counted per session), letting the Web view rebuild the graph by replaying logged tool calls rather than reading the database.
The nine tools, and who may call them
The package README’s table lists eight: pentest_add_goal, pentest_add_intent, pentest_add_fact, pentest_add_finding, pentest_add_asset, pentest_state, pentest_graph, pentest_report. Its prose says “nine model-facing” tools; the ninth is pentest_submit, available to subagents only.
Calling rights are split. The decision agent (root) owns the eight record and read tools. The preset’s delegation rows carry an explicit toolFilter denying all eight of those plus subagent and subagent_fork to execution children, leaving them pentest_submit, which resolves the parent session from the delegation relationship.
Two consequences. pentest_add_goal is destructive — it wipes that session’s intents, facts, findings, assets and edges and restarts id counters from <kind>-1; docs/goal-reset.md calls it “a privileged destructive operation”. And pentest_submit refuses placeholder ids, so a delegation must carry the real id pentest_add_intent returned.
What the preset grants
preset/pentest/agent.cordis.yml is, by its own header comment, “a copy of the shipped standard preset with one addition — the @deepseek-ai/dsh-pentest row — and a commander persona”. Its row list is that, confirmed: shell (tool-bash, tool-pwsh), filesystem and search, jobs, skills, goals, planning, compaction, four subagent rows, workflow, ralph, ask_user_question, todo and web — all inherited unchanged.
That inventory answers “what will this agent do”. Because the shell rows are present, the agent — and its execution children, which inherit the parent composition — can run commands, and those commands reach a target. The plugin contributes the discipline and the ledger; the shell contributes the reach.
The commander persona is terse: it decides and creates intents while proposal generation and execution are delegated. The substantive protocol arrives as the pentest:protocol system-prompt section (order 50), written in Chinese, as is the instruction that all user interaction use Chinese. One line states the scope rule: any intent deviating from the authorized range recorded in pentest_state should be refused.
Authorization is recorded, not enforced
The README says this plainly and the source agrees: the goal’s authorization field is a declarative note (permission holder or written-permission reference) “recorded as an auditable fact, not a gate: enforcement stays at the deployment’s sandbox/approval layer.”
There is no allowlist, no domain matcher and no scope check in the code, and the package README’s limits list says the same — “the package does not itself block tools; sandbox/approval configuration remains the deployment’s responsibility.” The protocol prompt asks the decision agent to refuse out-of-scope intents, but a prompt instruction is not a mechanism, and nothing verifies that a recorded authorization note is real.
The authorization field leaves an audit trail; it does not stop anything. Containment is whatever your deployment’s sandbox and approval settings already provide for shell execution.
What is stored, and where
The package declares one storage domain, pentest (version 2), with six tables: goals, intents, facts, findings, assets, edges. The bundle’s cordis.patch.yml installs a sqlite backend at dshHomePath('storages', 'pentest-sessions.db') — in practice $DSH_HOME/storages/pentest-sessions.db — routing only the pentest domain to it; other domains keep the host’s json backend. Records are keyed sessionId:id, so an engagement is confined to one session.
On credentials: no table and no schema field holds one. Facts carry kind, target, detail, confidence; assets carry type, value and a free-form meta. The caveat is detail and meta — both agent-written free text, so a password or token could end up inside them. The schema neither provides a field for secrets nor prevents that.
Installing a build artifact
The upstream command installs the release tarball, not a source checkout and not an npm package. @howmp/dsh-pentest is not on npm — the registry returns 404 for both the scoped name and dsh-pentest — which is why this listing carries install:.
The latest release is v0.1.0-rc.30 (2026-09-11); its asset dsh-pentest.tar.gz is 216,652 bytes. The releases/latest/download/… URL resolves — two redirects, HTTP 200, and a byte-identical download whose SHA-256 (6ad06af1…4763) matches GitHub’s reported digest.
The tarball is 14 files and contains no source. It ships package.json, cordis.patch.yml, README.md, eight compiled JavaScript files under lib/ (including a 608 KB lib/pentest.js and a 486 KB lib/ui-pentest.client.js) and two preset/pentest/ YAML files — no src/, no tests, no .ts, no source maps. The repo does carry a readable TypeScript snapshot under src/dsh-pentest/src/ and src/dsh-client-ui-pentest/src/, but nothing ties the loaded bytes to it; comparing them means a manual diff or a local rebuild.
No install-time code runs. The shipped scripts block holds exactly one entry, "test": "vitest run" — no preinstall, install, postinstall, prepare, prepublish or prepack. The release workflow runs only on version tags: npm ci --legacy-peer-deps && npm test, then npm pack, renamed to dsh-pentest.tar.gz.
Requirements
- Node.js >= 22.5 for the sqlite backend’s built-in
node:sqlite(docs/storage.md). - Harness
0.1.0-rc.6as the declared target.AGENTS.mdstates “The bundle targets DSH0.1.0-rc.6; align peer dependencies and rebuild from the matching upstream version when DSH APIs change”, and the manifest pinsdsh-invariants,dsh-storage,dsh-storage-domainanddsh-toolsto exactly0.1.0-rc.6(@deepseek-ai/cordisat^4.0.1). The root README names the same release in shorthand (“compatible with DSH rc.6”). The manifest has nodsh.compatibilityordsh.targetblock, so those pins are an installation requirement, not a compatibility promise. The persona row carries bothprefix(required from0.1.3) and the legacytextalias (up to0.1.2) so one preset mounts on either — a compat tactic, not a tested-version claim. - Dependencies: runtime
zodand@deepseek-ai/schemastery. The client half injectsdsh-client-runtime,dsh-client-localeanddsh-client-ui-conversation, and declaresplatform: web— a headless or CLI-only profile is not what this is built for. - Telemetry: none found — grepping every tarball file for
telemetry,analytics,sentry,posthog,mixpanelandumamireturns nothing, and the plugin makes no outbound calls. The README says nothing either way, and documents no permission model for the shell the preset grants.
Known limits
- No scope enforcement of any kind.
- No license file in the repository. The shipped
package.jsondeclares"license": "MIT"and the nested host package agrees, but the repo has noLICENSE,LICENSE.md,COPYINGorNOTICEfile — GitHub’s license API returns 404 and the community profile reports"license": null. Treat it as an unaccompanied claim: absent a license text, no license is formally declared and all rights are reserved by default. The listing records the manifest’s value because that is what the repository says about itself, not independent evidence of a grant. - The Web tab hides itself when it cannot prove the plugin is mounted. The
pentestMountedunit turns true only once the session’s own log shows apentest_*tool in the assembled schemas, a logged call, or a folded delegated submission — presets cannot be read from the browser, so a copied preset is recognised by log evidence, not name. - The graph is a windowed view — the latest 200 nodes, assets and edges each, oldest evicted; the full record lives behind
pentest_stateandpentest_report. Layout is static and layered: pan and zoom, but nodes cannot be dragged. - One session, one goal. No cross-session resume; a new engagement means a new
pentest_add_goal, discarding the previous chain. - Transcription is on the agent. The README notes “nothing validates that transcription is complete or faithful” for delegated results, and concurrent multi-agent writes to one session are not additionally serialized.
When to use it — and when not to
Use it when you are already running an authorized engagement and want the agent’s findings to land as a queryable graph — assets with parent links, findings each carrying a reproducible step, a regenerable report — rather than prose in a long transcript. The reproducibleSteps rule is the most useful thing the store enforces: “I think this is vulnerable” fails to record; “here is how to reproduce it” lands.
Do not install it expecting a scanner, and do not treat the authorization field as a safeguard. It scans nothing itself and will not stop an agent from running a command against a host that was never in scope — that boundary belongs to your sandbox configuration, and to you. Note the missing license file if you intend to redistribute or build on it.
FAQ
Does this plugin attack targets by itself?
No. It registers nine pentest_* bookkeeping tools, a system-prompt section and a session projection — and no HTTP client, socket or process spawner; those greps return zero hits in the shipped lib/pentest.js. What reaches a target is the shell the preset grants, which subagents inherit. The plugin records; the shell acts.
Does it enforce the authorized scope?
No. The goal carries an optional authorization string the source describes as “an auditable fact, not a gate”, and the README confirms “the package does not itself block tools”. No allowlist or range check exists in the code. The protocol prompt tells the decision agent to refuse out-of-range intents, but that is an instruction to a model, not a control.
Does the database store credentials?
Not in a dedicated field. The pentest domain’s six tables have no credential, password or token field. But facts.detail and assets.meta are agent-written free text, so a secret could be written into them. The file is $DSH_HOME/storages/pentest-sessions.db; other domains keep the host’s json backend.
Why does the install command point at a release tarball instead of npm?
Because the package is not on npm — the registry returns 404 for both @howmp/dsh-pentest and dsh-pentest, so there is nothing for package: to resolve to. The tarball holds 14 compiled files and no source, so you are installing a build artifact; the repo has a TypeScript snapshot to compare against, but nothing links the two automatically. Installing runs no code.
Which harness version does it target?
AGENTS.md declares DSH 0.1.0-rc.6, and the manifest pins dsh-invariants, dsh-storage, dsh-storage-domain and dsh-tools to exactly that version. The root README says the same in shorthand (“DSH rc.6”). The manifest has no dsh.compatibility block, so those peer pins are an installation requirement, not a compatibility promise, and no source states which releases were tested.