Skip to content
dsh.fish
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">

[![npm](https://img.shields.io/npm/v/dsh-secret-paste?style=flat-square&color=5B4CF0)](https://www.npmjs.com/package/dsh-secret-paste)
[![MIT](https://img.shields.io/badge/license-MIT-0B7285?style=flat-square)](LICENSE)
[![DSH](https://img.shields.io/badge/DSH-Web-5B4CF0?style=flat-square)](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

  • 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.
Source