Bundle
dsh-privacy-mask
Display-only privacy mask for DeepSeek Harness chat: regex → ***, hover to reveal; copy and LLM context unchanged.
- Source
- Huauauaa
- License
- MIT
- Updated
- Updated 18 days ago
Readme
# dsh-privacy-mask
Display-only privacy mask for the DeepSeek Harness (dsh) web UI. Regex matches in chat text render as a placeholder (default `***`); hover reveals the original. Copy, selection, and LLM context are **unchanged**.
## What it does
- Scans user messages, assistant body text, and tool/result cards in the conversation DOM
- Wraps regex matches in a CSS mask — the DOM still holds the **original** text
- Shows a configurable placeholder instead of matched substrings; hover reveals the real value
- Built-in rules for common secret shapes (API keys, Bearer tokens, email, PEM headers) plus custom rules
- Settings page section **Privacy mask** (open via the bottom-left **Settings** button)
## What it does **not** do
- Does **not** redact session data, exports, telemetry, or model/tool payloads
- Does **not** change what the LLM sees or what gets stored server-side
- Does **not** alter clipboard output — copy/paste still yields the original secret text
Config still lives in browser `localStorage` (not Host `settings.plugin.item`); see [Configuration storage](#configuration-storage).
## Install
From a checkout of this repo:
```bash
dsh plugin --profile web add /absolute/path/to/privacy
```
Then **restart** (or reload) the web profile so the client bundle loads.
`lib/client.js` is a classic-script ModuleLoader factory (`window.__ModuleLoader__.load`, id `dsh-privacy-mask`), self-contained for the browser.
Dev overlay (optional): `dsh web --patch ./cordis.patch.yml` with absolute plugin paths per dsh docs.
## Settings
Open the bottom-left **Settings** control, then choose **Privacy mask** in the settings nav.
| Field | Description |
|-------|-------------|
| **Enabled** | Master switch. Off unwraps all masks and stops scanning. |
| **Placeholder** | Text shown instead of matches (default `***`). |
| **Rules** | Ordered list. Each rule has an enable toggle, name, and regex pattern. |
Per rule:
- **Builtin** rules — name and pattern are read-only; you can disable them but not delete or edit the pattern.
- **Custom** rules — fully editable; use **Delete** to remove. **Add rule** appends a blank custom row.
Actions:
- **Save** — validates every pattern; invalid regex or empty/whitespace-only patterns show an inline error and are **not** written.
- **Reset to defaults** — restores built-in rules and default placeholder immediately.
Storage key: `dsh-privacy-mask:v1` in `localStorage`. After changing `package.json` client injects, **restart** the web profile so `__DSH_BOOT__` picks up `slots`.
## Default built-in rules
All enabled by default:
| Name | Pattern (JS RegExp source) |
|------|----------------------------|
| API key-like | `\b(?:sk-or-\|sk-\|rk-)[A-Za-z0-9_\-]{16,}\b` |
| Bearer token | `\bBearer\s+[A-Za-z0-9\-._~+/]+=*` |
| Email | `\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b` |
| PEM private key header | `-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----` |
The API-key rule is intentionally conservative; disable it if you see false positives.
## Tests
```bash
npm test
```
Runs Node `node:test` suites for `mask-core` (match/segment contract), `writeConfig` (persist + invalid/empty-pattern rejection), and conversation selectors. Tests import ESM sources (`lib/mask-core.js`, `lib/settings-store.js`, `lib/selectors.js`); the browser file inlines those.
## Compatibility / DOM selectors
DSH web UI markup can change between versions. If masking misses the chat or hits the wrong area, edit **`lib/selectors.js`** (keep the inlined copy in `lib/client.js` in sync):
- **`conversationRoots()`** — scan start: prefers `[data-conversation-scroll]`, then `[role="log"]` / `[data-testid="conversation"]`, then `main`, then `document.body`
- **`shouldSkip()`** — ignores composer (`[data-composer-seat]` / `[data-composer-card]`), sidebar (`[data-slot="sidebar"]…`), inputs/contenteditable, shell overlays, the settings panel, and nodes already masked
Prefer stable `data-*` / roles over hashed class names. On first install, still verify in DevTools that the transcript root and composer markers match your dsh build.
## Configuration storage
The **Privacy mask** page is registered as a Settings nav section (`settings.section`). Values still persist in browser **`localStorage`**, not Host `settings.plugin.item`.
Public dsh rc builds enforce a **`WEB_SETTINGS_NAMESPACES` allowlist** that blocks third-party plugin namespaces from the Host settings store. Until that allowlist includes `privacy-mask`, `localStorage` remains the persistence path. The Host entry (`lib/index.js`) is a no-op mount marker only.
## Manual smoke checklist
After install into a web profile:
1. Send or view a message containing a fake Bearer token (e.g. `Authorization: Bearer abc.def-123`) — UI should show the placeholder.
2. Hover the masked span — original text appears.
3. Select and copy — clipboard contains the **original** token, not `***`.
4. Disable the Bearer rule (or master **Enabled**) and save — text renders plainly.
Install
dsh plugin --profile web add github:Huauauaa/dsh-privacy-mask
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-privacy-mask from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.