Skip to content

dsh-veil

Verified

dsh-veil Β· v0.1.0 Β· Apache-2.0

DeepSeek Harness plugin: the model sees consistent pseudonyms, your machine keeps the real values (veil privacy proxy)

Install

dsh plugin add dsh-veil

Confirm the layer applied with dsh --profile default --dump-config β€” see the install guide.

Source

Tags

Creators

Readme

dsh-veil

A DeepSeek Harness (dsh) plugin that sends every DeepSeek model request through veil, a local privacy proxy. The model sees consistent pseudonyms for your customers' names, emails, phones and other seeded values. Your machine keeps the real values: dsh's tools run with them, and you read them in dsh's answers.

dsh ──► veil (127.0.0.1, started by this plugin) ──► DeepSeek API
          real values β†’ pseudonyms on the way out
          pseudonyms β†’ real values on the way back (answers, tool calls)

Install

  1. Install veil 0.3.1 or later, then seed it with the data the model must not see:

    pip install veil-proxy             # or: brew install lemonadesaltbagel/veil/veil
    veil seed postgresql://localhost/app ./customers.db   # any sources veil supports
    
  2. Add the plugin to a dsh profile (dsh 0.2.1-rc.2 or later; dsh plugin needs pnpm):

    dsh plugin --profile headless add dsh-veil
    dsh plugin --profile web add dsh-veil
    

    Before the npm package is published, install a release tarball or a checkout:

    dsh plugin --profile headless add https://github.com/lemonadesaltbagel/dsh-veil/releases/download/v0.1.0/dsh-veil-0.1.0.tgz
    
  3. Use dsh as usual. dsh --profile headless "…" and the web app start veil with the session and stop it on exit. /veil shows the proxy, the upstream and the counts of seeded values and pseudonyms.

What it changes in your profile

The bundle's patch (cordis.patch.yml) does four things:

  • Starts veil. The veil row runs veil proxy on a free loopback port. It provides the veil service only after the proxy answers its health check.
  • Routes DeepSeek through it. The llm-deepseek and llm-deepseek-account rows inject that service and take baseURL from it.
  • Fails closed. If veil can't start, those rows never activate. dsh then reports NO_ADAPTER and sends nothing, instead of calling DeepSeek directly.
  • Turns off llm-pi-ai. That route sends other providers (OpenAI, Gemini, …) straight to their APIs, past veil.

A fetch guard backs this up. It refuses any model request (/messages, /chat/completions, /responses, :generateContent) that isn't addressed to veil. It catches a later layer that points an adapter elsewhere, such as a profile cordis.patch.yml, a --patch overlay or a Models setting, and prints dsh-veil: refused a model request to ….

Tool calls

veil restores real values into a tool call only for tools it knows by name. dsh's tools take Claude Code's argument names, so the plugin maps them onto veil's roles (veil proxy --tool-alias):

dsh tool veil role what the tool receives
bash Bash real values, shell-escaped; commands that send data off the machine keep pseudonyms
read, grep, glob, working_directory Read / Grep / Glob real values
edit, write Edit / Write real values only where the file already had them, escaped for the file's language
str_replace_editor (built in) as Write / Edit
every other tool (run_code, workflow, pwsh, subagent, web_*, terminals) none pseudonyms only

Configuration

Override the veil row in your profile's cordis.patch.yml:

- id: veil
  config:
    command: /opt/homebrew/bin/veil                 # default: veil on PATH
    upstream: https://api.deepseek.com/anthropic    # default: $DEEPSEEK_BASE_URL, then this
    args: [--ner]                                   # extra `veil proxy` arguments
    # url: http://127.0.0.1:8787                    # use a running `veil proxy` instead of starting one
    # toolAliases: {bash: Bash}                     # replaces the table above
    # logFile: /tmp/veil.log                        # default: <tmp>/dsh-veil/veil-<pid>.log
    # startTimeoutMs: 60000
    # guardFetch: true

veil's own settings (vault, seeded sources, display mode) live in VEIL_HOME, shared with the veil CLI. See the veil README.

Limits

  • Your machine is trusted. dsh's session logs hold real values, because veil hands dsh real values on the way back. Only the DeepSeek API sees pseudonyms.
  • Images aren't scanned. veil passes images and /v1/files uploads through unchanged.
  • No other providers. The DeepSeek account route signs in with an account token, which the account service may refuse to send to a local address; the API-key route is the tested one. The pi-ai providers are off.
  • Tested surface. The end-to-end test covers dsh --profile headless. The web app uses the same rows, but /veil hasn't been exercised in its UI yet.
  • Leftover proxy. veil stops with dsh. If dsh is killed with SIGKILL, its veil proxy keeps running until you stop it.

Development

npm install && npm run build
npm test                 # unit tests
npm run e2e              # real dsh + real veil + a scripted model: 0 leaks, tools get real values,
                         # fails closed without veil, refuses a baseURL past veil

The e2e test needs dsh, pnpm and veil on PATH, or DSH_BIN and VEIL_BIN. CI runs it on every push against the npm dsh and the PyPI veil-proxy.

License

Apache License 2.0, like veil.