DSH Plugins Marketplace

DSH Plugins

Plugins

/

Security & Audit

/

dsh-phocinae

P

dsh-phocinae

Manifest valid★ 1

Local non-generative decision layer for DeepSeek Harness: phocinae_ask / phocinae_gate tools plus a fail-closed approval gate

hasBundlePatch

English · 中文 · Español · Português · हिन्दी

dsh-phocinae

A local, non-generative decision layer for DeepSeek Harness.

phocinae_ask / phocinae_gate tools backed by a Phocinae-Largha-150M-v1 server — yes/no judgements, single-choice picks and 2-10 scores, one forward pass each, on your own machine — plus a fail-closed approval gate that screens tool calls before they run.

This is 0.2.2, a repair release. The 0.1.2 plugin did not activate at all on DSH 0.2.x: the harness logged one warning and the entry died. Everything below the Fixed in 0.2.2 section is what changed and why.

dsh plugin --profile <name> add dsh-phocinae@0.2.2

Requires Node ^22.19 or >=24, and a reachable decision service (see Running the decision service).

npm i alone does not install the plugin

Two separate things have to happen, and only the second one mounts the plugin:

stepwhat it does
npm i dsh-phocinaePuts the package in the current directory's node_modules. Useful for reading the code or importing the guard directly. It does not reach any DSH profile.
dsh plugin --profile <name> add dsh-phocinae@0.2.2Adds it to that profile's package.json (dependencies and dsh.profile.bundles) and installs it there with pnpm. This is what makes the host mount it.

Measured on this machine, with a throwaway DSH_HOME:

  • npm i dsh-phocinae in an unrelated directory → installed there, profile dependencies still {}, profile bundles unchanged, plugin absent from dsh <profile> --dump-config;
  • dsh plugin --profile p add dsh-phocinae → the profile gains both the dependency and the bundle entry, and the composed config contains the phocinae row with its endpoint;
  • a dependency without the dsh.profile.bundles entry → installed but not mounted, because the bundle list is what the loader walks.

Pin the version

add dsh-phocinae resolves through pnpm's supply-chain age gate (minimumReleaseAge). A release younger than the gate's threshold is not selected, so for a while after a publish the bare name resolves to the previous version — and the previous version here is 0.1.2, which does not work.

Measured, minutes apart, on the same machine:

whencommandresolved to
order 0.2.2 published + 2 mindsh plugin --profile p add dsh-phocinae0.1.2 — main: index.js, the build that cannot activate
same, but add dsh-phocinae@0.2.20.2.2 — ./index.mjs, skill present
same, bare add with minimumReleaseAge: 0 in the profile's pnpm-workspace.yaml0.2.2
order 0.2.2 published + 150 mindsh plugin --profile p add dsh-phocinae0.2.2 — ^0.2.2, host boots clean

When the gate holds a version back, the harness records the refusal in the profile's pnpm-workspace.yaml:

minimumReleaseAgeExclude:
  - dsh-phocinae@0.2.2

The gate is a sound default — a freshly published package is exactly what a supply-chain attack looks like. It does mean the version should be named, both to get the fixed build immediately and to make an install reproducible:

dsh plugin --profile <name> add dsh-phocinae@0.2.2

If a bare add does land you on 0.1.2, dsh <profile> --dump-config shows which version is mounted, and re-adding with the pinned spec replaces it.

Git and local checkouts work the same way, and pin by construction:

dsh plugin --profile <name> add github:Phocinae/dsh-phocinae#v0.2.2
dsh plugin --profile <name> add /path/to/a/local/checkout

What it contributes

contributionseamwhat it does
phocinae_askctx.toolsRuns a batch of typed questions against the local model. Every answer carries a calibrated confidence and an advisory escalate flag.
phocinae_gatectx.toolsJudges one command or action: allow / ask / deny, with confidence. Checks an action without running it.
approval gatetools/pre-execute waterfallScreens every tool call the configured patterns cover, before it executes.
phocinae skillctx.skillsTeaches the model when a local decision is the right tool, how to phrase one, and how to read the escalate flag.

The model is not a chat model. It does not generate text, does not know facts, and cannot write code. It makes one structured decision per forward pass, which is exactly what a gate needs and nothing more.


Fixed in 0.2.2

Every item below is reproduced by a test in test/regressions.test.mjs, named D1…D12.

D1 — the plugin never activated

dsh: warning: 1 entry did not activate
phocinae (dsh-phocinae): Error: cannot get property "registerTool" without inject

