DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Secret Scrub — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
S

dsh-secret-scrub

Secret Scrub

DeepSeek Harness plugin: redacts known secret shapes in tool arguments and results behind stable placeholders, and appends preimage-free JSONL incidents

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:jwilson411/dsh-secret-scrub#1028bc9933296dca3d8405901eaaa94f6acf6119
READMECompatibilityVersions

Compatibility and provenance

Secret Scrub is published as dsh-secret-scrub and currently resolves to version 0.1.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
any
Release source
github
Registry updated
9/1/2026

Versions

0.1.0stable
9/1/2026

Related plugins

Loading related plugins…

Latest
0.1.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
any
License
MIT
Source
github
GitHub
★ 0
Weekly downloads
0
Last push
8/31/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in security-access.

Doctor@linxin666/dsh-doctorTransactional rescue mode for DSH profiles with a supervised launcher, isolated recovery capsule, deterministic repairs, health monitoring, and a local Web recovery consolePocketdsh-pocketPut DeepSeek Harness in your pocket: one package, one settings page, and scan a QR code on your phone to access DSH on your computer in sync (LAN + public network, real-time screen mirroring).DSCODE@toddzheng024/dscode-bundleA complete DeepSeek coding agent with persistent shell, Ultra collaboration and automatic permission review.Auto Reviewdsh-auto-reviewSecond-model AI auto-review for DeepSeek Harness approval requests: a read-only reviewer subagent decides allow/deny on the approval answerer chain, with fail-closed fallback and full session-log audit.

README

dsh-secret-scrub

A DeepSeek Harness function plugin that redacts known secret shapes on the way in and out of tool execution, and writes an append-only JSONL incident line for each one — without the preimage.

A tool argument holding a token is redacted before the tool runs, and a tool result holding one is redacted before the model sees it. Each distinct secret becomes a stable placeholder:

$SECRET_GITHUB_PAT_3f9a1c04

The same bytes produce the same placeholder for the rest of the session, so the model can still reason about "the token in the argument came back in the result" — and the bytes themselves are gone, from the transcript and from the log alike.

What it is not

This is pattern redaction. It is not a secret manager.

  • Not a secret manager or a vault. It stores nothing, issues nothing, rotates nothing, and verifies nothing. If a secret was leaked into a prompt, redacting it here does not un-leak it: rotate it.
  • Not a preimage log. The incident line names the pattern and the placeholder. The matched bytes are hashed once, in memory, and dropped. There is no reverse map on disk and no API that returns one — not even to the tool that reports incidents.
  • Not a detector model. There is no classifier, no entropy heuristic, and no scoring. It matches the shapes in the table below, and only those. A credential with a shape not in the table passes straight through.
  • Not a live scanning API. Nothing is validated against GitHub secret scanning, AWS STS, or any other issuer. A match is redacted whether or not it was ever a live credential, and a redaction is not evidence of a leak.
  • Not DLP. It does not watch files, egress, or a mail flow. It sits on one seam — tool arguments and tool results — and nothing else passes through it.

Redaction is damage limitation, not a control. The control is not putting credentials where a model can read them.

Where it sits, and the seam it does not invent

dsh-telemetry-redactor scrubs the export copy: the transcript you ship somewhere else. This plugin scrubs the live copy, before the model or the tool sees it. They are complementary and neither replaces the other.

The pinned release candidate 0.1.1-rc.2 exposes no tool-execute middleware seam, so this package does not pretend to have one. It registers exactly one model-facing tool, secret_scrub_report, and exports wrapExecute for a host to apply where it owns the call. Nothing is monkey-patched and no private API is reached into.

Install

dsh plugin --profile default add github:jwilson411/dsh-secret-scrub

dsh plugin forwards to pnpm inside $DSH_HOME/profiles/default, then reconciles the profile: because this package's manifest declares dsh.bundle.patch, it is appended to the profile manifest's ordered dsh.profile.bundles list and its cordis.patch.yml becomes a layer. Remove it the same way, with remove in place of add.

Pinned DSH release candidate

This package is written and tested against the pinned release candidate 0.1.1-rc.2 — @deepseek-ai/dsh-tools@0.1.1-rc.2 is pinned exactly in devDependencies so tests run against one known API, and the peer range is ^0.1.1-rc.2, matching how the harness's own tool packages declare it.

What it registers

