dsh-vault is a security-focused plugin for DeepSeek Harness that stores sensitive credentials — usernames, emails, phone numbers, passwords, TOTP secrets, and developer credentials like SSH connections, API keys, secrets, and OAuth access/refresh tokens — encrypted at rest, and exposes them to the model through CRUD, search, password generation, and TOTP tools, plus a Settings UI page.
Security & Implementation
Zero external crypto dependencies: everything is built on Node's built-in node:crypto (AES-256-GCM authenticated encryption, scrypt key derivation, RFC 6238 TOTP).
Master password: every entry is encrypted with a 256-bit key derived via scrypt(master password, salt) and AES-256-GCM. The key never touches disk; after unlock it is cached in-process and re-derived on restart.
Tamper-evident: GCM auth tags plus a fixed-plaintext verification envelope — a wrong master password or modified ciphertext fails immediately, never returning garbage.
No plaintext at rest: the on-disk document contains no plaintext credentials; each entry uses an independent random nonce.
Atomic writes: reuses the harness writeFileAtomic + file lock; in-process writes are serialized, cross-process writers take the lock.
Search never leaks: vault_search returns summaries only (id/title/kind/username/email/phone/host/port/url/tags) — never passwords, keys, tokens, or TOTP secrets. Full credentials are readable only via explicit by id.
vault_get
Entry Model
Each record has a title, an optional kind, and any combination of fields:
Bring a trashed entry back / purge it / undo the last delete
vault_lock / vault_unlock
Explicitly lock the vault (wipe the in-memory key) / re-unlock it
vault_totp
Generate the current 6-digit code for a stored otpSecret (or a bare Base32 / otpauth URI)
vault_generate_password
Generate a strong random password (length/classes/grouping) or a memorable passphrase (passphrase: true, EFF-style word list, words/separator/wordDigits)
List an entry's previous passwords (1Password/Bitwarden-style, newest first, capped at 10; current password excluded)
kind: card
Bank/credit-card entries: cardNumber/cardExpiry (MM/YY)/cardCvv/cardHolder; search summaries expose expiry + holder only (never the number or CVV); Bitwarden JSON export maps to a card item (type 3, brand inferred)
vault_password_rollback
Restore an entry password to a stored history entry (current password is archived first, so it is reversible)
One-time vault recovery code (1Password/Bitwarden-style): 32-char code shown once, only its SHA-256 hash is stored; verify possession of the code; check whether one is set
vault_rekey
Upgrade the vault to fresh scrypt KDF parameters in place
vault_backup
Timestamped encrypted backup with retention; optional note
vault_import_csv
Bulk-import credentials from a CSV file (custom columns become fields; overwrite: true merges fields into existing entries instead of duplicating)
vault_bulk_delete
Soft-delete entries matching a query/kind/tag or explicit ids; confirm: true required (dry-run by default); trashed entries are restorable
vault_apply_tags
Bulk add/remove/replace tags on every entry matching a query (dry-run supported, no secrets)
vault_totp_uri
Build an otpauth:// provisioning URI for a stored or bare TOTP secret
vault_switch / vault_list
Switch the active vault by name / list available vaults
vault_rotation
Report expired / due-for-rotation / expiring-soon credentials; soonWindowDays (1-90, default 7) tunes the soon horizon (no secrets)
vault_health
Vault health scan: weak/reused passwords, missing 2FA, insecure http:// sites, and an overall security score (0–100)
vault_watchtower
Watchtower-style per-entry risk analysis (1Password/Bitwarden-inspired): flags short/weak passwords, keyboard sequences, embedded years, common passwords, reuse, http:// sites, missing 2FA, expiry — with a 0–100 score and good/warn/poor verdict (no secrets); entry rows show ⚠ badges
vault_breach_check
Watchtower-style breach scan against Have I Been Pwned (k-anonymity: only the SHA-1 prefix leaves the machine), with an offline common-password fallback
vault_integrity
Verify the on-disk vault file decrypts correctly and matches the in-memory store
vault_merge
Merge one entry into another; keepSource: true keeps the source after merging
Attach files to entries (private keys, certs, configs, recovery codes) — stored base64 inside the encrypted entry, encrypted at rest; list names/sizes, read content, remove
vault_quick_add
Fast capture (title + one secret) with optional tags/notes
vault_expiry
Set/clear expiry (expiresAt: 0 removes it)
vault_stats
Overview counts incl. trashCount (no secrets)
vault_verify
Verify one entry or audit every entry (all: true) for per-kind completeness, port/expiry sanity (no secrets)
vault_duplicates
Find duplicate groups: mode = both (default) / title / content (no secrets)
vault_report
Printable inventory with expiry/rotation columns and a stats footer (no secrets)
vault_export / vault_import
Portable encrypted backup/migration of the whole vault (separate export password)
vault_backup / vault_backup_now
Timestamped encrypted backup named <vault>-backups-YYYY-MM-DD_HH-MM-SS-<hex>.json (owning vault + date visible); retention pruning keeps the newest N
vault_restore_backup
Restore from a backup: mode: "merge" (default) copies the backup entries INTO the current vault so they appear in the entries list; mode: "replace" overwrites the whole vault with a safety snapshot first
vault_vault_rename / vault_vault_delete
Rename a named vault (file moves, active session follows) or permanently delete one (default vault protected)
vault_match_url
Find login entries matching a URL (Bitwarden/1Password-style: exact host, subdomain, parent domain, path-prefix; www./port normalization) with a 0–100 score — never returns the password
vault_fill
Find the entry matching a host/URL/username/title and return its credentials
vault_env
Render env-flagged entries (tags contain env) as KEY=VALUE lines — see Environment export
vault_export_bitwarden / vault_import_bitwarden
Bitwarden/Vaultwarden JSON interop (full field mapping, overwrite support)
vault_import_bitwarden_encrypted
Decrypt a Bitwarden password-protected JSON export (PBKDF2/Argon2id + HKDF → AES-256-CBC + HMAC) and import it; pass the export passphrase
KeePass KDBX: 3.1 and 4.x, AES-KDF or Argon2 (RFC 9106), AES-256-CBC or ChaCha20 payload, keyfile support
vault_export_1password
Export entries as a 1Password 1PUX archive (ZIP + export.data) for import into 1Password or re-import here; item categories map to login / credit card / API credential / server
KeePass 2.x XML export (plaintext or ******** masked values)
vault_import_chrome / vault_import_keychain
Import passwords from Chrome's Login Data (macOS keychain / Linux keyring or peanuts / Windows DPAPI) or the macOS Keychain (internet passwords inet by default — the ones that actually back website logins — or generic genp via classes; session cache + preview, no prompt spam); every file import supports dryRun preview
Search Chrome / Keychain for sites & usernames — never exposes passwords
vault_session_open
Open a real headed browser window at a URL so the user can log in manually (password, 2FA, captcha) — the portable way to capture login state for sites that block embedding
vault_session_collect
Collect every cookie of an open browser session (incl. HttpOnly) and save it as a cookie entry
vault_session_import
Save session cookies from pasted JSON (devtools export shape) or a raw Cookie header string — the no-browser alternative
vault_session_import_file
Import a Netscape cookie-jar file (curl -b / wget / browser-extension export; the same format vault_session_export writes)
vault_session_list
List saved login sessions with cookie counts, expired and expiring-in-7d counts (no values)
vault_session_export
Export a saved session as a Cookie header value, a Netscape cookie-jar file (curl -b), raw JSON (Playwright addCookies shape), or a ready-to-run Playwright snippet
vault_session_close
Close an open browser login session (collected cookies stay in the vault)
vault_session_prune
Remove expired cookies from a saved session (session cookies are kept); preview: true reports without writing
vault_copy
Copy an entry (secrets included) into another named vault
vault_templates
Built-in + user-defined templates (save/list/remove), KeePassXC-style; built-ins now include Wi-Fi, Server, Database, Identity, Bank account, Card (1Password-inspired)
Typical workflows: store an SSH credential (kind: ssh + host/port/username/password or privateKey) and have the model vault_search for the host then vault_get the connection details; keep api-key/oauth entries for API-gateway access/refresh token rotation.
Installation
dsh-vault is a bundle (a package declaring dsh.bundle): once installed into a profile, its cordis.patch.yml automatically inserts the vault plugin row (referenced by package name dsh-vault; the master password is injected via the DSH_VAULT_PASSWORD environment variable). The package ships a self-contained build script — git installs compile lib/ automatically.
All four install paths below are verified end-to-end (install → bundle layer recognized → plugin activates with all 7 vault_* tools registered → real vault_add/vault_get round trip → uninstall removes the layer):
Path
Command
Build needed
allowBuilds
npm
add dsh-vault
no (prebuilt lib/)
no
GitHub
add github:Ox0400/dsh-vault#v0.1.1
yes (prepare)
yes (first run)
local path
add /abs/path/to/dsh-vault
no (link to built source)
no
tarball
add ./dsh-vault-0.1.1.tgz
no (prebuilt lib/)
no
Option 1: Install from npm (easiest)
dsh plugin --profile web add dsh-vault
npm packages ship prebuilt lib/ artifacts — no allowBuilds, no local compilation, install and go. Set the master password before launching:
Option 2: Install from GitHub (pin a tag or commit)
dsh plugin --profile web add github:Ox0400/dsh-vault#v0.1.1
A git install fetches sources, not built artifacts, so the prepare script builds lib/ at install time. pnpm ≥10 blocks git dependencies from running build scripts by default. The verified flow:
Run the add command — it fails with an allowBuilds error and prints the exact key to allow (the line containing the repo URL, including the resolved commit hash):
Re-run the add — pnpm now runs the prepare script, builds lib/, and installs.
Pin a tag/commit so a later upstream push cannot silently change what runs on install. Treat the allowance for what it is: permission to execute that package's code on your machine at install time — only grant it to sources you trust.
Option 3: Install from a local path
dsh plugin --profile web add /absolute/path/to/dsh-vault
pnpm links the checkout into the profile; the bundle is recognized as long as lib/ exists (run pnpm build in the checkout first if needed).
Option 4: Install from a tarball
npm pack && dsh plugin --profile web add ./dsh-vault-0.1.1.tgz
The tarball ships prebuilt lib/ artifacts, so no build step or allowBuilds is required.
dsh plugin --profile web remove dsh-vault uninstalls (removes both the dependency and the layer).
Command line
The plugin hands the vault to the assistant. The bundled dsh-vault command
hands the same vault to shells and scripts, so a skill's child process can read
a secret without the plaintext ever entering the model's context.
0. First: which command can you actually type?
Nothing provides a dsh-vault binary until the package is installed — the
plugin is a Cordis bundle, and dsh has no plugin-subcommand registry (the
launcher parses only its own flags; dsh plugin … just forwards to pnpm, so
dsh web exec … hands its arguments to the web app and fails with
error: too many arguments). Pick your row before reading the examples:
your setup
the command
source checkout (git clone + pnpm build)
pnpm vault — or node lib/cli.js, or ./lib/cli.js
plugin installed into a profile
pnpm dsh plugin --profile web exec dsh-vault
globally installed (npm i -g dsh-vault)
dsh-vault
nothing installed
npx dsh-vault
# source checkout — this repository, right now:
pnpm vault list
# after a global install (or via npx) the same commands are:
dsh-vault list
npx dsh-vault list
Every example below is written as dsh-vault …; substitute the form from your
row (pnpm vault … in a checkout).
1. What env is for
get fetches one value and needs no tag. dsh-vault env is for the other
case: a script that needs several secrets at once. It prints the entries you
tagged env as shell assignments, so the script picks them up itself:
Only env-tagged entries are exported. With none tagged you get
"no entries are tagged for environment export"; tag one in the UI (the
entry's tags field) or with vault_update { id, tags: ["env"] }.
The name is derived from the title and field (DASHSCOPE + apiKey →
DASHSCOPE_API_KEY) unless the entry sets envKeys — see
Environment export.
Names — there are only three, and two of them are the word env on purpose:
name
what it is
meaning
env
an entry tag
which entries dsh-vault env exports; nothing is exported without it
env
the command
prints exactly the entries carrying that tag
envKeys
an entry field (optional)
pin exact names, e.g. ["DASHSCOPE_API_KEY"]; the old singular envKey is accepted as a one-element shorthand
Nothing else is called env*: list --json / show report envKeys (the names
an entry exports — what get accepts) and envTagged (whether env includes it).
2. The commands
dsh-vault list # grouped: what `env` exports vs the rest
# Exported by `env` (tag "env") — 1 entry:
# a1b2c3d4-… api-key DASHSCOPE → DASHSCOPE_API_KEY
# Not exported (no "env" tag) — 1 entry; `get <name>` still works for it:
# 9f8e7d6c-… api-key Example Billing → EXAMPLE_BILLING_API_KEY
dsh-vault get my-entry # the entry's primary secret, stdout only
dsh-vault get my-entry --field apiKey # one named field
dsh-vault get my-entry --mask # confirm it exists without printing it
dsh-vault show my-entry # non-secret metadata as JSON
dsh-vault env # env-tagged entries as KEY=VALUE
dsh-vault env --mask # human view, both sections as KEY=VALUE:
# ## exported items
# NPM_REGISTRY_API_KEY=npm_***
# ## unexported items
# EXAMPLE_BILLING_API_KEY=***
dsh-vault export-env .env # …or write them to a 0600 file
dsh-vault verify # check the master password, nothing on stdout
Secrets go to stdout and everything else (progress, errors) to stderr, so
$(dsh-vault get …) and pipelines behave. Exit codes: 0 ok, 1 runtime
error, 2 usage error.
3. Shell quoting: not export $(…)
eval "$(dsh-vault env)" is right; export $(dsh-vault env) is wrong. Command
substitution splits on whitespace and does not strip the quotes the CLI
emits, so a value containing a space breaks into the wrong words and every value
keeps its literal quotes:
$ export $(dsh-vault env); echo "$WITH_SPACE"
['pass] # split at the space
bash: export: `here': not a valid identifier
$ eval "$(dsh-vault env)"; echo "$WITH_SPACE"
[pass word here] # read as intended
In a script, prefer the file form — no eval at all:
dsh-vault export-env .env && set -a && . ./.env && set +a
4. The master password
--password-stdin, then $DSH_VAULT_MASTER_PASSWORD (or $DSH_VAULT_PASSWORD),
then an interactive prompt that hides what you type (the terminal is switched
to raw mode; verify with tests/e2e/cli-prompt.mjs). It is not read from the plugin's config file, so
the CLI never depends on (or leaks) whatever the profile patch holds:
DSH_VAULT_MASTER_PASSWORD=… pnpm vault list
printf '%s\n' "$PASSWORD" | pnpm vault list --password-stdin
5. Install details
Source checkout:
git clone git@github.com:Ox0400/dsh-vault.git && cd dsh-vault
npm install && npm run build # builds lib/, including executable lib/cli.js
pnpm vault list # the repo's own entry point (package.json script)
node lib/cli.js list # same thing, spelled out
./lib/cli.js list # the file is executable too
npm link # …or put a real `dsh-vault` on PATH
To develop the plugin against a profile you also link the checkout into the
profile and add a patch layer that inserts it (how this repository is normally
developed); the CLI needs none of that — it reads the vault file directly. A
hand-linked plugin has no .bin/dsh-vault shim, so pnpm … exec reports
Command "dsh-vault" not found unless you install it as a dependency
(pnpm dsh plugin --profile web add dsh-vault) or create the shim by hand:
pnpm dsh plugin --profile web add dsh-vault # into a profile (plugin + CLI together)
pnpm dsh plugin --profile web exec dsh-vault list # --profile is required
npm i -g dsh-vault && dsh-vault list # globally
npx dsh-vault list # or not at all
The read commands (list, get, env, show, verify, export-env) depend
on Node alone, which is why the global and npx forms work. Only writing to a
vault needs the harness runtime, and the plugin always has it.
Two more caveats on the profile route: its .bin shim can be pruned by a later
pnpm install, and pnpm dsh plugin --profile web add dsh-vault reconciles
dsh.profile.bundles from the package's dsh.bundle declaration — if your own
patch layer also inserts the plugin, that mounts it twice, so remove the patch
row first.
Privacy of the breach check
vault_breach_check (and the security page) can check passwords against Have I
Been Pwned. Exactly one thing leaves this machine:
GET https://api.pwnedpasswords.com/range/<FIRST 5 HEX CHARS OF SHA-1(password)>
Add-Padding: true
The response lists every suffix in that bucket (hundreds of hashes) and the match
is computed locally — the full hash, the password, and the entry it belongs to
never leave. A password already in the bundled common-password list is answered
offline and produces no request at all; repeat lookups of the same prefix are
cached in memory for an hour.
What k-anonymity does not hide, stated plainly:
the five-character prefix (20 bits, one bucket in ~1M) goes to Cloudflare,
together with your IP;
an observer who can guess candidate passwords can compute each candidate's
prefix and see whether you queried that bucket — that is the known limit of
this protocol, not something the header fixes;
Add-Padding only removes the response-size signal (without it, a bucket's
response length correlates with how common the password is).
To keep even the prefix on your machine, pass online: false — the check then
uses only the offline list, which is far smaller, so treat it as a screening aid
rather than a verdict.
Strength indicator
Each entry row shows a three-star indicator after its title — ☆☆☆ = 0 score,
★★★ = full marks — in six half-star steps:
A half star is a real half-filled star, not a ½ next to it: the coloured copy
of the same three glyphs is clipped to a percentage, and ★/☆ share an advance
width so the clip lands exactly on a star boundary.
score
filled stars
band
0–8
0
weak
9–24
½
weak
25–41
1
weak
42–58
1½
fair
59–74
2
fair
75–91
2½
strong
92–100
3
very strong
Hovering it shows the exact score and verdict. The score is computed host-side
(the list never receives the secret) and covers the entry's password or card
PIN; machine-generated API keys and private keys are deliberately not scored,
so every key does not sit at full marks.
Environment export
Entries tagged env can be materialised as KEY=VALUE lines (vault_env,
vault_export_env), and their names follow the field names every toolchain
expects:
Entry
Exported key
title DASHSCOPE, apiKey
DASHSCOPE_API_KEY
title DASHSCOPE, prefix: APP_
APP_DASHSCOPE_API_KEY
envKey: "TAVILY_TOKEN", accessToken
TAVILY_TOKEN
…the same entry's refreshToken / fields.scope
TAVILY_TOKEN_REFRESH_TOKEN / TAVILY_TOKEN_SCOPE
The field suffix is vendor-standard: apiKey → API_KEY, accessToken → ACCESS_TOKEN, refreshToken → REFRESH_TOKEN, privateKey → PRIVATE_KEY,
password → PASSWORD, cardNumber → CARD_NUMBER.
Set an entry's env-var names (optional field envKeys, also via
vault_add/vault_update, or comma-separated in the editor) when the derived
names are not what your scripts expect. They are positional and used
verbatim (no prefix, no title):
envKeys
exported
["DASHSCOPE_API_KEY"]
DASHSCOPE_API_KEY=apiKey
["GOOGLE_ACCESS_TOKEN", "GOOGLE_REFRESH_TOKEN"]
...=accessToken, ...=refreshToken
["MY_KEY"] on an entry with 3 secrets
MY_KEY, MY_KEY_REFRESH_TOKEN, MY_KEY_SCOPE
Each name must be a POSIX identifier ([A-Za-z_][A-Za-z0-9_]*), at most 8,
and no duplicates. envKey (singular string) is still accepted as a
one-element shorthand and is folded into envKeys on write.
Custom fields are exported too, as <BASE>_<FIELD>.
Upgrading: keys changed from DASHSCOPE_APIKEY to DASHSCOPE_API_KEY in
1.10.64 to match vendor conventions. Set envKey on an entry to pin an exact
name if a script depends on the old spelling.
Tool profiles
The plugin ships 110+ model tools, but registers only 11 core tools by default (vault_list/search/get/add/update/delete/fill/clipboard/totp/generate_password/strength) to keep the model's tool catalog small and cheap.
Switch profiles in Settings → Credentials → Permissions → Model tools — it applies instantly (no restart) and is persisted per vault:
tools: basic|standard|full|custom can also be set in the plugin config; the UI choice wins and is stored in <vault dir>/access.json.
Configuration
[!WARNING] Security: never put the master password in plaintext
A profile patch (cordis.patch.yml / cordis.yml) is a config file that is
easy to commit to git, screenshot, or capture in logs — so a plaintext
masterPassword: row leaks the key to your whole encrypted vault. Always use
masterPasswordEnv and export the real password into the process/shell
environment instead (e.g. export DSH_VAULT_PASSWORD='…'). See the
community-plugin-audit handbook
for the evidence-first checklist for credential-handling plugins.
# ✗ DON'T — plaintext master password in a config file
- id: vault
name: dsh-vault
config:
masterPassword: 'my-secret'
# ✓ DO — reference an environment variable; keep the file itself 0600
- id: vault
name: dsh-vault
config:
masterPasswordEnv: DSH_VAULT_PASSWORD
Option
Description
masterPassword
The master password inline (appears in cordis.yml; not recommended)
masterPasswordEnv
Environment variable name holding the master password (recommended)
path
Vault file path; defaults to $DSH_HOME/vault/default.json
name
Vault name for the default path (e.g. name: work → $DSH_HOME/vault/work.json)
accessMode
Access policy for the model tools. Three states: readonly (mutations rejected on tools + UI), ask (default — reads free, every add/update/delete goes through the harness approval channel so the user confirms each write), or auto (automatic read-write, no per-call prompt). The Settings UI offers this exact three-way choice and persists it to <vault dir>/access.json.
autoCapture
false (default). When true, the system prompt instructs the model to detect credentials shared in conversation and — per user preference — offer to save them with vault_add.
lockTimeoutSeconds
Auto-lock: after this many seconds of inactivity the vault re-locks (key wiped) and every read/write requires vault_unlock. 0/absent disables.
exportPasswordEnv
Environment variable holding the export/import password for vault_export/vault_import (never pass it as a model argument).
backupRetention
How many encrypted backups to keep (default 10); vault_backup prunes older copies.
With autoCapture: true, when you share a credential in chat (e.g. "my npm token is npm_…"), the assistant offers to store it; on your consent it calls vault_add immediately. With autoCapture off, credentials are only saved when you explicitly ask. The Settings UI shows the current mode (read-only / ask-before-write / automatic read-write) with a dropdown to switch it, an auto-capture toggle (detect credentials shared in chat → offer to save), a kind filter, a health & rotation summary, a trash view, and masked secret fields with a Show/Hide toggle.
The vault is created automatically on first tool use; every launch re-unlocks with the master password. Forgetting the master password = permanent data loss (no backdoor — by design).
Development
Clone and develop locally:
git clone git@github.com:Ox0400/dsh-vault.git
cd dsh-vault
pnpm install # installs devDependencies (typescript/tsdown/vitest, …)
pnpm build # builds host lib/*.js and the browser bundle lib/client.js
pnpm test # runs the 420 vitest tests
Tests need harness peer packages such as dsh-llm/dsh-system-prompt; inside the harness monorepo these resolve via workspace links.
All 420 tests pass (crypto / TOTP / password generation / store CRUD / gateway / integration).
Browser checks run against a real dsh web instead of vitest, and every one of
them refuses to operate on the default vault:
node tests/e2e/theme-check.mjs # dual-theme tokens, ring track, attach row
node tests/e2e/polish-check.mjs # empty-state copy, relative-age hover hints
node tests/e2e/contrast-audit.mjs # WCAG sweep over all eight tabs, both themes
See tests/e2e/README.md for the safety rules and for the two traps that make a
contrast audit lie (serialised color(srgb …) values, and opacity counting as
part of the colour).
Keeping credentials out of public content
Everything in this repository is public — the npm tarball, the GitHub releases,
the issue and discussion posts. Before publishing, pnpm scan:secrets checks
the working tree, the tracked files and the commit messages for anything that
looks like a credential, and pnpm scan:public additionally checks every
release and comment already posted. The local half also runs in pnpm test, so
a committed secret fails CI rather than shipping.
Theming
The UI reads the host's design tokens instead of hard-coded colours. A local
semantic layer maps onto the real --dsw-alias-* namespace, with literal
fallbacks so the page still renders outside DeepSeek Harness:
Because the aliases flip with body[data-ds-dark-theme], light and dark follow
the host automatically — there is no second stylesheet and no theme prop.
Two rules the layer exists to enforce:
Never use state-*-primary as body text. Those tokens are tuned for fills
and icons: on white they measure 2.28:1 (success) and 2.15:1 (warn), far below
WCAG AA. The --v-*-text variants mix them towards the theme's own text colour,
so one declaration darkens on light and lightens on dark (light 5.8/5.6/7.5:1,
dark 12+/11+/6.5:1).
Never dim text with opacity.opacity: .8 turns a 5.8:1 colour into
3.9:1. Express "secondary" with a token (--v-text-2), not with transparency.
tests/e2e/contrast-audit.mjs verifies both across every tab in both themes.
Packaging & Publishing
This package is a standard npm bundle:
dsh.bundle.patch → cordis.patch.yml (the layer applied automatically when a profile lists this bundle)
dsh.client → browser-side declaration (exports["./client"] points at lib/client.js)
Vault strength is bounded by master-password strength; use ≥ 16 characters of high entropy.
scrypt cost parameters (N=32768, r=8, p=1) are persisted in the document and can be raised in future versions; old documents remain decryptable.
Plaintext credentials exist only in process memory and during explicit vault_get reads; vault_search/vault_update outputs never contain passwords, keys, or tokens. Secrets returned by vault_get enter that tool call's result (model context) — callers should avoid repeating them in conversation.
This plugin targets single-machine / personal deployments; team-shared vaults are out of scope.
Listed in
awesome-dsh-plugin — official curated directory (Security & Safety section)