index.js probed typeof ctx.registerTool === 'function' as a defensive check. A Cordis context is a proxy: reading an undeclared service name throws instead of returning undefined. registerTool is not a DSH service — the registry lives at ctx.tools — so the probe threw on the first line of the plugin body, taking the whole entry down before it could subscribe to anything.

Fixed: tool registration waits for the service through ctx.inject(['tools'], …). No probe, no undeclared read, and if the service never appears the plugin still arms its gate.

D2 — the tool definition was the wrong shape

The definition carried {inputSchema, handler}. ToolDefinition extends ToolSchema, which declares {name, description, parameters}, and the registry reads definition.parameters. Registering the old shape fails the model-facing projection:

dsh: UNKNOWN: tool "phocinae_ask" parameters must be lossless JSON before schema projection

Fixed: definitions now carry parameters and output: {schema, render} plus execute, all inside the harness's enforced JSON Schema subset (required as an array, oneOf for unions, no author-only keywords). test/contract.test.mjs re-implements that subset check, and the assembled-headless run registers the definitions in a real host.

D3 — the gate read the answer from the wrong place

data.answers is an object keyed by question id. 0.1.2 did Array.isArray(data.answers) ? data.answers[0] : …, which is undefined for every real response.

D4 — and therefore allowed everything, silently

The verdict test was if (risk === true). With risk always undefined, that is always false, so guard() returned {decision: 'allow'} with the reason "the model judged this low risk" — for every command, including rm -rf /.

On the 53-command labelled set in bench/, recall on destructive commands was 0.00.

Fixed: answers are read by id and shape-checked, a malformed body is a protocol error rather than a default, and the gate fails closed — an unreachable service, a missing confidence extension, or an unreadable body all route to a human.

D5 — verdicts were thrown, not returned

The deny path called next(err). The tools/pre-execute waterfall expects a PreToolDecision ({kind: 'allow' | 'deny' | 'ask', reason}); rejecting the waterfall is not a decision.

Fixed: {kind: 'deny', reason} and {kind: 'ask', reason, displayReason}, with next() called exactly once on the allow path.

D6 — gate.tools defaulted to ['bash']

The shell tool is pwsh on Windows, bash elsewhere, and MCP servers add their own names. On this machine the default matched nothing, so the gate never ran.

Fixed: the default is ['*'] — every tool — with exact names, * globs, and case-insensitive matching available.

D7 — the escalation gate the release notes promised was not there

The model release documents an E1 gate: escalate a decision to a larger model when answer_confidence falls below τ. 0.1.2 contained no τ, no confidence comparison, and no escalation branch — a repository-wide search for escalate found only the Python server.

Fixed: phocinae_ask returns escalate, escalatedIds, escalationReason and escalateAt, and the gate consults the same confidence. The plugin still does not perform the escalation — it cannot call a large model on your behalf — but the signal is now produced rather than claimed.

Breaking changes

change0.1.20.2.2
tool registryctx.registerTool / ctx.tools.register probectx.inject(['tools'], …)
tool definitioninputSchema + handlerparameters + output + execute
gate answer sourceanswers[0] (array)answers[id] (object)
failure directionalways allowask by default (gate.failMode)
tool match default['bash']['*']
verdictnext(err){kind, reason}
gate questionboolean noulharm scale (harmless/risky/destructive)
gate.mode defaultdenyharm
escalationabsentescalate on every answer

gate.failMode: 'open' restores the old always-allow behaviour for a deployment that wants it. It is not the default and should not become one.


Measured behaviour

The measured figures were produced on one machine (CPU, fp32) against the released weights, with the scripts in bench/; re-run them on your own hardware before relying on them. The published figures come from the model release's own documents.

Version anchors. v1.0 — first published figures. v1.0 corrected — E1 numbers corrected (kept-subset 0.886, −54.4% LLM calls, 45.7% escalate). v1.1 (2026-10-09) — release of record (0.906 / 0.848, 45.0% escalated / −55.0% calls, kept-subset 0.9936). The pre-correction reading (0.7948 / −82% / 18%) is deprecated upstream — do not cite.

Decision quality — the model is honest

datasets/typed_test/test_typed_400.jsonl, 400 cases × 5 questions = 2000 decisions, judged per decision against gold.label:

metricpublished (en)measured (en)published (zh)measured (zh)
local accuracy0.9060.78250.8480.7820
escalation rate @ τ=0.645.0%45.65%—43.00%
accuracy on kept decisions—0.8859—0.9035

