Skip to content
| Marketplace
Sign in
Visual Studio Code>Other>CredsForDevsNew to Visual Studio Code? Get it now.
CredsForDevs

CredsForDevs

RemSoftDev

|
19 installs
| (2) | Free
Your coding agent can run the migration — and can never be handed the password. SSH hosts, keys, databases, VPNs, secrets and config files in VS Code, with optional end-to-end encrypted team sync.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

CredsForDevs

Your SSH hosts and keys, VPN configs, database connections, passwords and the CLI commands nobody remembers a week later — in the editor you already have open.

Zero-trust is a property of the design here, not a promise. Secrets live in the OS keychain. Anything that leaves your machine is encrypted in the editor, under a PIN or a security key that never leaves it — so the folder, the NAS or the server holding your vault holds ciphertext and nothing else. Whoever runs that storage, including you, cannot read what is in it.

Creating a profile signs in once with the Microsoft or Google account you already have — that identity is only the profile's name, nothing is stored with them, and there is nothing to sign up for. After that it works with no server and no network: nothing leaves the machine. Point it at a folder to sync your own machines; add the optional self-hosted server when a team needs to share.

Where it runs, and two honest caveats.

The extension is pinned to your local machine ("extensionKind": ["ui"]). Under Remote-SSH, WSL or a Dev Container it therefore keeps running on your own computer, which is the only place ~/.ssh, your keychain and your VPN client actually are. The cost is that Share with Claude Code reaches an agent running on that same machine — an agent running inside a container cannot see the broker's loopback port. Run Claude Code on the client for that feature.

