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

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source