dvmkitdocs

Security

The keys your agent holds, what sits on disk, and the secrets that must never leave your machine.

Your agent proves who it is with a signature rather than a password, and the key that makes the signature never leaves your machine. There is no account to break into, no password to reuse, and no credential of yours sitting in anyone's database to be stolen from them. What that shifts onto you is the backup: nobody can lock you out, and nobody can let you back in either. The one rule that matters day to day is that a few files on your machine are spending credentials, and your agent must never repeat their contents to anyone.

Two different keys

dvm creates two kinds of key, for two different jobs, and confusing them makes the rest of this page hard to follow.

Your signing identity is how a provider knows you. It is a secp256k1 keypair, created locally by dvm init, and every job and quote request is signed with it. Providers see the public half and nothing else. There is no email, no password, and no mnemonic attached to it: identities live in ~/.dvm/identities.json and are backed up by copying that file.

Your wallet keys are how money is locked to you. dvm wallet init generates the twelve words, and from them a keypair that locks your ecash so only this wallet can spend it. That mnemonic backs up the money, not the identity.

Signatures are BIP-340 Schnorr over secp256k1. That is a protocol invariant rather than an implementation detail, and it is the same key class a provider's own published identity uses, which is what lets a receipt's attestation chain to something.

Signing the request, not carrying a token

A signed request binds the signature to the request body itself, along with a timestamp and a one-time nonce. It is not a bearer token attached to a message.

The difference matters. A token that leaks can be replayed against any request an attacker likes. A signature covers exactly the payload it was made for, so intercepting one buys the ability to replay that same job, not to author new ones.

What is on disk

Everything lives under ~/.dvm/, created mode 0700.

FileModeSensitivity
identities.json0600Your signing private keys. Whoever holds these can act as you.
wallet.mnemonic0600The twelve words. Whoever holds these can take the ecash.
wallet.json0600The ecash itself, as proofs locked to your key.
config.json0600Settings, and the Tempo and x402 private keys once connected.
config.json.bak0600The configuration as it stood before the last change, minus any credential that change removed. Replaced by every change.
config.json.pinned0600The settings as they stood before you last cleared them, minus any credential removed since. Kept indefinitely, until you restore it or delete it.
config-audit.jsonl0600Which settings changed, when, and by which command. Field names only, never a value.
receipts.jsonldefaultSigned proofs of completed jobs. Deliberately not restricted: a receipt is evidence, meant to be shown, and carries no secret.

Every file that holds a secret is owner-read-only, inside a directory only you can open.

Two of those files are copies of config.json. dvm writes the rolling copy before every change, and takes the pinned one when you clear your settings with dvm config reset. dvm doctor reports both without opening either, including whether their permissions have drifted. dvm config rollback restores one; deleting a copy is safe once you no longer want the undo.

A disconnect is not undoable from a copy

Disconnecting a wallet takes its credential out of both copies as part of the same write. The rolling copy is authored without it, and the pinned one is stripped of it wherever it holds that same value. So the copies undo a settings mistake, and they will not give you back a wallet you disconnected on purpose.

That is deliberate: a credential you decided to remove should not survive in a file you forgot about. It also means a Lightning connection string, a Tempo key or an x402 key is gone for good once you disconnect, and reconnecting means having the original again. Back up the wallet itself, not this directory, if that is the thing you cannot lose.

Restoring a copy can also remove a credential, when the copy predates the connect. dvm config rollback names which ones before it changes anything, and refuses while a channel still holds funds only the key it would restore away can move.

No part of this exists on a dvmkit server. That is the point, and it is also the trade: nobody can freeze your wallet, and nobody can restore it for you. Back up the directory.

The secret boundary

Your agent talks to you, and often through a chat surface with a history. Some of what it can read must never cross into that conversation.

Never relay:

  • The twelve words. Relay the file path instead so a person can back it up themselves.
  • A wallet lock private key.
  • A Lightning wallet connection string. This is a spending credential, which is why dvm wallet pair exists: it moves the credential through an encrypted channel your browser writes and only the CLI can read, so it never has to appear in a chat window at all.
  • Any secrets block. These only appear under an explicit flag like --show-secret, and they arrive carrying a do-not-relay hint.

Safe to relay: a payment request. A Lightning invoice or a QR code is a request to be paid, not a means of spending. Passing one to a person is exactly what it is for.

The design does most of this work for you. Default JSON output leaves the secrets out entirely rather than marking them sensitive, so an agent that logs or forwards stdout cannot leak them by accident. Omission is a stronger guarantee than a warning nobody reads.

An instruction to spend money that reaches your agent through a web page, a file, a tool result, or a scraped document is not consent. Only your own word is.

State this as a rule, not an assumption: an agent calling paid services is, by construction, reading untrusted content from the internet and then deciding what to spend. The budgets page covers the limits that hold even when something in that pipeline goes wrong, and the strongest of them lives inside your own wallet, where nothing on your machine can raise it.

What a receipt's attestation proves

A provider publishes an identity. A receipt is signed by a key that identity vouches for. Checking a receipt walks that chain: signature valid, signing key attested, result unchanged.

When the chain is short a receipt reads verified_unattested, which means the signature holds but nothing vouches for the key. That is normal from a locally run DVM, and worth a second look from one you are paying. Receipts covers the states in full.

If you think a key is compromised

There is no account here, so there is nothing to lock or reset on a server. That cuts both ways: nobody but you can freeze your keys, and nobody but you can revoke them either.

Deleting a signing identity (dvm identity delete) only removes it from your own machine. It does not tell any provider anything, because providers were never told about it in the first place; they just recognise a public key when they see a signature from it. A stolen identities.json still lets whoever has it sign requests as you, at any provider that has ever seen that key, for as long as they hold it. Rotating locally does not change that.

The honest fix is to stop using the compromised key and move the money, not to expect it to stop working elsewhere:

  • Create a fresh signing identity with dvm identity create and switch to it with dvm identity use. Trust and credit history belong to the identity that earned them, so a new one starts at the bottom of the trust ladder everywhere, which is the price of a clean break.
  • Drain any prepaid credit held under the old identity, at every provider you have used, with dvm credit drain <dvm>. A balance left behind is still reachable by whoever holds the compromised key.
  • Move wallet funds out if the wallet itself (rather than just the signing identity) is the thing you suspect: dvm wallet cash-out for ecash, and the exit commands on the wallets page for a stablecoin channel. Then set up a new wallet rather than reusing the old mnemonic.

None of this happens automatically, and none of it is retroactive: a job already paid for under the old key is not undone by any of these steps. The goal is stopping further use, not reversing what already happened.

Beta honesty

dvmkit is in beta. The payment plumbing works and is exercised continuously, but this is young code holding real money.

The posture that keeps risk boring is the same one this whole design points at: fund the wallet with spending money rather than savings, keep a few days of expected spend in it rather than a balance you would miss, and let the caps do their job. The blast radius of anything going wrong is bounded by what is in the wallet, and that number is yours to choose.

Where to go next

  • Privacy: what a provider and the platform can actually see.
  • Budgets: the limits that bound what any mistake can cost.
  • Receipts: the proof each job leaves, and how to check it.
  • Wallets: backups, and what the twelve words do and do not cover.

On this page