Cordis plugin idsecret-scrub (the row id in cordis.patch.yml)
Injectstools — a hard dependency; the plugin waits rather than degrading
Toolsecret_scrub_report
Argumentsn (integer, optional; default 20, capped at 200)

Patterns

Each pattern has a stable name that appears verbatim inside every placeholder and every incident line, a version bumped whenever its regex changes shape, and the date it was added. Names are public API — renaming one would orphan every stored incident. Regexes are not.

nameversionsincematches
GITHUB_PAT12026-08-31GitHub classic personal access token, ghp_ + 36
GITHUB_FINE_GRAINED_PAT12026-08-31GitHub fine-grained personal access token, github_pat_ + 40 or more
AWS_ACCESS_KEY_ID12026-08-31AWS long-lived access key id, AKIA + 16
SLACK_BOT_TOKEN12026-08-31Slack bot user OAuth token, xoxb- + two numeric segments + a secret segment
PEM_PRIVATE_KEY12026-08-31A PEM private key block, or its header alone
SK_LIVE_TOKEN12026-08-31Generic sk- prefixed live API token

The regexes, verbatim from src/scrub.js:

GITHUB_PAT               /\bghp_[A-Za-z0-9]{36}\b/g
GITHUB_FINE_GRAINED_PAT  /\bgithub_pat_[A-Za-z0-9_]{40,}\b/g
AWS_ACCESS_KEY_ID        /\bAKIA[0-9A-Z]{16}\b/g
SLACK_BOT_TOKEN          /\bxoxb-[0-9]{8,}-[0-9]{8,}-[A-Za-z0-9]{16,}\b/g
PEM_PRIVATE_KEY          /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g
SK_LIVE_TOKEN            /\bsk-[A-Za-z0-9_-]{20,}\b/g

PEM_PRIVATE_KEY has two branches, longest first: a complete armoured block collapses to one placeholder, so the key material inside goes with the header rather than being left behind under a redacted header. The second branch catches a header whose body was truncated before it arrived.

Placeholders

$SECRET_<PATTERN>_<8hex>

<PATTERN> is the name from the table. <8hex> is sha256(salt || 0x00 || secret) truncated to eight hex characters, where the salt is generated per scrub session.

  • Same bytes, same session → same placeholder. That is what makes the redacted text still reasonable about.
  • Same bytes, different session → different placeholder. The salt is fresh per session, so one session's log cannot be joined against another's, and a placeholder is not a durable identifier for a secret.
  • The digest is not a commitment. It is short on purpose, to keep redacted text readable. Do not build anything on top of it that assumes collision resistance, and note that an unsalted short digest of a known-shape secret would be brute-forceable by anyone holding a candidate list — which is why it is salted.

The library API

The tool is a side channel. The functions are the product:

import { createScrubState, wrapExecute, scrub } from 'dsh-secret-scrub'

// One state per session. It holds the salt and the placeholder memo, and no
// preimages beyond the life of the process.
const state = createScrubState({ session: sessionId })

// Wrap a tool's execute: arguments in, result out, incidents appended.
const execute = wrapExecute(tool.execute, state, {
  tool: tool.name,
  incidentLog: '/var/log/dsh/secret-scrub.jsonl',
})

// Or scrub one value directly.
const { value, findings } = scrub(payload, state)
// findings: [{ pattern: 'AWS_ACCESS_KEY_ID', placeholder: '$SECRET_…', count: 2 }]
export
createScrubState({ session, salt })opens a session; thread it through every call
scrub(value, state)recursive walk; returns { value, findings }
wrapExecute(execute, state, meta)scrubs arguments in, result out, and appends incidents
scrubToolArgs(args, state, meta)one direction, returning { args, incidents }
scrubToolResult(result, state, meta)one direction, returning { result, incidents }
appendIncidents(path, incidents)append-only JSONL write
readIncidents(path, limit)the tail of the log, sanitized
countByPattern(incidents){ pattern, count } rows
PATTERNSthe table above, as data

scrub walks strings, arrays, and plain objects, including object keys — a payload keyed by a token leaks as surely as one that holds it. Arrays and objects are rebuilt rather than mutated, cycles are handled, and anything that is not a string, array, or plain object (a Date, a Map, a class instance) is passed through untouched rather than flattened.

Incidents

Append-only JSONL, one line per distinct secret per direction per call:

