Skip to content
BasaltDBBasaltDB
Star

Passkeys

View as Markdown

A passkey is how you approve things. When a write is held, or you undo a run, you sign with a passkey: Touch ID, a security key, or a password manager that stores passkeys. Postgres checks the signature before anything changes. The engine, the CLI and the pages only carry it. An agent can open a link but cannot sign, and an agent’s role can neither register nor remove a passkey.

For what a signature covers, see Approvals and Undo.

basalt init registers your first passkey. After the guard is installed it prints a link:

  1. Open the link in a browser on the same machine.
  2. Press the button and confirm with your passkey.
  3. Copy the code the page shows (it starts with basalt-passkey.1.) and paste it at Paste the code the page shows:.

You see basalt: passkey registered for localhost. The public key is stored in your Postgres. The private key never leaves your device.

To add another passkey, for a second device or a teammate, run init again for the same agent:

basalt init --agent NAME --add-passkey --passkey-label "work laptop"

Re-running init keeps the agent’s password. --skip-passkey on a first install defers the registration.

A passkey belongs to the page where you registered it. The engine serves its pages at http://localhost:8787 by default, so the passkey is for localhost. A passkey made on that page signs only on that page, so keep the same port. For a bot on its own computer the pages come from the BasaltVault portal, and the passkey belongs to it.

  • macOS. Touch ID, or any option listed for Linux.
  • Linux desktop. There is no built-in passkey store, so use one of these:
    • Your phone, through the browser’s QR code flow, only for pages on a real domain such as the BasaltVault portal. Both devices need Bluetooth.
    • A USB or NFC security key. It needs a PIN, which you set with ykman fido access change-pin or in the browser’s security key settings.
    • A password manager extension that stores passkeys.

A phone through the QR flow is not offered for pages on localhost (Chrome on macOS shows no phone option), so approving from a phone needs the pages on a real domain: the hosted portal.

If the engine runs on a headless server, open an SSH tunnel with the same port on both ends, then open the link on your own computer:

ssh -N -L 8787:127.0.0.1:8787 you@server

Register through the same tunnel. Keep it open while basalt init waits for the code.

basalt passkey list --agent NAME

It prints one line per active passkey: its id, label, where it was registered, when it was registered, and when it last signed something (never if it has not). --json prints the same as JSON. It never prints key material.

Nothing tells BasaltDB when you delete a passkey from a device or a password manager. basalt passkey check finds out.

basalt passkey check --agent NAME --open

It opens a page that asks for each passkey in turn. Confirm each one, then press Done and paste the code the page shows. The command prints each passkey as found on this device or not found on this device. For each one not found it asks Remove <label> (<id>)? [y/N]. Nothing is removed unless you answer y. A check signature authorizes nothing: it cannot approve a write or an undo.

Remove a passkey you lost, or one on a device you no longer use. This needs the admin connection string, at a hidden prompt, because it changes your database.

basalt passkey remove PASSKEY_ID

PASSKEY_ID is the full id from basalt passkey list, or a unique start of it with at least 8 characters. The command shows the label, where it was registered and when it last signed, and asks Remove passkey ...? [y/N]. A removed passkey is refused from then on: it can no longer approve a write or sign an undo. The removal is recorded in the ledger.

Removing a passkey does not delete it from the device. The command prints a link ending /passkeys/forget. Open it on each device that holds the passkey, so its password manager drops it. --open opens the link for you. Not every browser supports this. If yours does not, delete the passkey by hand in your password manager.

With no passkey, nothing held can be approved and no undo can be signed. So basalt passkey remove refuses to remove the last one. --force removes it, and you then type its label at the prompt, even with --yes. Add another passkey first, or run this afterwards:

basalt init --agent NAME --add-passkey
  1. If you have another passkey, run basalt passkey list --agent NAME and basalt passkey remove PASSKEY_ID for the lost one.
  2. If it was your only passkey, run basalt init --agent NAME --add-passkey first to register a new one, then remove the old one.
  3. Held writes wait until you can sign. They expire after 24 hours.

basalt log warns when no passkey is active. It also warns about a passkey that has not signed anything for 90 days, counted from registration if it never did. It names the passkey and says to use basalt passkey remove. Nothing is removed automatically. basalt status --agent NAME reports a missing passkey too. The agent sees the same as NO_PASSKEY. See When something is refused.

BasaltDB · Apache-2.0