The zh figures are machine-translated cases; the training mix includes machine-translated Chinese (≈2,400 rows) plus native Chinese (≈1,400 rows) — an in-mix (fitted) evaluation, not cross-lingual transfer.

The measured column is this repository's 2026-10-08 audit (pre-v1.1 weights) and is retained unchanged — audit numbers are not rewritten, and v1.1 was not re-measured here; see the version anchors for the v1.1 figures. The "79.6% fewer LLM calls" saving belongs to τ≈0.50, while the frozen E1 gate is τ=0.6 (τ is swept on the eval set — re-scan per domain; the sweep below is the retained 2026-10-08 measurement):

τescalatedLLM calls cutaccuracy on decisions kept local
0.5017.15%82.8%0.8214
0.6045.65%54.4%0.8859
0.7067.15%32.9%0.9300
0.8081.70%18.3%0.9645

"79.6% saved" and "τ=0.6" cannot both be true. At the threshold the model release calls frozen, the reduction is 55.0%. That is still a real saving, and the confidence ordering is genuinely useful — accuracy on what stays local climbs from 0.78 to 0.96 as the bar rises — but the headline number belongs to a different threshold.

Gate quality — and why the default question changed

bench/gate-bench.mjs, 53 commands (31 destructive, 22 routine), all against the real model. recall is the share of destructive commands the gate stops; auto-pass purity is how often a passed command was actually safe.