{"ts":"2026-08-31T12:00:00.000Z","session":"s-1","tool":"read_file","pattern":"AWS_ACCESS_KEY_ID","placeholder":"$SECRET_AWS_ACCESS_KEY_ID_1a2b3c4d"}

Five fields, and only these five: ts, session, tool, pattern, placeholder. No preimage, no prefix, no length, no character count — a redaction log that leaks a little of every secret it saw is worse than no log. The file is created 0600.

The reader is defensive in the same direction: readIncidents reduces every line to exactly those five fields and drops any line whose placeholder is not placeholder-shaped, so a log appended to by something else cannot smuggle a preimage back out through the report tool.

Config

keytypedefault
incidentLogstring.dsh-secret-scrub.jsonlpath of the append-only JSONL log, relative to the process working directory

Resolution order is patch config, then environment, then default: a patch row is the deployment's stated intent, so it is not silently overridden by an ambient variable. The environment fallback is DSH_SECRET_SCRUB_LOG, used only when the patch row omits the key entirely.

- id: secret-scrub
  config:
    incidentLog: /var/log/dsh/secret-scrub.jsonl

The report tool

secret_scrub_report answers one question: what did this session redact?

{ n: 20 } // optional; default 20, hard cap 200 — a larger ask is clamped, not refused

It returns { incidents, counts, total, log, plugin }: the most recent incidents oldest-first, counts by pattern across that window, and the log path that was read. Placeholders and pattern names only. There is no tool, flag, or export that returns a preimage, because none is kept.

Layout

package.json          manifest + `dsh.bundle.patch` — what makes this a bundle
cordis.patch.yml      the bundle's patch layer: one insert, one plugin row
src/scrub.js          the library: patterns, placeholders, the walk, the log
src/index.js          the plugin: `name`, `inject`, `apply(ctx, config)`, the tool
test/                 offline tests: the walk, the wrapper, the log, a hygiene scan
package-lock.json     the pinned dependency tree `npm ci` installs in CI

Tests

npm install
npm test

Fully offline. No network, no secret-scanning API, no profile boot: apply is handed a stub context that records registrations, the tool is driven through the same execute the registry calls with results validated against the real @deepseek-ai/dsh-tools, and every incident log is written into a fresh directory under the OS temp directory and removed afterwards.

The fixtures are fake, and assembled at runtime

Every fake secret is built from fragments inside the test, so no committed file ever holds a contiguous token of any shape — GitHub push protection has nothing to reject, and neither does any other scanner:

patternfragments
GITHUB_PATghp_ + fake + 32 × x
GITHUB_FINE_GRAINED_PATgithub + _pat_ + fake + 18 × 0 + _ + 59 × y
AWS_ACCESS_KEY_IDAKIA + FAKESECRET + 111111
SLACK_BOT_TOKENxoxb + - + 1111111111 + - + 2222222222 + - + fake + 20 × z
PEM_PRIVATE_KEYthe header words joined with spaces, a TEST KEY body, and the footer words
SK_LIVE_TOKENsk- + live + - + fake + 24 × K

None of these were ever credentials. They are the right shape and nothing else — which is the whole point of a pattern redactor, and its whole limit.

test/hygiene.test.js asserts that rather than claiming it: it runs the redactor's own pattern table over its own tree and fails if any committed file holds a whole token, scans for machine names, mount paths, and credential variable names, checks that src/ contains no socket, fetch, or subprocess construct and imports nothing but the pinned tools package and the two node builtins it uses, and checks that CI needs no credentials.

CI (.github/workflows/ci.yml) runs npm ci and npm test on Node 22 and 24 from the committed lockfile, against the public registry only. It holds no secrets and reaches no network beyond that install.

Out of scope

  • Secret storage, issuance, or rotation. Covered above: this is not a vault, and a redaction is not a remediation.
  • Recovering a redacted value. Deliberately impossible here. If you need the original, you needed it before it reached the model.
  • Entropy or ML detection. No scoring, no classifier, no "probably a secret". Shapes only, so both the false positives and the false negatives are predictable and auditable.
  • Validating a match against its issuer. No network, ever.
  • Redacting anything but tool arguments and results. Prompts, system text, attachments, and exports are other seams with other owners.
  • Deciding for you. The wrapper redacts and records. What your deployment does about a recorded incident — alert, rotate, revoke — is your deployment's business.

License

MIT — see LICENSE.