dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Deterministic, one-way PII anonymizer at the start of the DeepSeek Harness agent loop
A native DeepSeek Harness plugin that replaces detected PII with stable placeholders in what the model is sent: your prompt, the system prompt, runtime context, workspace instructions, recalled sessions, relayed agent messages, and tool results.
Detection uses fixed rules and checksums (never a model). Every detected value is
replaced with a keyed placeholder such as [[EMAIL_3f9a2c1d]] before the request
goes to the provider. The real values do stay on this computer: the host app
commits your message to the local session log exactly as you typed it, and files
and tool outputs on disk are not changed.
you type: email a@x.com or call +31 6 1234 5678
model sees: email [[EMAIL_6c1f0a9e]] or call [[PHONE_d24b7c03]]
+ a notice: "🔒 PII shield: 2 item(s) in this step's input were
replaced with placeholders before being sent to the model
provider (1 EMAIL, 1 PHONE)."
local log: the message as typed — raw values remain in the local session
log and transcript.
The notice is a user-role message: it is shown in your transcript and the
model receives it (that is how the harness delivers plugin notices). It carries
only types and counts — never values or placeholders. Turn it off with
notify: false.
Anonymization is one-way: the plugin keeps no value → placeholder table and has no restore path, so nothing it holds can put the real values back.
Each listener is registered with prepend: true, so it is the outermost
listener of its waterfall and rewrites the final value after every other plugin
has contributed.
| Hook (package) | What is rewritten |
|---|---|
system-prompt/assemble (dsh-system-prompt) | prompt section text (e.g. a terminal app's workspace section), runtime-context text, and string prompt variables. The agent loop renders the assembled contexts into its form: "snapshot" user message, so snapshots (e.g. a memory index) are covered here, and the loop's "has the snapshot changed?" check stays stable. |
agent/pre-step (dsh-agent-loop) | the text blocks of every message entering the step, whatever its source: the user prompt, runtime-context snapshots, workspace instructions (AGENTS.md), recalled sessions (recall), relayed agent messages (relay), skill catalogs, and contexts attached by tool results. |
tools/post-execute (dsh-tools) | the tool result content the model reads (file reads, grep, shell output, memory reads…), a block decision's feedback, and attached contexts. |
The Claude Code / Codex hook bridges cannot do this. HookOutput in
@deepseek-ai/dsh-hook-protocol exposes only additionalContext; its
updatedInput is documented as "PARSED but NOT honored." There is no
prompt-rewrite channel. Only a native Cordis plugin can replace the messages,
prompt, and tool results that enter the model request.
Deterministic means structural. The detectors find things with a grammar or a checksum:
| Type | Trigger | Validation |
|---|---|---|
API_KEY | known prefixes (sk-, sk-ant-, AKIA…, ghp_…, github_pat_…, xox…, AIza…) | — |
IBAN | CC00…, compact or space-grouped | mod-97 checksum |
CREDIT_CARD | 13–19 digits, spaces/dashes allowed | Luhn checksum |
SSN | DDD-DD-DDDD | none |
EMAIL | RFC-ish address shape | none |
IPV6 / IPV4 | address shapes | octet range for IPv4 |
PHONE | digit run with + ( ) . - space, optional leading (, 7–15 digits | rejects ISO dates and version strings |
Free-form names, postal addresses, and other context-dependent PII are not detected — a deterministic rule cannot catch them without unacceptable false positives. Adding an ML/NER stage would break the "deterministic" property.
Overlaps are resolved by priority (checksum types beat the loose phone rule), then span length, then position.
A placeholder is [[TYPE_<hash>]], where <hash> is the first 8 hex digits of
HMAC-SHA256(key, type + normalized value). Normalization folds trivial spelling
differences (email case, IBAN/card/phone spacing and separators) into one value.
$DSH_HOME/pii-anonymizer.key (default ~/.dsh/pii-anonymizer.key), created
on first use with mode 0600 (an existing file with looser permissions is
tightened). Without the key, a placeholder cannot be matched against guessed
values, which matters for low-entropy values such as phone numbers. Keep the
file private; deleting it changes every placeholder from then on. If the file
cannot be read or created, the plugin logs a warning and uses a process-only
key: values are still replaced, but placeholders are not stable across
restarts.Covered (model-facing text):
Not covered:
$DSH_HOME
and nothing else.complete: true system-prompt section. dsh-system-prompt restores an
effective complete section from its pre-waterfall text after the
system-prompt/assemble waterfall, so a plugin cannot rewrite it.tools/post-execute. The registry finalizes a
few outcomes without post-execute — cancellations and pipeline failures (for
example a throwing tools/pre-execute or tools/execute listener) — and a
tool's own finalizeContent callback runs after post-execute. Their text
reaches the model unrewritten. Denials, approval rejections, tool errors and
successes all pass through post-execute and are covered. A downstream
post-execute listener that replaces the result's value is left alone (the
registry forbids replacing both value and content).Rewriting injected context can occasionally hide a value the agent genuinely
needs (for example an IP address in a workspace instruction). Disable the
system-prompt or tool-result rewrite with systemPrompt: false or
toolResults: false if that matters more to you than the coverage.
- id: pii-anonymizer
name: dsh-pii-anonymizer
config:
enabled: true # master switch
notify: true # add the notice message when a step's input had PII
debug: false # log load/shield events (counts only, never values)
systemPrompt: true # rewrite system prompt + runtime context
toolResults: true # rewrite tool result content
keyFile: '' # empty = $DSH_HOME/pii-anonymizer.key
This package is a DSH bundle: package.json declares dsh.bundle.patch, and
cordis.patch.yml inserts the pii-anonymizer row. A profile lists it in both
dependencies and dsh.profile.bundles — which is what the moqi-tui /plugins
picker reads (it reads the manifest, not the Cordis loader).
Install it from the registry into a profile:
npm install dsh-pii-anonymizer # inside ~/.dsh/profiles/<name>
# or, from the terminal app:
/plugins add dsh-pii-anonymizer
tools/register-profile.mjs <profileDir> makes the manifest edit for a local
checkout, and writes a package.json.bak beside it:
node tools/register-profile.mjs ~/.dsh/profiles/tui
The package must also be resolvable from the profile, for example by symlinking the checkout into the shared resolution root:
ln -s "$PWD" ~/.dsh/profiles/node_modules/dsh-pii-anonymizer
Restart the app to load it. Rollback per profile: restore its package.json.bak.
npm test # node --test: detectors, keyed pseudonymizer, key file, and
# the pre-step / assemble / post-execute rewrites
The unit tests import only src/detectors.mjs, src/anonymizer.mjs and
src/hooks.mjs, which have no harness dependencies, so CI needs no install.
src/index.mjs imports the harness peers; for local module-load smoke tests,
symlink node_modules/@deepseek-ai to the @deepseek-ai directory of your
harness install. An installed plugin resolves them from the profile instead.
tools/mock-provider.mjs is a tiny OpenAI-compatible SSE server; tools/e2e.patch.yml
routes the deepseek adapter at it and loads this plugin. A real harness loop then
runs against the mock, and tools/requests.jsonl records exactly what the
provider received. With MOCK_READ_FILE set, the mock first asks the agent to
read that file, so the tool-result path is exercised too.
# 1. throwaway harness home (never ~/.dsh), with this checkout resolvable
export DSH_HOME=$(mktemp -d)
dsh --profile pitest --from-default-profile headless --dump-config >/dev/null
ln -s "$PWD" "$DSH_HOME/profiles/node_modules/dsh-pii-anonymizer"
# 2. a scratch workspace with PII in an instruction file and a data file
WORK=$(mktemp -d) && git -C "$WORK" init -q
echo 'Owner: owner@corp.example' > "$WORK/AGENTS.md"
echo 'customer: c@corp.example +31 6 9876 5432' > "$WORK/notes.txt"
# 3. mock provider
MOCK_READ_FILE="$WORK/notes.txt" node tools/mock-provider.mjs &
# 4. one real turn
(cd "$WORK" && DSH_E2E_KEY=dummy dsh --profile pitest \
--patch "$OLDPWD/tools/e2e.patch.yml" "My email is jane.doe@corp.example; read notes.txt")
# 5. confirm the provider only ever saw placeholders
grep -c 'corp.example' tools/requests.jsonl # expect 0
grep -o '\[\[[A-Z_]*_[0-9a-f]*\]\]' tools/requests.jsonl | sort | uniq -c
Verified: across the agent requests and the session-title request, the
provider received placeholders for the prompt email, the AGENTS.md email
(workspace instructions), and the email and phone in the read tool result;
no corp.example value appeared. A second run with the same DSH_HOME
produced the identical placeholders, and the runtime-context snapshot was sent
once, not re-sent every step. The raw values do remain in the local session
log, as documented above.
agent/pre-stepsystem-prompt/assembletools/post-execute