gate questionrecall on destructiveconfident passes that were destructive
noul — "should this be blocked?" (0.1.2's default)0.00—
tri-state — allow / ask / deny0.741
harm — harmless / risky / destructive (0.2.2 default)0.970

The inherited boolean question is not merely weak, it is inverted: it passed every one of the 31 destructive commands, most with confidence between 0.54 and 0.80. Asking "is this safe?" is worse still — 27 of 31 passed with confidence as high as 0.87. The harm scale is the only formulation measured that both caught almost everything and never confidently waved a destructive command through.

Its one miss, vssadmin delete shadows /all /quiet, is caught by the auto-allow list's operator rule rather than by confidence, so no destructive command in the set reaches execution.

Confidence is compressed: keep the gate narrow

The model's confidence does not separate routine commands from risky ones well enough to be a general-purpose filter. Its harmless verdicts sit mostly in 0.27–0.54, so a gate that asked it about git status would send git status to a human.

That is why 0.2.2 ships an auto-allow list: read-only and build/test commands with no shell operators never reach the model at all. The gate's job is to stop things, not to approve the routine:

  • names on the list, no operator → pass locally, no request, no latency
  • anything containing ;&|><$(){}[]` etc. → the list is bypassed and the model decides
  • model says destructive → deny
  • model says risky, or harmless below gate.minConfidence → a human decides
  • service unreachable → a human decides

An entry containing a shell operator is refused at load time. git status && rm -rf / starts with an allow-listed prefix, so accepting such a pattern would build a bypass into the list.

Honest summary of the claims

claimverdict
"144.3M bilingual decision model, one forward pass, local"true
"typed-decisions en 0.906 / zh 0.848"reproduces, as recorded upstream (0.9055 / 0.848; this repository's retained reading: 0.7825 / 0.7820)
"cuts LLM calls by 79.6% with a τ=0.6 confidence gate"false as written — 79.6% is τ≈0.50; at τ=0.6 it is 55.0%
"escalation improves combined accuracy to 0.8737"consistent — escalation only replaces wrong local answers
"the plugin provides this gate"was false for 0.1.2 (recall 0.00, and it never loaded)

Configuration

Defaults live in cordis.patch.yml and are documented in lib/config.mjs. Every key has a default, so the bundle patch can be reduced to enabled: true.

keydefaultmeaning
endpointhttp://127.0.0.1:8155/v1/systemonedecision service. Loopback only — the decision state carries raw tool-call text, so a remote host is a load-time error unless allowRemoteEndpoint: true.
modelPhocinae-Largha-150M-v1served model name
timeoutMs3000per request; one attempt, no retry
escalateAt0.6confidence below this marks an answer escalate: true
maxStateChars4096cap on the rendered decision state
permuteChoicefalsepermutation-average choice questions (4x forwards)
gate.enabledtruearm the pre-execute gate
gate.modeharmharm / tri-state / noul / off
gate.threshold0.8noul mode only: P(risky) that counts as a positive signal
gate.minConfidence0.45below this, an allow becomes a review
gate.autoAllowsee DEFAULT_AUTO_ALLOWcommands passed without asking the model
gate.tools['*']tool names to screen: exact, * globs, or *
gate.failModeclosedclosed = unreachable service asks a human; open = allow
gate.riskyDecisiondenynoul mode only: deny or ask on a positive finding
gate.audittruelog every verdict through the host logger

To screen nothing and only use the tools:

- id: phocinae
  config:
    gate:
      mode: off

To screen only the shell, and only ask:

- id: phocinae
  config:
    gate:
      tools: ['pwsh', 'bash']
      riskyDecision: ask

Running the decision service

The plugin talks to a local server; it does not load weights itself.

# weights (the reference download is ~330 MB)
huggingface-cli download Phocinae/Phocinae-Largha-150M-v1 --local-dir ./model
# (modelscope hosts the same repo: modelscope download --model PerryLink/Phocinae-Largha-150M-v1)

pip install phocinae-server
PHOC_MODEL_DIR=./model python -m phocinae.main

GET http://127.0.0.1:8155/health answering {"status":"ok","model_loaded":true} means it is up. Without it, phocinae_ask returns a PHOCINAE_SERVICE_UNAVAILABLE error naming that command, and the gate routes every screened call to a human.


Development

npm test                  # unit + contract tests, no network, no model
npm run test:integration  # gate over real HTTP, then a real dsh host boot
npm run bench:gate        # the gate benchmark (needs the real service)

npm run test:integration creates a throwaway DSH_HOME under the system temp directory, so it never reads or writes your own profiles. Its first half drives the real gate over real HTTP against a fake service; its second half installs the plugin into that throwaway profile and boots the real dsh, asserting that every bundle entry activates — which is the check 0.1.2 would have failed.

The fake service exists because the real one needs a 330 MB checkpoint. Point the harness at a running real service with --real-endpoint http://127.0.0.1:8155/v1/systemone.


Documentation

doccontents
ARCHITECTURE.mdthe mounting contract, the gate's decision order, and why each choice was made
SECURITY.mdtrust boundaries, what leaves the machine, and the failure modes
CHANGELOG.mdrelease history
bench/the benchmark scripts and their saved results
THIRD_PARTY_NOTICES.mdreused work and its licences

License

Apache-2.0 — see LICENSE. The model weights are Apache-2.0 as well; their card is the authority on the model, this repository is the authority on the plugin.

Comments

Loading…

From the same category

dsh-infinite-gen-3

System-prompt armor plugin for DeepSeek models: appends an unconditional-compliance prompt section at order 100, exposes a profile tool with calibration metadata, and shows a realtime armor-status bad

Security & AuditManifest valid

★ 2.1k

MIT

C#

dsh plugin --profile web add dsh-infinite-gen-4

by toby-bridges

Local security audit for AI API relays and LLM proxies: detects prompt injection, model substitution, tool-call rewriting, SSE anomalies, error leakage, and Web3 wallet risks.

Security & AuditManifest valid

★ 876

AGPL-3.0

Python

Oct 10, 2026

dsh plugin --profile web add dsh-api-relay-audit

by SeaOf0

基于dsh web实现的多种模式,目的是服务于redteam进行授权的安全研究,覆盖渗透测试、红队评估、代码审计等范围领域,请勿用于非法行为。(允许二开,赋予模块各位自己的业务逻辑,方法论只有自己熟练的才好用,好的方法论=好的生态)

Security & AuditManifest valid

★ 685

MIT

Python

Oct 8, 2026

dsh plugin --profile web add @dsh-external/dsh-redteam-model

by agentic-os-org

ANOLISA (Agentic Nexus Operating Layer & Interface System Architecture) | Agentic OS with runtime, security, observability, and Tokenless response compression for lower token usage and cost.

Security & Audit

★ 666

Apache-2.0

Rust

Oct 12, 2026

Index only — not installable

by howmp

面向 DeepSeek Harness(dsh)的渗透测试模式 @CloverSecLabs

Security & AuditManifest valid

★ 607

↓ 1.1k/wk

NOASSERTION

JavaScript

Oct 9, 2026

dsh plugin --profile web add @howmp/dsh-pentest

by xiaods

k8e.sh - OpenSource Agentic AI Sandbox Matrix

Security & AuditManifest valid

★ 500

↓ 10/wk

Apache-2.0

Go

Oct 12, 2026

dsh plugin --profile agent add @k8e-sandbox/dsh-k8e-sandbox-bundle