Wraith Browser
Guides

Credential Vault

Securely store credentials with AES-256-GCM encryption, automate logins, generate TOTP codes, and audit vault access

Overview

The Wraith credential vault is an AES-256-GCM encrypted store backed by a local SQLite database at ~/.wraith/vault.db. Every secret is encrypted at rest. The vault supports passwords, API keys, OAuth tokens, TOTP seeds, session cookies, and generic secrets.

The vault integrates directly with browse_login for automated authentication flows and browse_vault_totp for generating 2FA codes. All access is logged to an audit trail.


How encryption works

The vault uses AES-256-GCM (Galois/Counter Mode), an authenticated encryption scheme that provides both confidentiality and integrity:

  • Key derivation: The master key is derived from your passphrase (or an empty passphrase in MCP auto-unlock mode)
  • Per-credential encryption: Each credential gets a unique nonce. The secret value is encrypted; metadata (domain, kind, identity) is stored in plaintext for lookup
  • Integrity: GCM produces an authentication tag that detects any tampering with the ciphertext
  • At-rest protection: The SQLite database stores only ciphertext. Without the master key in memory, secrets are unreadable

Step 1: Store a credential

Use browse_vault_store to add a credential. Each credential requires a domain, kind, identity, and secret value.

Store a password

{
  "tool": "browse_vault_store",
  "arguments": {
    "domain": "github.com",
    "kind": "password",
    "identity": "jane.smith@email.com",
    "secret": "my-secure-password-123"
  }
}

Response:

Credential stored: a1b2c3d4-e5f6-7890-abcd-ef1234567890 (jane.smith@email.com@github.com, Password)

Store an API key

{
  "tool": "browse_vault_store",
  "arguments": {
    "domain": "api.openai.com",
    "kind": "api_key",
    "identity": "project-alpha",
    "secret": "sk-abc123def456..."
  }
}

Store an OAuth token

{
  "tool": "browse_vault_store",
  "arguments": {
    "domain": "linkedin.com",
    "kind": "oauth_token",
    "identity": "jane-linkedin",
    "secret": "eyJhbGciOiJSUzI1NiIs..."
  }
}

Store a TOTP seed

TOTP seeds are the base32-encoded secret from your authenticator app setup (the value behind the QR code):

{
  "tool": "browse_vault_store",
  "arguments": {
    "domain": "github.com",
    "kind": "totp_seed",
    "identity": "jane.smith@email.com",
    "secret": "JBSWY3DPEHPK3PXP"
  }
}
{
  "tool": "browse_vault_store",
  "arguments": {
    "domain": "dashboard.example.com",
    "kind": "session_cookie",
    "identity": "admin-session",
    "secret": "sid=abc123; path=/; httponly; secure"
  }
}

Store a generic secret

For anything that does not fit the other categories:

{
  "tool": "browse_vault_store",
  "arguments": {
    "domain": "internal.example.com",
    "kind": "generic",
    "identity": "database-connection-string",
    "secret": "postgresql://user:pass@localhost:5432/mydb"
  }
}

Supported credential kinds

KindUse case
passwordLogin passwords
api_keyAPI authentication tokens
oauth_tokenOAuth/SSO bearer tokens
totp_seedBase32 TOTP secret for 2FA code generation
session_cookieSession cookies for pre-authenticated access
genericAny other secret value

Step 2: Retrieve a credential

Use browse_vault_get to decrypt and retrieve a stored credential:

{
  "tool": "browse_vault_get",
  "arguments": {
    "domain": "github.com"
  }
}

This returns the first credential matching the domain, including the decrypted secret value.

To filter by credential type:

{
  "tool": "browse_vault_get",
  "arguments": {
    "domain": "github.com",
    "kind": "totp_seed"
  }
}

Step 3: List all credentials

View all stored credentials without decrypting secrets:

{
  "tool": "browse_vault_list"
}

Response:

4 credential(s):

  a1b2c3d4 | github.com        | Password     | jane.smith@email.com | 5 uses
  e5f6g7h8 | github.com        | TotpSeed     | jane.smith@email.com | 3 uses
  i9j0k1l2 | api.openai.com    | ApiKey       | project-alpha        | 12 uses
  m3n4o5p6 | linkedin.com      | OauthToken   | jane-linkedin        | 1 uses

The list shows IDs, domains, kinds, identities, and usage counts. Secret values stay encrypted.


Step 4: Automate login with stored credentials

The browse_login tool performs a complete login flow: navigate to the login page, fill credentials, submit, and follow redirect chains.

{
  "tool": "browse_login",
  "arguments": {
    "url": "https://github.com/login",
    "username": "jane.smith@email.com",
    "password": "my-secure-password-123"
  }
}

This tool:

  1. Navigates to the login URL
  2. Detects username and password fields automatically
  3. Fills both fields
  4. Clicks the submit button
  5. Follows the entire redirect chain (302 -> 302 -> 200)
  6. Captures all Set-Cookie headers at every hop
  7. Returns the final page snapshot and all cookies

Combined workflow: vault retrieval + login

A typical pattern is to retrieve the credential from the vault, then use it to log in:

// Step 1: Get the credential
{
  "tool": "browse_vault_get",
  "arguments": {
    "domain": "github.com",
    "kind": "password"
  }
}

// Step 2: Use the returned username and password in browse_login
{
  "tool": "browse_login",
  "arguments": {
    "url": "https://github.com/login",
    "username": "jane.smith@email.com",
    "password": "my-secure-password-123"
  }
}

Detect authentication type first

Not sure what kind of login the site uses? Run auth detection:

{
  "tool": "auth_detect",
  "arguments": {
    "url": "https://example.com/login"
  }
}

This reports detected mechanisms: password forms, OAuth/SSO buttons, TOTP inputs, and CAPTCHA challenges.


Step 5: Generate TOTP 2FA codes

If you stored a totp_seed for a domain, generate a current 6-digit TOTP code:

{
  "tool": "browse_vault_totp",
  "arguments": {
    "domain": "github.com"
  }
}

Response:

TOTP code for github.com: 482916

The code is valid for the current 30-second window. Use it immediately with browse_fill on the 2FA input field:

{
  "tool": "browse_fill",
  "arguments": {
    "ref_id": 5,
    "text": "482916"
  }
}

Complete 2FA login flow

// Step 1: Log in with username and password
{
  "tool": "browse_login",
  "arguments": {
    "url": "https://github.com/login",
    "username": "jane.smith@email.com",
    "password": "my-secure-password-123"
  }
}

// Step 2: The login redirects to a 2FA page. Snapshot it.
{
  "tool": "browse_snapshot"
}

// Step 3: Generate the TOTP code
{
  "tool": "browse_vault_totp",
  "arguments": { "domain": "github.com" }
}

// Step 4: Fill the 2FA code into the input field
{
  "tool": "browse_fill",
  "arguments": { "ref_id": 5, "text": "482916" }
}

// Step 5: Submit the 2FA form
{
  "tool": "browse_submit_form",
  "arguments": { "ref_id": 6 }
}

Step 6: Rotate credentials

When you change a password or regenerate an API key, update the vault without deleting and re-creating:

{
  "tool": "browse_vault_rotate",
  "arguments": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "new_secret": "my-new-password-456"
  }
}

Get the credential ID from browse_vault_list.


Step 7: Delete credentials

Remove a credential permanently by its UUID:

{
  "tool": "browse_vault_delete",
  "arguments": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

This is a destructive operation -- the credential is removed from the database and cannot be recovered.


Step 8: Audit vault access

Every vault operation is logged. View the audit trail:

{
  "tool": "browse_vault_audit",
  "arguments": {
    "limit": 20
  }
}

Response:

5 audit entries:

  2026-03-22T10:00:00Z | STORE  | github.com        | OK
  2026-03-22T09:55:00Z | GET    | github.com        | OK
  2026-03-22T09:50:00Z | TOTP   | github.com        | OK
  2026-03-22T09:45:00Z | ROTATE | api.openai.com    | OK
  2026-03-22T09:40:00Z | LIST   | *                 | OK

The audit log records the operation type, target domain, timestamp, and status. Use it to verify that credentials are only accessed when expected.


Step 9: Domain approval controls

Restrict which domains can use a credential. This prevents a credential from being accidentally used on the wrong site.

Approve a domain

{
  "tool": "vault_approve_domain",
  "arguments": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "domain": "github.com"
  }
}

Check if a domain is approved

{
  "tool": "vault_check_approval",
  "arguments": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "domain": "github.com"
  }
}

Revoke domain access

{
  "tool": "vault_revoke_domain",
  "arguments": {
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "domain": "github.com"
  }
}

Step 10: Lock and unlock the vault

Lock the vault

Zeroize the master key from memory so no further decryption is possible until unlock:

{
  "tool": "vault_lock"
}

Unlock the vault

{
  "tool": "vault_unlock",
  "arguments": {
    "passphrase": "my-vault-passphrase"
  }
}

In MCP mode, the vault auto-unlocks with an empty passphrase by default. To use a custom passphrase, set it when you first create the vault or unlock with the passphrase after locking.


Persisting cookies after login

After a successful browse_login, save the session cookies so you can restore them later without logging in again:

{
  "tool": "cookie_save",
  "arguments": {
    "path": "~/.wraith/cookies.json"
  }
}

Next session, load them back:

{
  "tool": "cookie_load",
  "arguments": {
    "path": "~/.wraith/cookies.json"
  }
}

Tips

  • Store TOTP seeds early. Add the totp_seed when you set up 2FA on a site, not when you need it in an emergency.
  • Use domain approvals for sensitive credentials. Restricting a credential to specific domains adds a safety layer against accidental use.
  • Rotate, do not delete and recreate. browse_vault_rotate preserves the credential UUID, audit history, and domain approvals.
  • Lock the vault when idle. If you are done with a session, vault_lock clears the master key from memory immediately.
  • Check the audit log periodically. It is the fastest way to verify nothing unexpected accessed your credentials.
  • The vault is local only. All data stays in ~/.wraith/vault.db on your machine. Nothing is sent to any server.

On this page