Bundle
dsh-secret-paste
DeepSeek Harness plugin: auto-detect secrets pasted into the composer, store them in the official credentials seam, and send [secret:REF] placeholders to the model instead of the value.
- Source
- zhujiaqi
- stars
- 2 stars
- License
- MIT
- Updated
- Updated 10 days ago
Readme
# dsh-secret-paste
> A DeepSeek Harness plugin that detects secrets pasted into the composer, stores
> them in the official credentials seam, and sends a `[secret:REF]` placeholder to
> the model instead of the plaintext. The real value never reaches the message,
> the session history, or any transcript.
<div align="center">
[](https://www.npmjs.com/package/dsh-secret-paste)
[](LICENSE)
[](cordis.patch.yml)
π δΈζοΌ[README.zh.md](README.zh.md) ο½ **English**
</div>
## Why
You paste an API key or a token into the chat. Without protection it goes straight
to the model and into the session log β which is exactly where a secret should
never land. This plugin intercepts the paste, stores the value in the official
DSH credential seam (`~/.dsh/.credentials.yaml`, mode 0600), and replaces it in
the draft with `[secret:REF]`. The model only ever sees the placeholder, and can
read the real value on demand through a dedicated tool.
## How it works
| Step | What happens |
|---|---|
| Detect | `@sanity-labs/secret-scan` (1,100+ rules derived from gitleaks / TruffleHog) runs on the pasted text. |
| Store | The value is written with the official `credentials.set` Web API β no new server route. |
| Replace | The matched range becomes `[secret:PASTE_N]` in the draft. |
| Resolve | The model calls the `secret_resolve(ref)` tool when it actually needs the value. |
The plaintext value lives in exactly two places: (1) this tab's memory, and
(2) `~/.dsh/.credentials.yaml` (mode 0600). Every other surface β the sent
message, session history, transcripts β sees only `[secret:REF]`.
## Installation
### Prerequisites
- A DeepSeek Harness **Web** profile (the examples use `web`).
- The `dsh` CLI and `pnpm` on your `PATH` (`dsh plugin` is a thin pnpm forwarder).
### Install from the npm registry (recommended)
```bash
dsh plugin --profile web add dsh-secret-paste
```
What this command does:
1. Runs `pnpm add dsh-secret-paste` inside the profile directory.
2. Installs the single runtime dependency (`@sanity-labs/secret-scan`).
3. Because the package declares `dsh.bundle.patch`, it is auto-registered in
`dsh.profile.bundles` as a profile layer.
No build step runs on install β the browser bundle is prebuilt and shipped.
### Restart
The server half (the `secret_resolve` tool) loads at boot, so restart the `dsh web`
process after installing, then force-refresh the browser tab
(`Cmd+Shift+R` / `Ctrl+Shift+R`) to load the client bundle:
```bash
# restart the dsh web process you manage (Ctrl-C + relaunch, or your process manager)
```
### Verify
- **Settings β Plugins** lists `dsh-secret-paste`.
- Paste a GitHub token (`ghp_...`) into the composer: it is immediately replaced
by `[secret:PASTE_N]` and a chip appears.
### Installing from an agent or in automation
The exact same steps work when an AI agent or a CI job installs the plugin:
1. `dsh plugin --profile web add dsh-secret-paste`
2. Restart `dsh web` (required for the `secret_resolve` tool to register).
3. Force-refresh the browser tab.
4. Verify: paste `ghp_...` and expect `[secret:PASTE_N]`, or confirm the served
page's `window.__DSH_BOOT__` contains a `dsh-secret-paste` entry.
## Usage
### Auto-detect and hide
Paste text that contains a high-confidence secret (`ghp_...`, `sk-proj-...`, JWT, β¦).
It is stored and replaced with `[secret:PASTE_N]` immediately, and a chip shows:
> π Hidden Β· <rule> Β· PASTE_N Β· Undo
- **Hover** the chip to reveal the value in a tooltip β no layout shift.
- **Undo** restores the plaintext while you are still drafting.
- After you **send**, the chip stays visible but the undo button disappears; once
the answer arrives, the chip is removed.
### Medium confidence
`confidence === 'medium'` hits (e.g. a `Bearer <token>`) stay in the draft as-is
and a "Suspected secret" chip asks you to confirm (Hide) or ignore (Ignore).
### Manual marking
Formats the detector does not recognize (`ark-...`, some `sk-...`) are never
guessed. Select the text and use the "Mark selection as secret" action, then "Hide & store".
### Nested placeholders
A selection that already contains `[secret:REF]` can be wrapped again. The
`secret_resolve` tool resolves such chains recursively down to plaintext
(cycles or missing inner refs return `found: false`).
## Model side: `secret_resolve`
When the model needs the real value, it calls the `secret_resolve(ref)` tool:
- Returns `{ found, value, source }`.
- Resolves nested placeholders recursively to plaintext.
- The value is **SENSITIVE**: the tool description instructs the model never to
echo, repeat, or write it into a reply, a file, a command, or a tool argument.
## Security model
- **Values never leak**: only `[secret:REF]` appears in the message, history, or
transcripts. Values exist only in tab memory and the 0600 credential file.
- **Detection is conservative**: `high` auto-hides, `medium` waits for a confirm,
and unknown formats are never guessed (manual marking is the fallback).
- **Collision-safe refs**: if a ref is already configured, the next free ref is
used instead of overwriting; the same value is stored once per session.
- **No reveal endpoint**: the credential seam reads value-free with no
enumeration; after a refresh the in-memory value is gone and chips do not persist.
- **Use-time visibility (v1)**: `secret_resolve` brings the value into model
context only when the model asks for it.
## Development
```bash
node scripts/build.mjs # rebuild lib/client.js (no external bundler)
npm test # node --test tests/*.test.mjs
```
## Structure
```
dsh-secret-paste/
βββ package.json # dsh.bundle.patch + dsh.client declarations
βββ cordis.patch.yml # mounts the server row
βββ lib/
β βββ index.js # server: secret_resolve tool (recursive resolution)
β βββ client.js # prebuilt browser bundle
βββ src/
β βββ resolve.js # nested-placeholder resolver (shared with server)
β βββ scan.js # detection helpers (shared with tests)
β βββ client/index.js # paste interception, chips, credentials.set
βββ vendor/secret-scan.cjs # vendored @sanity-labs/secret-scan@1.1.0 (MIT)
βββ scripts/build.mjs # assembles lib/client.js
βββ tests/ # node:test unit tests
```
## License
MIT. `vendor/secret-scan.cjs` is the compiled `dist/index.cjs` of
[`@sanity-labs/secret-scan`](https://www.npmjs.com/package/@sanity-labs/secret-scan)
v1.1.0 (MIT), whose rules derive from
[gitleaks](https://github.com/gitleaks/gitleaks) (MIT) and TruffleHog detectors;
its license is preserved at `vendor/secret-scan.LICENSE`.
Install
dsh plugin --profile web add github:zhujiaqi/dsh-secret-paste
Profile: web
With the hub plugin installed, ask your agent to install it by name β it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-secret-paste from the hub
- This package builds from source on install. pnpm will ask you to allow its build script β that is permission to run the packageβs code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.