Secrets go to vscode.SecretStorage, which is the OS keychain when there is one. On a Linux box with no reachable Secret Service — headless, a minimal container, a WSL distribution, SSH with no D-Bus — VS Code silently falls back to a basic store that is obfuscated rather than encrypted, and says nothing about it (microsoft/vscode#204552). A session bus is not enough on its own: the store is chosen from the desktop environment before anything is tried, so a session advertising none gets the basic store however much is installed. Install gnome-keyring or kwallet on those machines, make sure it runs in the session VS Code is started from, or treat that vault as unprotected at rest.

Everything it does

SSH in one click The stored password is supplied to ssh through SSH_ASKPASS — never retyped, never on a command line, never in scrollback. A key kept in the vault is written out 0600 for that session and deleted when the terminal closes
SSH keys Store the key pair itself, or point at a file on this machine. One connection can borrow another entity's key. Install SSH Key to System writes the pair into ~/.ssh — dir 0700, private 0600, public 0644
SSH agent, confirming every use Add to SSH Agent serves a stored key from memory over a socket of this window's own, so no key file exists on disk at all — and every single use opens a dialog naming the key and what it is signing: an SSH login as user, or a Git commit. New terminals get SSH_AUTH_SOCK, so ssh and git just find it
Git commit signing Copy Git Signing Config gives you the gpg.format ssh lines with the public key inline, so Git signs commits with a key that lives only in the vault
One-time codes (TOTP) Paste the otpauth:// URI or the base32 secret from any service's "can't scan the QR code?" screen. The viewer shows the live code with a countdown; the tree has Copy One-Time Code. Steam Guard too — so the second app can stay closed
Secret references + masked run Write creds://you@corp.com/prod-db/password where a value would go in a command or a script, then Run with Secrets: the value reaches only the child process's environment, and every appearance of it in what that process prints is replaced with a <CREDS_MASKED:NAME> marker naming which one it was
Generate, don't invent Passwords, passphrases and Ed25519 key pairs, made in the form with the OS's own randomness and reported in bits rather than as a coloured bar. A generated key is saved straight to the keychain — ssh-keygen cannot do that, it writes a file by definition
Import what you already have ~/.ssh/config, and CSV or JSON exports from Bitwarden, 1Password, KeePass, LastPass and Termius. Every skipped row is reported rather than quietly dropped
Health report Reused passwords, weak ones, unencrypted keys in ~/.ssh, plaintext credentials in a workspace .env — all on this machine. An optional breach check sends five characters of a hash and nothing else
Keyboard and orientation Ctrl+Alt+P jumps to any credential by name, plus filter, copy, connect and lock bindings, a walkthrough, and a status-bar item that says whether the vault is open or locked
VPN OpenVPN / WireGuard / IKEv2 / L2TP / other, with host, login, port and a key or certificate. The config file is a secret like any other; Start VPN / Stop VPN bring the tunnel up and down through the OS's own elevation prompt
Databases postgres / mysql / mssql / mongodb, entered as a connection string or as fields that rebuild it in the right dialect. Open in DB Extension hands the connection to Database Client, MongoDB for VS Code or the SQL Server extension
Terminal commands aws sso login --sso-session OD-org is unfindable in shell history a week later, and the part you forgot is never the verb. So each argument is a row with its own note and a tick to keep a flag without using it. Run in Terminal, Copy Command, Show Command and Notes
Plain credentials Anything that is just a login and a password — plus a login and a URL stored encrypted like the password and shown in clear on the card — with notes that live in the keychain rather than in plaintext metadata
Attachments Every entity can carry one encrypted file (PDF, Office, text, archives — the executable family is refused, double extensions included) and one encrypted image, 4 MB each, shown as a zoomable preview
Environment variables Export a secret field into every new integrated terminal under a name you choose. The name syncs; the value is written only from the local keychain, so a binding arriving by sync is a name waiting for a value, never a secret in transit. A ✓? button echoes it in a fresh terminal so you see it
A terminal client creds ssh prod-db -- uname -a from iTerm, Alacritty, tmux — a native binary that holds no secret and can obtain none. It relays the request to the VS Code window that owns the entry, which performs it and returns only the output. Inside WSL it hands the call to the Windows binary through interop, so nothing new listens anywhere and no networking has to be configured
Share with an AI agent Share with Claude Code… lets a coding agent run commands on an SSH host without ever receiving the password or key — see below
Team sharing Send one entity, or a whole folder, sealed to a colleague with a one-time PIN. Create Entity for… authors one directly for someone else
Multi-machine sync One AES-256-GCM file per profile, merged causally (version vectors) rather than overwritten, so two machines editing at once converge on the same answer
Short-lived entries An entry can carry a lifetime: a clock, one agent use, or this window's close. When it ends, the entry is really deleted — tombstone, every stored secret, and its version history — so the deletion travels to your other machines like any other. A "burned" flag that left the secret in place would still be readable from history and would come back on the next sync
Git sync Point an account at a private git repo (git@github.com:me/vault.git) and the encrypted vault is committed and pushed on change, pulled on every cycle — the same causal merge as every other transport. Needs git on PATH. Commit messages carry only an account hash and a time, never anything about what is inside; a reader of the repo can infer when a vault changed, never what
Dated snapshots Separate from sync and deliberately so: sync merges, and a merge propagates a deletion. A snapshot is the copy that still has the thing you deleted
Security keys Open the vault by touching a YubiKey (WebAuthn PRF) — several keys plus the PIN, any of them opens it, adding or removing one never re-encrypts your data
Corporate recovery For a team on a self-hosted server: the operator names recovery officers, and any two of three (configurable) can together open a vault whose owner has left — without the server ever being able to. The organisation's key is split between them at setup and destroyed; each share is sealed under an officer's own PIN or YubiKey. Enrolment is automatic, so the extension tells you it happened, shows you who the officers are, and refuses to seal your key to a fingerprint this machine has not pinned
Recovery code The third way in, for the day the PIN is forgotten and the YubiKey is gone: a 150-bit code shown once, printed, and kept on paper. It opens the vault on its own and then offers you a new PIN. Shown with a Print button and no way to copy — deliberately, because a clipboard is read by managers, sync tools and screenshot pipelines, and this is the factor that has to outlive the laptop. Generating a new one retires the old printout
Auto-lock Locks after an idle window measured in your actions, not mouse movement and not background sync
Masked agent output A secret this extension supplied to run a command cannot come back out in that command's output: the broker replaces it with <CREDS_MASKED:NAME> on the way out. Exact values only — never a guess about what looks like a token, because a wrong guess corrupts the diff or the JSON the agent then acts on. It covers commands run through CredsForDevs, and says so rather than implying it guards every channel. Three limits stated plainly, because a security feature that overstates its reach is worse than one you understand: it does not watch the clipboard continuously (VS Code exposes no clipboard-change event — there are two on-demand commands instead, Check Clipboard for Secrets and Scan File for Vault Secrets), it does not see a file the agent opened by itself, and it cannot help against Windows Clipboard History, which captures at copy time before anything of ours runs
Filter the tree The first row of the sidebar is a search box: type and the tree narrows live, folders holding a hit open themselves, accounts with nothing matching drop out, and the row says how many entries survived. It matches what a row already shows you — name, user, host, port, command — and never a secret: a filter over passwords would confirm one a keystroke at a time to anyone at an unlocked window
Config files as entries appsettings.Development.json or a .env lives in the vault instead of being passed between developers by hand: the body is a secret, validation saves anyway and marks the row until it parses, a Fields tab edits one value without touching your formatting, and Write Config File Here… materialises it to disk — refusing a path git tracks. Enable Code Access… mints a long-lived key (shown once; the vault keeps only its hash) and the viewer answers "how do I read this from code?" in twenty languages, naming the exact file the snippet goes into
Agents over MCP creds-mcp — a separate binary an MCP client starts — shows an agent exactly what you opened to it: two ladders, ten switches — six over entries, four over folders — all off by default and inherited by everything below the folder you set them on. An agent can list, use, rotate (through a {{creds:new}} placeholder, so no value ever enters its context), create into open folders and shape what gets generated (length, character sets, word count), delete to the Trash only, make and rename folders — and ask how code reads a config (creds_config_snippet, the same twenty-language catalog the viewer renders). An action raises the consent modal as often as the entry says — every time, once every 12 hours, or never, with creating and deleting always asking; MCP logs is the journal either way
Capability filters The tree filter understands what an entry CAN DO, not just what is written on it: has:totp, has:cli, has:env, has:code-access, has:deps, has:attachment, has:image, is:ephemeral, and mcp:visible / usable / rotate / create / delete-own / delete-any — combinable with free text (aws has:totp mcp:usable). Predicates read metadata only; an unrecognised one is named on the search row rather than silently matched as text
A missing tool becomes an offer Connect over SSH on a machine with no ssh client, or start a VPN with no WireGuard, and instead of a dead terminal you get a modal naming what is missing and — on Yes — a visible terminal running the platform's own install recipe
Remote Bridge On a Remote-SSH host the local window is unreachable by definition; Open Remote Bridge… holds an ssh -R tunnel so creds on the host reaches the window that holds the vault. Install creds on the Host… puts the binary there from the entity's own context menu
Inside WSL creds hands calls to the Windows binary through interop — nothing new listens anywhere. Set Up the WSL Agent Relay serves the SSH agent on a unix socket inside the distribution, so ssh and git in WSL can use vault keys with a confirmation dialog per signature
Text zoom ± buttons on every CredsForDevs page scale the text up to five steps either way; the offset is shown (+3), stored in a synced setting, and every open page follows it at once
Diagnostics you can attach Show Diagnostics opens the one output channel and names the per-run log file on disk — sync, backup, transport and unlock failures land there, and no secret can, by construction
Several accounts Microsoft and Google profiles side by side, each with its own tree, its own vault location, and its own team — separated by a blank row, each account row counting entries / in the Trash / shared

The repository page carries the security model, the broker's design and every security review this code has been through.

The tree

Activity Bar → key icon. Top-level items are account profiles added via the VS Code Authentication API (Microsoft is built-in; Google via the extension's own OAuth provider — see below). Inside a profile: folders and entities.

  • Single click on an entity selects it and shows it in the read-only viewer — in one shared preview tab that the next single click reuses, the way the editor previews files: ten clicks on ten entries leave one tab. The viewer shows only the fields that actually hold a value, each with a copy-icon button (secrets stay masked; copying goes through SecretStorage, never through the page), a download icon on the VPN config row (Save As), plus a kind-aware Copy All.
  • Double click pins that preview into a tab of its own (and opens one when nothing is previewed), so two entries can be compared side by side. The twisty of a row with history or dependants is the workbench's: a double click still toggles it, as it does in every tree.
  • Green ▶ (hover, SSH entities) connects SSH; green database icon (DB entities) opens the entity in a DB extension. Nothing ever runs on a plain click.
  • Right-click menus are capability-filtered: an entry only offers what it can actually do (Connect SSH/Toggle SSH need a host, Copy Password needs a stored password, key/VPN/DB actions need those kinds).
  • Move items between folders by drag & drop or Move to Folder… (within one profile). The toolbar + buttons create at the profile root; the inline + on an account/folder row creates inside that row.

Folders: types and ordering

  • Default folders for a new account: the first time you add an account, and only when it starts out empty, it is seeded with five typed folders — db (Database), vpn (VPN), ssh keys (SSH key), ssh connections (SSH connection) and passwords (Credential). This is one-time: if the account already has data (e.g. a returning user whose vault is pulled from the NAS first), or once you rename or delete the defaults, they are never re-created.
  • Creating a folder asks for its content type — Credential (default), SSH connection, SSH key, VPN, Database, or Any type; right-click → Change Folder Type… updates it later. The folder row shows the type's icon (lock/remote/key/shield/database) and name.
  • A typed folder holds only its own kind: entities created inside it get the form's Type selector preset and locked, and moving/dragging a mismatched entity in is blocked with a warning. Existing entities are never retro-converted.
  • Manual ordering: right-click a folder → Move Up / Move Down. The order persists and syncs across machines; untouched folders stay alphabetical after the manually placed ones. Entities stay alphabetical.

Entities

Add Entity / Edit opens a single webview form with a Type selector — Credential (default) / SSH connection / SSH key / VPN / Database / Terminal command / Script — and only the chosen kind's section is shown. Saving scrubs the other kinds' fields, so switching type leaves no stale data. Inside a typed folder the selector is preset and locked. Validation errors render inline, and any script failure is printed into the form's error area (never a silently dead form).

  • Password / secret value → context.secrets (OS keychain), key ${accountId}_${entityId}. Never in globalState, settings, or logs.
  • SSH private key content → SecretStorage (${accountId}_${entityId}:sshPrivateKey); public key → metadata.
  • SSH key path — alternative to content: a pointer to a key file on this machine's disk (the path syncs, the file does not).
  • SSH key source — an SSH connection can reference another entity as its key. Resolution order when connecting: referenced entity → stored key content (materialized to a 0600 file under extension storage) → plain key path.
  • Install SSH Key to System (entities flagged as SSH keys): writes the pair into ~/.ssh — dir 0700, private 0600, public .pub 0644 — with an overwrite confirmation.
  • One encrypted file and one encrypted image, on an entity of any kind. Additional file takes what people actually attach — PDF, Office, text, data, archives — and refuses the executable family outright, including as the tail of a double extension (invoice.pdf.exe). Both are capped at 4 MB, checked before anything is stored, and both live where every other secret lives: the OS keychain locally, the sealed vault in transit — sync, backups and snapshots carry them like passwords. In the viewer a stored file is a row with a save button and a stored image is a 200×200 preview (click to zoom ×2, twice; a third click resets). Only the file name is plaintext metadata; the content never is.

In edit mode stored secrets are never shown or sent into the webview: leaving a secret field empty keeps the current value; explicit "clear" checkboxes remove it.

VPN entities

Pick the VPN type in the form: choose the protocol (openvpn / wireguard / ikev2 / l2tp / other) and upload the config file (.ovpn / .conf — the picker runs in the webview, so it browses the client OS's files, i.e. Windows even under WSL). The config content is a secret: SecretStorage locally, inside the AES-256-GCM payload on the NAS — never plaintext in a backup; only the original filename is metadata. VPN entities show a shield icon; Save VPN Config As… writes the decrypted file back out for your VPN client; the viewer shows the type and a masked, copyable config.

A VPN entity also carries host / gateway, login, port, and a key or certificate — the key goes to the OS keychain like an SSH private key; host, login and port travel only inside the encrypted vault. The viewer shows exactly the fields that are filled; an empty one adds no row.

Start VPN / Stop VPN bring the tunnel up and down. The config is materialized to a 0600 file, the launcher is located on this machine (wg-quick, wireguard.exe, openvpn, or the OpenVPN Connect GUI on Windows — recognised as the different product it is, rather than reported as a missing binary), and the composed line is shown in a terminal rather than run silently. The extension never elevates itself: Windows raises its own UAC prompt, POSIX its own sudo password prompt. That dialog is the trust boundary, and it should be. The terminal it opens runs this machine's own shell (PowerShell on Windows, bash elsewhere) whatever your default terminal is — a WSL-bash default no longer receives a PowerShell line.

Started by lets one of your own Terminal entries start the VPN instead of the built-in launcher: its command runs with {config} replaced by the path of the stored config. That makes any VPN startable from the tree — IKEv2 through rasdial, a vendor's own client, OpenVPN from a path nobody probes. Stop explains where to stop it; the extension does not guess a process.

Database entities

Pick the Database type in the form: choose postgres / mysql / mssql / mongodb and fill either the connection string or the component fields (Host / Port / Database / User / Password) — they are two-way linked: typing the string fills the fields, editing a field rebuilds the string in the right dialect (URI for postgres/mysql/mongodb, Server=…;Database=… for mssql). The port is optional with per-type default placeholders (5432/3306/1433/27017); a host pasted with a scheme (http://…) is auto-cleaned. The string is the single stored secret — prefilled and editable in Edit mode (the one deliberate secret-in-webview exception); emptying it clears it. The viewer shows the parsed parts with copy buttons (password masked, default port labeled), and Copy All emits type, string, and all parts. The inline green database button = Connect opens the entity in a dedicated DB extension:

  1. Target resolution: your override in credSshManager.dbExtensions (e.g. { "mysql": "cweijan.vscode-mysql-client2" }) → the first installed candidate → the recommended default, offered with a one-click Install when missing.
  2. Launch: the connection string is copied to the clipboard, the target is activated, and its add-connection flow opens — Database Client (mysql.connection.add; flip its "Use Connection String" toggle and paste, all fields fill at once), MongoDB for VS Code (mdb.connectWithURI, takes the URI directly), SQL Server (mssql.addObjectExplorer); for other extensions the command is discovered from their own manifest. The post-open notification carries the exact paste instruction per target.
  3. Honest limitation (verified against Database Client's source: no exported API, no settings store, no URI handler): extensions without a public connection API get the open-form-plus-one-paste treatment — we never write into another extension's private storage.

Terminal command entities

Pick the Terminal command type in the form. The case it exists for: aws sso login --sso-session OD-org is unfindable in shell history a week later, and the part you have forgotten is never the verb — it is which value belongs to which environment, and why.

So an argument is a row, not a word inside a string: each has its own value, its own explanation underneath it, and a tick that keeps a flag without using it (--debug is what you want back next week; deleting it means retyping it from memory). Rows can be added, removed and reordered, and a live preview shows exactly what will run.

  • Run in Terminal (the same green ▶ as Connect SSH) runs the assembled line.
  • Copy Command for the times you want to edit before running; Show Command and Notes prints the line with every argument's explanation.
  • The notes can fill themselves in. With credSshManager.readCliHelp on (default), pasting a whole command reads what each flag means by running <tool> --help. It runs only the tool you just typed, with no shell and none of our own arguments, and only when every word of the command is a plain tool name — anything carrying a shell metacharacter is refused rather than probed.
  • Runs on (Windows / macOS / Linux) says which system the command is written for. Your default terminal runs it when it is a shell of that system — pwsh 7, zsh with your aliases — and that system's own shell does otherwise; on another system it is refused. Not set keeps your default terminal, exactly as before.

Execute what is executable first (in Depends on) turns an entry's dependencies into a chain: before Run in Terminal, Run Script, Run with Secrets or Start VPN, its Terminal dependencies run in order, each one awaited — "install openvpn", then "start openvpn", then the VPN. You read the chain before its first run; a step that fails asks whether to go on; a circular chain is refused. Waiting for a step needs VS Code 1.93 or newer.

Environment variables in the terminal

Each secret field — password, private key, public key, connection string, DB password — has a toggle in Edit (off by default). Switching it on mints a name from the entity (entity git key, private key → ENV_GITKEY_PRIVATEKEY); edit it if you want another. Saving writes the value into every new integrated terminal, persistently across reloads.

What syncs is the name — a name is not a secret. The value is written only on the machine that saved or pressed the button, from that machine's own keychain, so a binding arriving by sync is a name waiting for a value, never a secret that travelled. Renaming or disabling a binding deletes the old variable on save rather than leaving it set forever.

The viewer shows the variable's name with a copy button, a Set button that re-writes the value on demand (the collection can be lost with extension storage, and recovering must not require re-saving the entity), and a ✓? button that opens a fresh terminal and echoes the variable — so it is seen rather than trusted from a notification. The probe's spelling follows the actual default shell, not the OS: PowerShell gets $env:NAME, cmd %NAME%, bash $NAME. Fair warning it carries: echoing prints the secret into that terminal's scrollback.

The SSH agent — a key that is never a file

Add to SSH Agent on a stored SSH key serves it from this window's memory over a socket of its own (a named pipe on Windows). SSH_AUTH_SOCK is set in every integrated terminal opened afterwards, so ssh and git find it with no configuration — and the 0600 file the extension used to write for ssh -i stops existing. Connect SSH on an agent-served key passes no -i.

Every single use asks. The dialog names the key, its fingerprint, what is being signed — an SSH login as a particular user, or a Git commit signature — and when it was asked (Requested 2026-09-24 12:30:00 (UTC+03:00).), because "a key is being used" is not something anyone can decide about. Unanswered for five minutes it refuses — the same bound as the agent consent dialog — and a click after that signs nothing. Three answers: Allow once, Allow for 10 minutes (a git push signs and authenticates in one breath, and two modals per push is how people learn to click without reading), or Deny. Every request is a line in CredsForDevs: SSH Agent.

Git commit signing. Copy Git Signing Config puts the gpg.format ssh lines on the clipboard with the public key inline (user.signingkey "key::ssh-ed25519 AAAA…"), so Git signs with a key that exists in no file. Add the same public key to GitHub/GitLab as a signing key — forges keep those separate from authentication keys.

Honest boundaries:

  • A passphrase-protected key is refused, with the ssh-keygen -p -N "" command that fixes it. OpenSSH encrypts with bcrypt_pbkdf and Node has no implementation of it; the vault already encrypts the key at rest, so storing it unencrypted here loses nothing.
  • On Windows use the built-in OpenSSH (C:\Windows\System32\OpenSSH). The ssh that ships with Git for Windows is an MSYS build that cannot connect to a named pipe — the signing config sets gpg.ssh.program accordingly, and this is measured, not assumed.
  • Keys are held in memory only. Closing the window revokes everything, which is the whole revocation story — the same as it is for agent grants.
  • The agent is read-only: it lists keys and signs. A client cannot add, remove or lock anything.

One-time codes (TOTP)

Paste an otpauth:// URI — or the bare base32 secret — from the service's "can't scan the QR code?" screen into the One-time code field. The viewer then shows the current code with a countdown next to it, the tree offers Copy One-Time Code, and the copy expires from the clipboard like every other secret here. Steam Guard's five-character variant is a checkbox.

The seed is a secret like a password: it lives in the OS keychain, rides the same encrypted envelope in sync, backups and shares, and — the part worth stating — the webview never receives it. What the panel is sent is the six digits, which stop being true in under a minute anyway.

Secret references and Run with Secrets

A script's variables never enter its text; they travel in the environment. What that could never stop is the script printing them itself. This closes that, and extends it to commands:

creds://you@corp.com/prod-db/password

Write that reference as a script variable's value or a command argument, and Run with Secrets:

  • resolves it from your vault at the moment of running, into the child process's environment and nowhere else — the argument on the command line becomes "$CREDS_REF_1", so the value is not in the process list where every user on the machine can read it;
  • runs it in a terminal this extension owns, and replaces every appearance of the value in what the process prints with <CREDS_MASKED:NAME>, naming which secret stood there — including a value split across two writes.

Reference a folder path (creds://you@corp.com/Servers/EU/gateway/privateKey) when two entities share a name: a reference that could mean two things is refused, naming both, rather than quietly picking one. Fields: password, privateKey, publicKey, dbConnection, dbPassword, notes, totp.

What it cannot do, so you meet it here rather than in a bug: the terminal it runs in has no PTY. A program that prompts interactively, draws a progress bar, or colours its output by detecting a terminal will behave as it does when piped — use Run in Terminal for those. And masking is textual: a program that base64s a value before printing it defeats it, which is why a script that prints its own variables is still flagged when you save it.

Generating a secret instead of inventing one

The Secret section of the form has Generate password and Generate passphrase; the SSH key section has Generate Ed25519 key pair. CredsForDevs: Generate Password or Passphrase… does the same for a password that is not stored here at all, and puts it on the expiring clipboard.

  • A password is 20 characters over all four sets by default, with one character guaranteed from each selected set and the whole thing shuffled — so it satisfies a site's composition rule without the rule being visible in the result.
  • A passphrase is six words from a 256-word list: exactly eight bits each, 48 bits for the default. The optional capital and trailing digit exist for sites that demand them and are not counted as strength, because a number that flatters is a number you cannot use.
  • Ask for a word length and the source becomes a longer list, filtered — and the strength is recomputed from the pool that is actually left. A filter that shrinks the source and goes on reporting the old number would be a lie about a password.
  • A seed phrase is drawn with the wordlist and the word count you choose, with the checksum computed for you, so what comes out is a phrase a wallet will accept. Word length is deliberately NOT offered there: on a BIP-39 list the lengths belong to the list, and filtering them produces something no wallet takes.
  • The strength is stated in bits, not as a colour. A bar tells you how a designer felt; the bits tell you how long a guess takes.
  • A generated key pair is drawn in the editor and saved to the keychain. ssh-keygen cannot do that — it writes a file — and with Add to SSH Agent the key is then used without becoming one.

A PIN on one entry — a second lock inside the vault

The vault is one lock: everything inside it opens together, which is what makes it usable, and it means an open vault is a readable vault. For the few things that deserve a second lock — the production database, the payment card, the key that signs releases — right-click the entry and choose Protect with a PIN.

Every secret that entry holds is re-encrypted under a PIN of your own: the password, the private key, the notes, the login and URL, the payment details, the config body, the connection string, the VPN configuration, the one-time-code seed. A fresh random key seals the value, the PIN seals that key, and the plaintext is nowhere. This is a real re-encryption, not a prompt in front of a value that is already readable.

  • Opening the entry asks once. The PIN opens the whole entry for as long as that window lives; closing it, reloading it or locking the vault forgets it. It is written down nowhere.
  • Agents do not see it at all — absent from the listing and the search, and a direct request by id is answered the way a made-up id is answered. An agent that can see an entry it can never open will keep asking.
  • Nothing automatic uses it. An environment variable, a terminal, the SSH broker, a creds:// reference and an agent rotation each say why instead of guessing.
  • The health report skips it and says so, because ciphertext always grades as a strong unique password and grading it would tell you your weakest habit is fine.
  • A backup keeps the protection — what is exported is what is stored, still wrapped.

Protect Every Entry with a PIN on a folder does the same to every entry inside it, at any depth. It does not encrypt the folder — a folder is a place, and the protection belongs to the entries. Entries that already have a PIN of their own are named before the run starts and then skipped: a folder may legitimately hold entries under two PINs, and when it already holds protected ones the PIN you type is checked against them and you are told how many it opens before anything is written.

Sharing one asks you for its PIN: the values have to be unwrapped before they can travel, because the person receiving them does not have your PIN and never will. Their copy is therefore not protected by yours — they are asked to choose one of their own when they accept it, and declining imports nothing. Your PIN never leaves this machine.

Running the folder command also sets that folder to ask for a PIN on every entry created in it afterwards, including when the folder was empty at the time — the one case nothing else could tell. Stop Asking for a PIN Here turns that back off; it changes nothing about the entries, which keep their own PINs.

Remove PIN Protection takes it back off, given the PIN. And the one thing to read twice: a forgotten PIN is gone data. There is no recovery — the vault recovery code opens the VAULT, not an entry.

A password stored woven with a decoy

The General section of a credential has Store this password woven with a decoy. Tick it, pick one of the twelve methods — the same twelve a card's PIN uses, offered in an order drawn afresh every time — and what goes into the keychain is your password and a made-up one of the same shape, interleaved.

The method is written down nowhere. Opening the entry shows two rows of characters with nothing marking either one; you pick the method you chose and read the row you recognise. A wrong method answers in exactly the same shape as a right one, which is the point rather than an omission: the choice in your memory is the only thing separating the two halves.

  • The decoy has the same length and the same character sets as the real password. A character from a set your password does not use would be provably decoy.
  • The entry says Woven — on on the edit form and in the viewer. What cannot be undone is the unweaving — that needs the method. Replacing the password always works, and the box arrives already ticked, so a replacement stays protected unless you untick it on purpose.
  • Nothing automatic will use it. An environment variable, a terminal, the SSH broker, a creds:// reference and an agent rotation are each given a sentence saying why instead of a guess — a wrong password injected into a login is an account lockout nobody watches happen.
  • A share carries the mark, so whoever you share with is told the value is woven rather than opening an entry full of gibberish with nothing explaining it.
  • Too short to weave, or no method picked, and the form asks before it saves rather than quietly storing the password in the clear.

Payment instruments — and reading one back

A card, a set of bank details, or a phrase you must not lose.

  • The card number is shown in the groups it is printed in — fours, or 4-6-5 for American Express — and stored as digits, because a woven number is permuted character by character and a stored space could never be unwoven. Two copy buttons: digits for a form that refuses spaces, groups of four for reading it aloud.
  • The payment system is detected from the number and is yours to correct. It has to be: a number stored woven with a decoy has no first digits left to read it from.
  • The billing address is six cells — paste a whole one and it is split into them, where every guess is visible and correctable. The block underneath is assembled in the order the destination country writes it, and that block is what a share and an export carry.
  • A CVV or PIN asks before it appears, and can be put away again — the button becomes Hide. Copying it asks nothing: Copy and Show are separate buttons, and only Show asks.
  • Any of the number, CVV, PIN, IBAN or account number can be stored woven with a decoy under one of twelve methods, and the controls now SHOW what a method does: a green column, an orange one, and the weave of the two. Both are made up for the picture; your own value is never drawn beside the decoy it is woven with.
  • A method is called the same thing everywhere. The order the twelve are offered in is drawn afresh each time, so a position never becomes a habit — but "Method 5" names one algorithm, in the form where you weave a value and in the card where you read it back.

Import what you already have

Right-click an account → Import from ~/.ssh/config or another manager…

From What comes across
~/.ssh/config Host, HostName, User, Port, IdentityFile — as connectable SSH entries
Bitwarden, KeePass, LastPass, Termius (CSV) name, username, password, address, notes, TOTP, folder
Bitwarden, 1Password (JSON) the same, from login items

The file's content decides how it is read, so a misnamed export still imports. Nothing lands before you have seen the count and what will be skipped — and skipped rows are listed with the reason, never dropped in silence. Everything gets a fresh id, so an import can never overwrite what you already had.

KDBX (KeePass's own database) is not read, deliberately: it is Argon2-encrypted and Argon2 is not in Node, so a half-right implementation would be worse than none. KeePass exports CSV, which imports fine.

The health report

CredsForDevs: Health Report looks at what you have and says what is wrong with it:

  • passwords used in more than one entry — the finding you cannot see by eye, and the one that turns a single breach into several;
  • passwords under 60 bits;
  • private keys in ~/.ssh with no passphrase — reported as medium, because that is the normal state of a key on a machine only you use, and a report that calls everything a catastrophe is one nobody reads twice;
  • plaintext credentials in a workspace .env, with the creds:// reference that fixes them.

All of it runs here. The one exception is opt-in: with credSshManager.breachCheck on, the report can ask Have I Been Pwned whether a password appears in public breach corpora — and it asks you again, each run, before it does. What is sent is the first five characters of the password's SHA-1: one bucket out of a million, shared by hundreds of thousands of passwords. The whole bucket comes back and the match happens on your machine, so the service cannot tell which password was asked about. The password never leaves.

No finding ever quotes the value that caused it — the report is a document you can paste into a bug without pasting a password into it.

Script entities

A script is the sibling of a terminal command: the same "I will never remember this next month" problem, one size larger.

Know where the body lives. A script and its variables are stored as entity metadata, not in the OS keychain — like a terminal command's arguments and unlike a password. Locally that means globalState, in plaintext; in transit and at rest on your NAS or vault server it is inside the same AES-256-GCM envelope as everything else. So a script is safe to sync and safe to share, and it is not the place to paste a token: put the token in a credential entity and let the script read it from an environment binding.

  • Language decides both the highlighting in the form and whether Run Script is offered at all. Only a language with an unambiguous interpreter can run:

    Language Runs with File
    Bash bash (on Windows: the one git-bash ships) .sh
    PowerShell powershell -ExecutionPolicy Bypass -File on Windows, pwsh -File elsewhere .ps1
    Python python .py
    JavaScript node .js

    SQL, YAML, JSON, Dockerfile and other are stored, highlighted and copyable, but not runnable — SQL needs a database and a data format is not a program. Run Script refuses those with the reason rather than piping them into a shell.

  • Variables are rows, exactly like a terminal command's arguments: a name, a value and a tick to keep one without using it. The values never enter the script text. They are handed to the run through the process environment, and the body reads them by name in its own language's syntax — bash needs no change at all (${NAME} already is that), PowerShell gets $env:NAME, Python os.environ.get('NAME', '') with the import os added only when something was actually translated, JavaScript process.env.NAME. The file on disk, the viewer and Copy All carry names where they used to carry values.

    Two consequences worth knowing. A script runs in a fresh terminal every time, because VS Code can only set a terminal's environment when it is created — reusing one would run the script with the previous entry's values. And a script can still print its own variables: env injection stops the value leaking into the file, not into anything the script chooses to echo, so an entry whose body does that is flagged rather than silently trusted.

  • Where it runs from. The file is written into the extension's own private storage (keys/, dir 0700, file 0600, owner-only ACL on Windows) — the same directory materialized SSH keys use, and purged on both activate and deactivate. Nothing of it outlives the session.

Config file entities

A config entry holds a whole configuration file — the appsettings.Development.json that must never reach git and was being passed between developers by hand. The body is a secret like a password: never in plain metadata, never in a share's label, never handed to an agent.

  • Raw and Fields. The Raw tab is the file, highlighted as you type in its own format (JSON, YAML, TOML, INI, .env). The Fields tab is a view over the same text: it splices one value's span, so it structurally cannot lose your indentation or comments. A body that does not parse is still saved — the row is marked until it does, because losing a paste is worse than holding a draft.
  • Write Config File Here… materialises the body into the workspace, refusing a path git tracks and asking about one it does not ignore. Show What Changed is a key-level diff against the file on disk, so a colleague's edit is readable before it is taken.
  • Enable Code Access… mints a long-lived key for applications: shown once, copied to the clipboard, and the vault keeps only a SHA-256 of it. The app reads the config through POST /v1/config/read or creds config <key>; the key travels in CREDSFORDEVS_KEY. Revoke Code Access… retires it. The viewer's Read this from code panel gives the exact snippet in twenty languages — and names the file it goes into ("Program.cs, before builder.Build().").
  • Sharing carries the document. A shared config arrives with its body; the code-access key deliberately does not travel — it is minted per vault, and the recipient mints their own.

Export / Import externally

Share with… is for people on your team — it needs them to have the extension, an account and a vault. Export / Share Externally… is for everyone else: a contractor, a client, a colleague who has not installed anything yet.

  • Right-click an entity to export it, or a folder to export its whole subtree. Secrets travel with it — password, private key, VPN config, connection string, notes, the attachment and the image.
  • An entry marked Not for export (Edit → General) never travels this way or through Share with…: on its own it is refused, inside a folder it is left out and named. Agents, backup and sync are unaffected.
  • You are asked for a password. The file is then sealed with the same envelope the vault itself uses (scrypt + AES-256-GCM), so what lands on disk is ciphertext and the password is what opens it. Choosing to export plain JSON is offered and is exactly what it says — nothing protects it; send that over nothing you would not shout across a room.
  • Import from External… takes such a file, asks for the password if it is sealed, and gives every imported node a new id. The sender's ids belong to the sender's tree; colliding with your own would corrupt the next sync merge.

Sharing (Team / Shared with me / Create for…)

  • Team is account-scoped: every account row carries its own Team subtree — the people discovered on THAT account's NAS folder (owners of vault_*.enc there; people appear after their first sync), you marked "(you)". Two companies on two NAS folders = two separate teams that never see each other.
  • Share with… (context menu on an entity or a folder — a folder shares every entity in its subtree, one item each, preserving the folder chain + types on the recipient's side): pick recipients (only the sending account's team is offered), enter a one-time share PIN — each entity (metadata + all its secrets) is sealed with scrypt(recipientAccountId + PIN) and appended as a plaintext-array item into the recipient's vault envelope (their encrypted payload is untouched; only name/kind/sender are visible). Tell them the PIN out-of-band.
  • Shared with me (appears when something is pending; aggregates all your accounts): grouped by sender. Accept (inline ✓, enter PIN) imports the entity into the addressed account's vault (same id → re-shares update the copy) and removes the item; Decline (inline ✗, confirmed) removes without importing. Accept all (per sender or global) tries known PINs on everything and asks a new PIN for the first item that resists, round by round.
  • Create Entity for… (Team context menu): author an entity in the normal form directly for someone else, sent from the account whose Team you clicked — after a successful share nothing remains in your own storage.
  • Sender identity is signed (Ed25519, since 0.45) — see Sender signatures below for what that proves and what it does not.
  • Honest note: a share is a copy. There is no remote revoke; a recipient who has accepted it holds it.

Share with Claude Code — an agent that uses a credential it never receives

An AI coding agent needs your server. Pasting the password into its chat puts the plaintext in a transcript and in every log downstream of it; exporting it to a file is no better.

Right-click any entity an agent can do something with → Share with Claude Code… — SSH hosts, scripts, terminal commands, databases, VPN tunnels, and bare credentials. A capability token is minted and a paste-ready snippet lands on your clipboard. Give it to the agent, and it can:

node "<extension>/dist/agentCli.js" ssh <token> -- systemctl status nginx   # runs it, returns stdout/stderr/exit code
node "<extension>/dist/agentCli.js" terminal <token>                        # asks for the interactive terminal, for you
node "<extension>/dist/agentCli.js" db <token> -- "select count(*) from orders"
node "<extension>/dist/agentCli.js" script <token>     # runs your stored script, exactly as saved
node "<extension>/dist/agentCli.js" run <token>        # runs your stored command, exactly as saved
node "<extension>/dist/agentCli.js" env <token>        # exports the secret into new terminals; returns NAMES
node "<extension>/dist/agentCli.js" vpn-up <token>     # opens the tunnel; you answer the elevation prompt

Two of these deliberately ignore whatever the agent sends: script and run execute exactly what you saved, so no agent-authored text ever reaches an interpreter or a shell. They also require that you have run the entry yourself once on this machine — the broker's Allow covers a token, not a body, and a body can be replaced by a sync after you clicked it.

MongoDB is refused, on purpose. mongosh has no password environment variable and its --eval runs in the same JavaScript interpreter that can read process.env — so a "query" could print the password straight back. No SQL client has that channel. A capability that leaks by design is worse than one that is absent, so this one says no.

SSH keys are excluded for a duller reason: a key means nothing except attached to a host, and the host entry already has exec.

What the agent never gets is the secret. ssh is spawned by the extension — the half that already holds the credential — and the password rides that child process's environment through the same SSH_ASKPASS machinery a human Connect uses. The broker has no endpoint that returns plaintext, and this is structural rather than a promise in a comment: no response type in the protocol has a field a secret could ride in.

  • The token is a capability, not a credential. It reaches exactly one entity, and it carries the broker's loopback port, so the CLI dials the exact window that minted it.
  • It dies with the window. Grants live in memory only; closing or reloading VS Code revokes every one of them. That is the whole revocation story — there is nothing to expire and nothing left on disk.
  • First use asks. A modal names the entity and the exact command about to run. Afterwards that grant runs silently, but every call — allowed, denied or refused — is a line in the CredsForDevs: Agent Access output channel. A dismissed dialog is not remembered as a refusal: a missed notification must not lock an agent out for the window's life.
  • Bounded by construction: the loopback server binds 127.0.0.1 only, a bearer token authorizes every call, output is capped and truncated rather than buffered without limit, a hung command is killed at its ceiling, and eight execs may run at once — a runaway agent loop cannot fork-bomb the machine.
  • Auto-lock is not fooled by it. Your click on Allow counts as presence; the agent's calls count as nothing, for the same reason a background sync never postponed the lock.

Honest about the boundary: an agent that can run commands on a host can do anything that host lets it do. What this removes is the plaintext credential, not the access — the access is the point.

Agents over MCP — creds-mcp

Share with Claude Code… (above) opens ONE entry to one agent for one task. The MCP server is the standing version: Install the MCP Server… puts a separate creds-mcp binary on PATH and writes the client config; the extension itself keeps its zero runtime dependencies.

  • Two ladders, ten switches, all off by default. Over ENTRIES: visible / usable / may replace the secret / may create / may delete what it created / may delete anything here. Over FOLDERS: may create folders / may rename and move them / may delete the ones it made / may delete any. Set them in the Agent access section of an entry or a folder; everything below a folder inherits them until something inside gives its own answer, and an empty object is an answer — that is how you close one branch of an open tree. The tree marks an opened entry with a pentagon whose edges light per switch of the entry ladder. Nothing in the Trash answers, whatever its switches say.
  • An agent can never change a switch. Not on an entry, not on a folder, not by any route: there is no field in any request it can make that carries them. Folder editing means the name, the place and the type. A move needs the grant at both ends, because a folder passes its answers to everything inside it — so moving one is a permission change for its contents.
  • The switch is not consent. It says an agent may ask. Whether the modal appears — with the real entry, the real command and when it was asked (Requested 2026-09-23 14:05:12 (UTC+03:00).) — is that entry's consent setting: every time, once every 12 hours, or never.
  • How often it asks is yours to set, on the entry or on a folder it inherits from: ask every time (the default), ask once every 12 hours, or never ask. It covers using an entry only — creating and deleting always ask, whatever it says — and never-ask makes the switches the whole gate, which the form says in those words beside the option. The twelve-hour window is remembered on this machine alone and is never synced or shared; Forget Agent Consents on This Machine takes every such window back at once. Every call is recorded either way, in ⋯ → MCP logs.
  • The agent may shape a generated secret, never see it. length, the four character sets and avoidAmbiguous for a password; words and separator for a passphrase. It matters because the alternative to "this system caps passwords at 16" is an agent generating the value itself, and then the secret is in its context. Asking for a password with no character sets is refused rather than drawn — the generator answers with an empty string, and an empty secret stored as if it were one is a working-looking entry that is not.
  • Rotation without disclosure: an agent writes {{creds:new}}, the extension generates the value, and neither the old nor the new secret ever enters the agent's context.
  • creds_config_snippet answers "how does code read this config?" from the same catalog the viewer renders — languages first, then the snippet, its target file, and the CREDSFORDEVS_KEY environment variable. The listing's codeAccessEnabled tells the agent whether the key exists; minting one is yours alone.
  • MCP logs (the … menu) is the journal: every ask, every refusal, and the two costs worth counting — secrets that came FROM an agent, and requests we could not serve. Show Entry by id… jumps from a journal line to the entry it names.
  • It works from inside WSL, where the agent usually lives. The window is on Windows and 127.0.0.1 inside a distribution is that virtual machine's loopback, so the Linux creds-mcp hands the whole session to creds-mcp.exe through WSL interop and carries its stdio — nothing new listens anywhere, and the consent modal still appears on Windows. Install the MCP Server… asks where the agent runs and installs both halves: the Linux one into the distribution you pick, and a config block naming it, with the Windows binary's path in env — asked of the distribution through wslpath rather than composed here.

Help — the yellow question mark

The question mark in the view's title bar opens the built-in help: every feature as an article in one fixed shape — what it is, why, how to set it up, how to use it, what can go wrong — with the least self-explanatory ones first (MCP logs, Install…), a search box, breadcrumbs and Back, and a language switch (English, Russian, Ukrainian, German, Spanish) that remembers your choice for the help pages only. Pictures come later; the slots are there.

Selecting several at once

Ctrl-click or Shift-click in the tree, then Delete, Export / Share Externally… or Share with… — one confirmation, one recipient pick, one PIN, one file, however many rows are selected. Everything else still acts on the row you clicked.

Rows that cannot take part are left out and named: an account row, a team member, an inbox item. Rows from a different profile are left out too — ctrl-clicking across two account roots is an ordinary gesture, so it is reported rather than refused. And a folder quietly swallows anything of its own you also selected, at any depth, because deleting or exporting the folder already covers it.

Dates and history

Every entry carries when it was created and when it last changed, shown in both the viewer and the edit form. The creation date is stamped once and never moves again, so it survives every later edit; entries made before this feature existed say the date is unknown rather than inventing one.

The last 3 versions of an entry are kept. An entry with history wears a blue-tinted icon, so "this has been changed" is visible in the tree rather than only after opening it, and the viewer lists each kept version — when it was replaced, what it was called then, and a button to copy that version's secret.

The versions are rows in the tree: open the entry's twisty and they are underneath, newest first, labelled by when each was replaced. One click on a version opens the viewer on that version — every copy button reads the old value; a version of an entry protected with its own PIN opens only after that PIN. Right-click → Restore This Version… brings the entry back to it: what the entry holds now becomes its newest previous version, so a restore is undone the same way, and its agent access and code-access key stay today's. Run and Copy Command work on an old version of a command, and Clone makes a new entry from one. Nothing else is offered on a version: no Edit, no Share, and no writing its secret into a terminal variable. A clone never carries history — a new id starts with an empty past, as it starts with no secrets.

Two limits, so they are not discovered the hard way: a kept version does not include attachments (three copies of a 4 MB file per entry would cost more than the history is worth), and history is local to the machine — it is not part of the encrypted vault that syncs, so another machine keeps its own. And one fact worth knowing before you rely on it: history means a replaced password stays retrievable. That is the point of it, and it is why the kept versions live in the same encrypted store as the current ones.

When somebody re-shares the same thing

A colleague sends you the password for an account. Six months later they change it and send it again. Rather than a second copy appearing beside the first with nothing saying which is current, the second one asks: Update it — in place, keeping its folder, its identity and its history — or Keep both.

Dismissing that question leaves the item in "Shared with me." Deciding usually needs a look at what you already have, and consuming the share to ask you would destroy the only copy of the decision. Come back to it when you know.

How it knows, and why a sender cannot abuse it: the record is local to your machine and keyed by who sent it together with what they called it. A sender can never address an entry they never sent you, whatever identity they claim — which is exactly the protection that made every accepted share a new entry in the first place.

Accounts

  • Add Account → Microsoft (works out of the box) or Google.
  • Google: VS Code has no built-in Google provider, so the extension registers its own (OAuth 2.0 code flow + PKCE, system browser, loopback redirect on 127.0.0.1). One-time setup, prompted inline on first sign-in: create a Desktop app OAuth client in Google Cloud Console (consent screen → External, add yourself as a test user), paste the client id (saved to credSshManager.googleClientId) and the client secret (SecretStorage). Reset Google OAuth clears both.
  • Sign Out / Remove Account (inline icon or context menu) removes the profile, its tree, and its secrets; for Google it also drops the auth session (Microsoft sessions are owned by VS Code's Accounts menu).

Vault locations: NAS folder or vault server

Every account syncs to a location, set per account (account row → Set Sync Location…, stored in credSshManager.accountNasPaths; the global credSshManager.nasBackupPath is the fallback):

  • a folder (/mnt/v/vault, Z:\Backups, \\NAS\Vault) — the original transport: one vault_<email>.enc per person in a shared folder, pending shares carried inside each file's plaintext envelope array. Anyone with folder access can read everyone's ciphertext.
  • a server URL (https://vault.company.com) — the Cred Vault Server (cred-vault-server/ in this repository): every request carries that account's own OAuth token, so you can read only your own vault and inbox, and the server stamps a share's sender from the verified token (unforgeable). Recommended for company-wide use.

Mixed setups are normal: the corporate account on the company server, the personal one on your own NAS. Shares are bound to the recipient's account id on folders and to their email on the server (that is the identity the server enforces) — the transport handles this transparently.

Which transport for what (architectural boundary)

  • NAS folder → personal / solo sync, now with signed senders. It remains the right choice for one person syncing their own vault across their own machines. A share sent over a folder is signed with the sender's Ed25519 key, and the recipient pins that key on first contact: every later share from that address must match it, and one that does not is refused with both fingerprints shown side by side.

    What that is and is not. A signature proves "signed by the holder of key K". Tying K to a person is a separate problem, and if keys travel over the same folder an attacker can write, they can publish their own. So this is trust-on-first-use plus continuity — strong against somebody who turns up after you have exchanged a share, weak against somebody already in place before the first one. The only thing that closes that gap is reading the fingerprint to each other out of band, which is why the first-contact dialog shows it and why Show Signing Fingerprint… exists on your own account row. It is forgery-resistant, not forgery-proof, and should never be described as the latter.

    Since 0.82 a share's label — sender, name, kind, date — is bound to its ciphertext as GCM additional authenticated data: a label edited after sealing breaks decryption instead of changing what you are shown. A share from an older build is unbound, says so in the inbox and on the PIN prompt, and opens until 0.85.0; from then on it is refused with a request to update the sender. A share from an older build carries no signature and is shown as unsigned rather than refused. A sender who has signed before and suddenly does not is a different matter and is refused: that is what stripping a signature looks like.

  • Server transport → the recommended standard for teams. The Cred Vault Server authenticates every request with that account's OAuth token and stamps the share sender from the verified token, so fromEmail is unforgeable. Any deployment where people share credentials with each other should use the server, not a shared NAS.

Unlocking with a security key (YubiKey / FIDO2)

A vault can be opened by several security keys plus the PIN, in the "touch your key" style of a Microsoft sign-in — no typed password.

  • Add: account row → Add Security Key (YubiKey)…. The browser opens a local http://creds-for-devs.localhost page (loopback by RFC 6761 — a name no other local page can claim), the OS shows its native security-key prompt, and the key's WebAuthn PRF secret becomes a wrapping key.
  • How it is stored: a v2 vault encrypts its payload with a random master key, and that master key is wrapped once per unlock method — the PIN wrap and one wrap per registered key — as plaintext metadata in the envelope (wraps[]). Any wrap opens the vault, so adding or removing a key never re-encrypts your data and never invalidates the others.
  • Every vault is v3 now — PIN-only included — so update every machine BEFORE anyone syncs. A vault used to stay on the slow v1 format (PIN-derived key, scrypt on every read and write) unless you registered a security key. As of this release nothing writes v1 any more: the next save migrates a PIN-only vault to v3 (a random master key, wrapped once under your PIN, read with HKDF), a brand-new vault is v3 from the start, and your backups convert on their next run too. The migration is automatic and keeps the same PIN — nothing is converted by hand and no data is touched. The catch is the same as any format bump: a build older than this one refuses a v3 file ("Unsupported backup version: 3"), so one updated laptop syncing to a shared folder can lock a colleague still on an old build out of their own vault. Roll the extension out to everyone first, then sync. Reading a legacy v1/v2 file keeps working forever.
  • Unlock: the master key is cached in memory for the window, so background sync never asks for a touch again. Lock Vaults (clear cached keys) drops the cache; Unlock Vault (Security Key)… prompts on demand.
  • Auto-lock (credSshManager.autoLockMinutes, default 60, 0 disables) locks the vaults after an idle window measured in your actions — opening or copying a credential, connecting, installing a key, editing an entry, unlocking. Not mouse movement, and deliberately not background sync: a five-minute sync timer counted as activity would mean the window never elapses and the setting silently does nothing. Locking forgets the cached master key; local credentials keep working, because they live in the OS keychain and are not protected by the vault key.
  • Remove: account row → Remove Security Key… (pick from the list).
  • The PIN always remains a fallback — losing every key does not lock you out, and losing the PIN does not either as long as one key is registered.
  • Requirements & limits: PRF needs Chrome or Edge as the default browser and a FIDO2 key with hmac-secret (YubiKey 5 or newer). Firefox/Safari currently return no PRF result — the flow reports that and the PIN keeps working. Credentials are registered against the RP id creds-for-devs.localhost (0.81+), so the same physical key works on every machine — and no other local page can ask the key for this vault's secret. A key registered before 0.81 is bound to the bare localhost: it keeps unlocking, and the first time it does you are offered a one-touch re-registration; the old registration is retired only once the new one is in the vault, and the PIN works throughout.

Recovery — the printed code, and the corporate break-glass

  • Set Up Recovery Code… shows a 150-bit code once, with Print and deliberately no Copy; Unlock Vault (Recovery Code)… opens the vault with it and offers a new PIN; Remove Recovery Code… retires it.
  • Corporate Recovery… is the operator's panel on a self-hosted server: name the officers, set the quorum. Accept Recovery Share… is each officer taking their share (per machine — shares live in this machine's keychain); Recover a Colleague's Vault… starts the break-glass, Contribute to a Recovery… is an officer's half, Finish a Recovery… re-keys the vault under a temporary PIN told to the target out of band.

Corporate roles — admin, member, dev

On a server with recovery officers, every account has a role, and the server tells the extension which on every sync — together with the policy for that role (may export, may share with whom, may move an entry out of a project). Set Role… on a colleague's Team row lets an admin — or a recovery officer, who administers unconditionally — make somebody admin, member (today's behaviour, and the default) or dev, and for a dev choose whether they may share inside their projects or not at all. My Role and Policy… on an account row shows what the server says about you.

The event log. A corporate server records what happened — shares with their outcome (taken, declined, withdrawn, expired), roles, blocks, projects and assignments — and Event Log… on an account row opens it as a tab: newest first, four buttons to narrow by, Load more for the page before. The server decides what each person sees: an admin — or a recovery officer, who administers unconditionally — reads the company, everybody else reads only the rows naming them. It records metadata — an entry's name and kind — and never a byte of what was in it.

Server Backup. Server Backup… on the account row of an admin — or of a recovery officer, who administers unconditionally — takes one encrypted archive of everything the server holds — every vault, the sealed login keys, the registry, projects, the event log and a snapshot of the server's own configuration — on a schedule, and sends it to an S3-compatible bucket or an Azure Blob container once a destination is configured. Minting the backup key shows its words once: nothing can produce them again, and without them no archive can be opened. The tab shows the last run, the newest archive on the server, and a row per destination saying whether the upload arrived and whether the retention pass could then run; Back up now starts one immediately and Download newest archive streams it to a file you choose. A deployment with no key, or whose last run failed, is said out loud — once a day, or once an hour while failing — because the failure this exists to end is a scheduler refusing every five minutes with nobody watching.

Projects. An admin runs New Project… from Team — that is where the first one is made — then Assign to Project… and Remove from Project… on a colleague's row. An assignment becomes a folder in that person's own vault, named after the project and following its name; for a developer that folder cannot be renamed, moved or deleted, and an entry cannot be moved out of it except to the Trash. Removing somebody asks what happens to their copy — leave it with them, or delete it from every machine they sync — because the two are not recoverable from each other. A Team row shows the projects a colleague is on beside their role.

A developer's sharing is the one rule the server enforces rather than asking an honest client to obey: they may share only out of a project folder, only with somebody on that project, and only while the project is open. The project travels with the share and is bound into its encryption, so it cannot be edited afterwards.

NAS backup & automatic multi-PC sync

Everything travels as one AES-256-GCM encrypted file per profile (vault_<sanitized email>.enc) in the folder set by credSshManager.nasBackupPath. The key is profile-bound: scryptSync(accountId + PIN, salt, 32) — restoring needs both an active auth session for that account and the PIN. Filenames are collision-safe (same email under two providers gets distinct names). The envelope keeps account metadata in plaintext (needed to verify the session and derive the key before decryption); salt/IV/GCM-tag are public by design; the payload (tree, passwords, private keys, VPN configs, DB connection strings, tombstones) is ciphertext. The PIN is the only protection — use a real passphrase, not 4 digits.

  • Manual: Backup to NAS / Import / Restore (also reachable as the original spec's command ids extension.exportSecrets / extension.importSecrets). These prompt for the PIN every time.
  • Automatic (credSshManager.autoSync): runs ~5s after every change, on startup, and every autoSyncIntervalMinutes (default 5); manual trigger via the toolbar's Sync Now. The sync PIN is per account, per machine (Set Sync PIN asks which account; a previously set machine-wide PIN keeps working as the fallback) and must match across machines for that account.
  • Per-account folders: each account can sync to its own NAS — account row → Set Sync Folder… (stored in credSshManager.accountNasPaths, email → path; unmapped accounts use the global nasBackupPath). The corporate account lives on the company NAS, the personal one on yours; teams, shares, and backups all follow the account's folder.
  • Merge, not overwrite (causal version vectors): both PCs may change data. Each machine has a persistent deviceId and a monotonic counter; every node carries a version vector v ({deviceId: seq}) alongside updatedAt. On merge, the vector that causally dominates wins — so a later edit beats an earlier one even when a skewed clock disagrees. Truly concurrent edits (neither vector dominates) fall back to the higher updatedAt, then to the lexicographically-greater last-writer deviceId, so both machines converge to the same winner regardless of merge order.
  • Deletions and rollback protection: deletions leave lightweight tombstones ({deletedAt, v}, no secret payload); an edit whose vector is causally newer than the delete resurrects the node, otherwise the deletion wins. A per-profile horizon (element-wise max of every vector ever seen, never pruned) lets tombstones be hard-deleted after 90 days without losing the causal memory: a node restored from a stale backup whose vector the horizon already covers is rejected as a phantom instead of resurrecting. Legacy pre-vector vaults (no v) still merge by updatedAt/tombstone time and adopt a vector on their first write. Secrets follow the winning side; orphaned children re-parent to root. NAS writes are temp-file + atomic rename; a file that fails to decrypt (wrong PIN / corrupted) is reported once and never overwritten — mismatched PINs pause sync, they cannot destroy data.

The creds CLI, bridges and WSL — the commands

  • Enable CLI Access… on an entry mints a named grant: creds ssh prod-db instead of a pasted token. The entry's viewer lists its CLI aliases with the exact command ready to copy.
  • Install creds (terminal CLI)… installs the binary locally; Copy install command for another machine... puts a one-line installer on the clipboard; Install creds on the Host… installs it on a Remote-SSH host from the entity's own menu.
  • Open Remote Bridge… / Close Remote Bridge hold the ssh -R tunnel that lets the host's creds reach this window; the menu item says which state it is in.
  • Set Up the WSL Agent Relay turns on the agent relay inside your distributions (wslAgentRelay, wslRelayCommand, wslRelayDistros are its settings — which distros, and what the relay runs).

Per-machine setup (WSL example)

# make the NAS/drive visible inside WSL (network drives are not auto-mounted)
wsl.exe -u root -e sh -c 'mkdir -p /mnt/v && mount -t drvfs V: /mnt/v'
# persist: echo 'V: /mnt/v drvfs defaults,uid=1000,gid=1000,metadata 0 0' >> /etc/fstab

Then set credSshManager.nasBackupPath (e.g. /mnt/v/vs code extn passw manager), enable credSshManager.autoSync, run Set Sync PIN with the shared PIN, and sign into the same account profile.

Dated snapshots (separate from sync, deliberately)

Sync keeps one live vault and merges — which means a deletion propagates. Delete a credential on the laptop and the desktop's next sync agrees it is gone. That is correct for a live vault and useless as a safety net, so snapshots are a second, independent path:

  • Where: credSshManager.backupLocation, or per account via account row → Set Backup Location… (accountBackupPaths). A NAS folder, an external drive, a Google Drive or OneDrive sync folder. Empty disables it.
  • When: backupIntervalHours (24 daily, 168 weekly, 0 off), or per account via Set Backup Schedule…. Snapshot Vault Now takes one on demand.
  • A snapshot identical to the previous one is not written, so a quiet vault does not fill a metered folder with copies of itself.
  • Retention: backupRetainDays deletes older ones (0 keeps everything). The newest is never deleted whatever its age — a laptop closed for a year must not come back to an empty backup folder.

Snapshots are the same encrypted format as everything else: they open with the account and the PIN, and carry attachments, images and VPN configs like passwords.

Settings

Almost nothing here needs editing by hand. Every setting that belongs to one account has a right-click command on the account row, and that is the intended way in — the entries below say which one. The raw keys are documented because a settings.json is what an admin pushes to a fleet, and because a value you cannot find is a value you cannot trust.

Two rules govern how they combine:

  • Per-account beats global. accountNasPaths / accountBackupPaths / accountBackupIntervals map an email to a value; an account with no mapping falls back to the global nasBackupPath / backupLocation / backupIntervalHours. That is what lets a work profile on a company server and a personal profile on a home NAS live in one window.
  • Nothing here holds a secret. Locations, intervals, client ids and scopes — all of it is safe in a synced settings.json or a fleet policy. Every actual secret is in the OS keychain.

Where the vault lives

Setting Default What it does
nasBackupPath (empty) The default vault location for accounts with no mapping of their own: either a folder for the encrypted vault_<email>.enc files (/mnt/z/Backups, Z:\Backups, \\NAS\Vault) or a Cred Vault Server URL (https://vault.company.com). A URL switches that account to authenticated server sync — the two transports differ in more than spelling, see Which transport for what above
accountNasPaths {} Per-account override: { "work@corp.com": "https://vault.corp.com", "me@gmail.com": "/mnt/home-nas/vault" }. From the UI: account right-click → Set Sync Location…
autoSync false Sync every profile automatically — after each change, at startup, and on the interval below. Edits from several machines are merged per node, not overwritten. Needs the same Sync PIN on every machine (Set Sync PIN)
autoSyncIntervalMinutes 5 How often auto-sync pulls. Lower it on a fast LAN, raise it on a metered link

Snapshots — the safety net, deliberately not the same thing as sync

Setting Default What it does
backupLocation (empty — off) Where dated snapshots are written. Point it at different storage from nasBackupPath: the sync location merges, so a deletion travels to every machine; the snapshot is the copy that still has what you deleted. Same encrypted bytes, no PIN needed to take one, restored with Import / Restore
accountBackupPaths {} Per-account snapshot folder. From the UI: account right-click → Set Backup Location…
backupIntervalHours 24 1 hourly · 24 daily · 168 weekly · 0 off. A snapshot identical to the previous one is not written, so a quiet vault does not fill a metered folder with copies of itself
accountBackupIntervals {} Per-account schedule, in hours. From the UI: account right-click → Set Backup Schedule… (hourly / 6h / daily / weekly / off / custom)
backupRetainDays 30 Delete snapshots older than this; 0 keeps them forever. The newest is never deleted whatever its age — a laptop closed for a year must not come back to an empty backup folder

Agents, the CLI and WSL

Setting Default What it does
agentGrantIdleMinutes 60 A Share with Claude Code token dies after this long unused. 0 disables the idle timeout — the token still dies with the window
agentGrantMaxCalls 0 The most calls one token may make before it expires; 0 is no cap. Useful for a one-off task: share, let the agent make its handful of calls, and know the token is spent
wslAgentRelay false Serve the SSH agent on a unix socket inside WSL distributions, so ssh and git there use vault keys with a dialog per signature. From the UI: Set Up the WSL Agent Relay
wslRelayCommand (auto) What the relay runs inside the distribution — override only if creds is not on the default path there
wslRelayDistros [] Which distributions get the relay; empty means the default distribution
gitDeployKeys {} Per-repository SSH key for the git sync transport: maps a repo URL to the entity whose key pushes it

Everything else

Setting Default What it does
helpLanguage en The language of the Help pages only (en, ru, uk, de, es); articles not yet translated show English with a note. The switch at the top of the help page writes this same setting
uiScale 0 Text size on every CredsForDevs page, in steps of 10% (−5…5). The ± buttons on each page write this same setting, and it syncs
secretClipboardTtlSeconds 45 How long a copied secret stays on the clipboard before the extension clears it — if the clipboard still holds exactly what was copied
microsoftApiScope (default scope) The OAuth scope requested for the vault server sign-in; set it when your server's app registration names its own

Locking

Setting Default What it does
autoLockMinutes 60 Lock after this many minutes without you using the vault; 0 disables. "Using" means an action of yours that touches a secret — open, copy, connect, install a key, edit, unlock. It is not mouse movement and not background sync: a timer firing is not you being present. Locking forgets the cached master key and refuses the saved Sync PIN until you unlock deliberately; your credentials keep working locally, because they live in the OS keychain and are not protected by the vault key. From the UI: Set Auto-Lock…

Sign-in

Setting Default What it does
microsoftApiScope (empty) The API scope of your own Entra app registration, e.g. api://<client-id>/vault.access. Against server 0.2.3 and newer, leave this empty — the server publishes the value on /api/client-config and the extension asks for the right scope by itself. Set it only to override a server advertising the wrong value, or to work against an older server. Why it exists: with no scope the extension receives a Microsoft Graph token, and Graph tokens are deliberately unverifiable by third parties, so every server refuses them with 401 — see When the Team is empty below
googleClientId (empty) OAuth 2.0 client id of your Desktop app credential (Google Cloud Console → APIs & Services → Credentials). Required for Sign in with Google. The client secret is prompted once and kept in SecretStorage, never in settings

Behaviour

Setting Default What it does
dbExtensions {} Which extension Open in DB Extension hands a connection to, per DB type: { "mysql": "cweijan.vscode-mysql-client2" }. Empty = the first installed candidate wins
secretClipboardTtlSeconds 45 How long a copied secret stays on the clipboard before it is cleared — and only if the clipboard still holds exactly what was copied, so a later copy of your own is never destroyed. What no extension can control: Windows Clipboard History (Win+V) and cross-device sync capture the value the moment it is copied, and clearing the clipboard afterwards does not reach them. Turn those off if you copy secrets on a machine you do not control
readCliHelp true When you paste a whole command into a terminal entry, fill the empty notes by running <tool> --help. It runs the tool you just typed, with no shell and no arguments of ours, and only when every word of the command is a plain tool name — anything containing a shell metacharacter is never run. It never overwrites a note you wrote. Turn it off if you would rather nothing were executed while you edit

When the Team is empty

The one failure this product used to produce on its own — everyone signed in, sync green, no error, and nobody in each other's Team. Three things now stand between you and it:

  1. 0.46 stopped swallowing the server's refusal. A 401/403 when listing the team is shown with the reason and what to do about it, instead of returning an empty list that looks exactly like a team nobody has joined yet.
  2. Server 0.2.3 makes it not happen: the server advertises its scope, the extension configures itself, and a developer signs in and is done.
  3. On an older server, set microsoftApiScope to match that server's MS_AUDIENCES. Any other cause — a domain missing from the server's allow-list, a token for a different audience — the message names as such.

Commands

Every command lives under the CredsForDevs: category in the palette, and each one is also on the right-click menu where it applies.

  • Accounts — Add Account · Sign Out / Remove Account · Set Sync Location… · Set Backup Location… · Set Backup Schedule… · Show Signing Fingerprint… · Reset Google OAuth
  • Vault — Set Sync PIN · Add Security Key (YubiKey)… · Remove Security Key… · Unlock Vault (Security Key)… · Lock Vaults (clear cached keys) · Set Auto-Lock…
  • Tree — Add Folder · Add Entity · Edit · Clone… · Delete · Move to Folder… · Change Folder Type… · Move Up · Move Down · View Details · Restore This Version… · Refresh
  • SSH — Connect SSH · Toggle SSH (on/off) · Copy Password · Install SSH Key to System (~/.ssh) · Add to SSH Agent · Remove from SSH Agent · Copy Git Signing Config
  • One-time codes — Copy One-Time Code
  • Websites — Open Site in Browser (an entry's URL, http and https only; also a button beside the URL in View Details)
  • Secrets in a run — Run with Secrets (creds:// references, output masked)
  • Getting started and getting around — Go to Credential… (Ctrl+Alt+P) · Generate Password or Passphrase… · Health Report · Import from ~/.ssh/config or another manager…
  • VPN — Start VPN · Stop VPN · Save VPN Config As…
  • Databases — Open in DB Extension · Copy Connection String
  • Terminal commands — Run in Terminal · Copy Command · Show Command and Notes
  • Scripts — Run Script
  • Agents — Share with Claude Code…
  • Sharing — Share with… · Create Entity for… · Accept… · Decline · Accept All from Sender… · Accept All Shared…
  • Outside the team — Export / Share Externally… · Import from External…
  • Sync & backup — Sync Now (NAS) · Backup to NAS · Import / Restore · Snapshot Vault Now (the original spec's Export Secrets (backup to NAS) / Import Secrets (restore backup) — extension.exportSecrets / extension.importSecrets — still work)
  • Find and filter — Filter Credentials… (with the has: / mcp: capability filters) · Clear Filter (reveals and briefly tints the row you had selected) · Show Entry by id… · Go to the Original Folder
  • Config entries — Write Config File Here… · Show What Changed · Enable Code Access… · Revoke Code Access…
  • CLI, bridge and WSL — Enable CLI Access… · Install creds (terminal CLI)… · Copy install command for another machine... · Install creds on the Host… · Open Remote Bridge… · Close Remote Bridge · Set Up the WSL Agent Relay
  • Agents (MCP) — Install the MCP Server… · MCP logs
  • Recovery — Set Up Recovery Code… · Unlock Vault (Recovery Code)… · Remove Recovery Code… · Corporate Recovery… · Accept Recovery Share… · Recover a Colleague’s Vault… · Contribute to a Recovery… · Finish a Recovery…
  • Corporate roles — Set Role… (on a colleague's Team row, for admins and officers) · My Role and Policy… (on any account row of a corporate server)
  • Corporate projects — New Project… (on the Team row, or a colleague's) · Assign to Project… · Remove from Project… (both on a colleague's Team row, for admins and officers)
  • Keys on disk — Install SSH Key to System (~/.ssh) · Remove Installed Key… · Add to SSH Agent (confirm every use) · Remove from SSH Agent
  • Scans — Health Report (weak, reused, exposed) · Check Clipboard for Vault Secrets · Scan This File for Vault Secrets · Show Diagnostics
  • Sharing, cont. — Withdraw a Share You Sent…
  • Databases, cont. — Copy Connection String (no password)
  • Secrets in a run — Run with Secrets (creds:// references, output masked)
  • Short-lived entries — Burn Now… (only on an entry that carries a lifetime; not the Trash: the secret, its history and every synced copy are gone for good, after a modal that says so)
  • The server, for recovery officers — Server Metrics… (on an officer's account row: the server's version and runtime support window, uptime, requests by outcome, vault traffic, what the data directory holds and the free space on its disk — one page, read from GET /api/metrics, which answers the recovery roster and nobody else)
  • The Trash — Restore (first on a deleted entry's or folder's menu: back to where it was deleted from, or the root when that folder is gone) · Empty the Trash Now · Empty the Trash Automatically… (each account's own retention, travelling with the vault)

Security notes

A full audit of every place a secret could be read without opening the vault ran in 0.50, and each finding is closed or bounded below. One of them had been introduced two releases earlier by the same hand that found it; it is listed rather than quietly patched, because the invariant it broke is written down and a broken invariant that leaves no trace breaks again.

  • A script's variables never enter the script text. They travel in the process environment, and the body reads them by name in its own language. So the file on disk, the viewer, and Copy All carry names where they used to carry values. A script that prints its own variable is still your code — that is noticed and mentioned once, not blocked.
  • The env-variable check button reports presence and length, never the value. It used to echo NAME=value, which put a bound private key into terminal scrollback in full.
  • File permissions on Windows are real now. chmod 0600 is nearly a no-op there, and the inherited NTFS list gives SYSTEM and the local Administrators group full control of everything under your profile — the wrong audience precisely on a machine where you are not the administrator. Every file this extension writes a secret into now breaks that inheritance and grants its owner alone.
  • Installing a key into ~/.ssh says that the copy is permanent, and Remove Installed Key… is the way back.
  • Copying a DB connection string says the password is in it, and Copy Connection String (no password) is the companion for when it should not be.
  • Passwords, private keys, VPN configs, and DB connection strings live only in SecretStorage and inside the encrypted .enc files; they are never written to globalState, settings, or logs, and never sent into webview HTML. (Tree metadata — host/user/port/notes/public key — is globalState plaintext by design; don't put secrets in notes.)
  • PIN strength is enforced (min 8 chars) everywhere a PIN is set — it is the sole barrier on ciphertext that lives off your machine.
  • Removing the last security key re-keys the vault under your PIN, so a removed YubiKey (and stale backups holding its wrap) can no longer open future versions. With other keys still registered, removal drops that wrap but does not re-key existing copies (you're told so).
  • Accepting a share always creates a fresh local entity — a sender can never address, and thus silently overwrite, something already in your vault.
  • An AI agent granted access never receives the secret. It holds a token that buys one entity's worth of work; ssh is run by the extension, the password rides that child process's environment, and no response the broker can send has a field a secret could travel in. The grant dies with the VS Code window, its first use needs your click, and every call is written down. What it does not remove is the access itself — an agent that can run commands on a host can do what that host allows.
  • Decrypted SSH keys are ephemeral: materialized only for an active ssh -i session (0600), deleted when that terminal closes and purged on every activate/deactivate — they never accumulate on disk.
  • Server sync must be HTTPS: a plain-http:// server URL (except localhost) triggers a modal warning before use, since the bearer token would otherwise travel in clear.
  • The WebAuthn unlock page is served only at an unguessable loopback path, so another local process can't read its challenge/nonce.
  • KDF cost is versioned in the header: new data is sealed at scrypt N=2^17 (params recorded per blob); older data reads at N=2^15 and upgrades to the higher cost the next time its vault is written.
  • Cross-machine merge is causal (version vectors + a per-profile horizon), so a stale/rolled-back backup can't resurrect a deleted entry even after its tombstone is GC'd — see the sync section above.
  • On a shared folder, the envelope's integrity check covers your own metadata but not the cross-user share list, because any member has to be able to append to it. Share metadata on a NAS is therefore not forgery-proof by cryptography — it is by deployment: teams use the server transport, which stamps the sender from the verified sign-in token. The folder transport is for one person on several machines.
  • Backups decrypt only with the exact account + PIN pair; there is no recovery for a lost PIN.
  • "Copy" actions put plaintext on the system clipboard — clear it after use on shared machines.
  • A Google "Desktop app" client secret is not confidential by Google's definition, but it is still kept in SecretStorage, not in settings.

The server, and raising it in Docker

Everything above works with no server at all — a folder is enough for one person on several machines. The server is what makes a team work: it authenticates every request against Microsoft Entra or Google, and stamps the sender of a shared credential from that verified identity, so a share cannot be forged by anyone with write access to a folder.

It is zero-trust by construction: the server stores ciphertext and never holds a key. Encryption and decryption happen in your editor, under your PIN or your security key. Whoever runs the box — including you — cannot read what is in the vault. That is a property of the design, not a policy anyone has to keep.

Raising it is three commands on any machine with Docker:

git clone https://github.com/oleksandrdubyna88/dew_flow_creds_for_devs
cd dew_flow_creds_for_devs/deploy

cp .env.example .env
$EDITOR .env            # who may sign in, and your domain or public IP
docker compose up -d

That starts the API, an nginx in front of it, and a certbot that obtains a Let's Encrypt certificate and then renews it forever — by domain or by bare IP, whichever you have. Then point the extension at https://your-host with Set Sync Location… and sign in.

The image is prebuilt and published for amd64 and arm64 — a ~50 MB Native-AOT build with no shell and no .NET inside, so nothing is compiled on your server and there is very little in the container to attack. Vault data, logs and certificates live in host folders you choose, so updating the image never touches them. Prefer no Docker at all? Every release also ships standalone binaries for Linux and Windows, x64 and ARM64 — one file, no runtime to install. deploy/README.md covers the rest: sign-in providers, TLS by IP against by domain, scheduled backups to a NAS, restore rehearsal, and one-command updates.

Source, issues, licence

  • Repository: github.com/oleksandrdubyna88/dew_flow_creds_for_devs
  • Issues and feature requests: github.com/oleksandrdubyna88/dew_flow_creds_for_devs/issues
  • Deploying the server: deploy/README.md
  • Licence: MIT — use it, fork it, run it wherever you like.
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft