Credential Vault
Secure password manager for AI agents — store, use, never expose.
Credential Vault
AI agents sign up for services, create accounts, and need to log back in later. The Credential Vault stores passwords, API keys, and TOTP secrets with enterprise-grade encryption — and ensures agents never see the plaintext in their conversation context.
Why This Matters
When an AI agent signs up for GitHub and gets a password, where does that password go? Without a vault:
- The password sits in the conversation context
- It could be extracted via prompt injection
- It appears in logs, transcripts, and audit trails
- Other agents or users viewing the conversation can see it
The Credential Vault eliminates all of these attack surfaces.
Security Architecture
3-Layer Protection
Layer 1: Envelope Encryption (AES-256-GCM)
Master Key (KEK) → per-org Data Key (DEK) → encrypted credential
DB dump is useless without the master key
Layer 2: Steel Browser Auto-Injection
Credentials injected at browser level → fields blurred immediately
Even screenshots can't capture the password
Layer 3: MCP Tool Design
No tool ever returns a credential value
Passwords flow: DB → decrypt → browser field (never into conversation)Envelope Encryption
Each organization gets its own Data Encryption Key (DEK), which is itself encrypted by a Key Encryption Key (KEK) — the master key stored in the server environment.
KEK (master, in env)
└─→ wraps Org DEK (stored in DB, encrypted)
└─→ encrypts each credential valueBenefits:
- Rotate the master key without re-encrypting every credential (just re-wrap DEKs)
- Delete an org's DEK to make all their credentials irrecoverable
- DB dump is useless — attacker needs both the wrapped DEK and the master key
Quick Start
Store a credential
# Via MCP (Claude Code, Cursor, etc.)
"Store my GitHub credentials: email bot@example.com, password MyS3cr3t!"The agent calls store_credential — the password is encrypted immediately and never returned in any future API response.
Log in securely
"Log into GitHub using my stored credentials"The agent calls login_with_credential — Steel Browser auto-fills the login form at the browser level, blurs the fields, and auto-submits. The password never appears in the conversation, DOM, or screenshots.
API Reference
Store Credential
POST /v1/credentialsBody:
{
"service": "github.com",
"identifier": "bot@example.com",
"value": "MyS3cr3t!",
"credential_type": "password",
"label": "GitHub bot account",
"expires_at": "2027-01-01T00:00:00Z"
}Response (201):
{
"id": "cred_abc123",
"service": "github.com",
"identifier": "bot@example.com",
"credential_type": "PASSWORD",
"label": "GitHub bot account",
"stored": true,
"message": "Credential stored securely. Use 'use_credential_in_browser' to fill it into forms."
}The value field is never returned in any API response after storage.
List Credentials
GET /v1/credentials
GET /v1/credentials?service=github.comResponse (200):
{
"data": [
{
"id": "cred_abc123",
"service": "github.com",
"identifier": "bot@example.com",
"credential_type": "PASSWORD",
"label": "GitHub bot account",
"last_used_at": "2026-03-15T10:00:00Z",
"created_at": "2026-03-01T10:00:00Z"
}
]
}Returns metadata only — never the actual values.
Use Credential (Internal)
POST /v1/credentials/useThis endpoint decrypts the credential for browser injection. Called internally by the MCP server's use_credential_in_browser tool. The plaintext goes directly to the browser and is never exposed in the API response to the agent.
Delete Credential
DELETE /v1/credentials/:idPermanently deletes the credential and logs the deletion.
Audit Log
GET /v1/credentials/audit?limit=50Every credential access is logged:
{
"data": [
{
"id": "log_abc",
"credential_id": "cred_abc123",
"action": "use_in_browser",
"metadata": { "service": "github.com", "identifier": "bot@example.com" },
"created_at": "2026-03-15T10:00:00Z"
}
]
}MCP Tools
store_credential
Store a password, API key, or TOTP secret in the encrypted vault. Also syncs to Steel Browser for auto-injection into login forms.
| Parameter | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Website (e.g. github.com) |
identifier | string | Yes | Username or email |
value | string | Yes | The secret (encrypted, never returned) |
credential_type | string | No | password, api_key, totp_secret, oauth_token, cookie |
totp_secret | string | No | Base32 TOTP secret for 2FA auto-fill |
namespace | string | No | Isolation namespace for multi-user setups |
label | string | No | Human-readable label |
login_with_credential
Most secure login method. Creates a Steel Browser session with credential injection enabled, navigates to the login page, and lets Steel auto-fill + auto-submit with fields blurred.
| Parameter | Type | Required | Description |
|---|---|---|---|
login_url | string | Yes | Login page URL |
service | string | Yes | Service matching stored credential |
namespace | string | No | Credential namespace |
session_id | string | No | Browser session name |
wait_seconds | number | No | Seconds to wait for injection (default: 5) |
success_selector | string | No | CSS selector confirming login success |
What happens:
- New browser session created with
credentials: { autoSubmit: true, blurFields: true } - Navigate to login URL
- Steel detects the login form and auto-fills username + password
- Fields are blurred immediately (prevents screenshots from capturing)
- Form auto-submits
- Returns snapshot of post-login page
use_credential_in_browser
Fallback for non-login password fields. Decrypts server-side and types into a specific browser element.
| Parameter | Type | Required | Description |
|---|---|---|---|
service | string | Yes | Service matching stored credential |
identifier | string | Yes | Username/email matching credential |
ref | string | Yes | Browser element ref (e.g. @e5) |
credential_type | string | No | Type of credential (default: password) |
press_enter | boolean | No | Press Enter after filling |
session_id | string | No | Browser session name |
Use this for:
- "Confirm password" fields on settings pages
- "Current password" fields when changing passwords
- API key fields in third-party dashboards
list_credentials
Lists stored credentials. Returns service, identifier, type, label — never the values.
delete_credential
Permanently removes a credential from the vault.
Credential Types
| Type | Use Case |
|---|---|
password | Website login passwords |
api_key | Third-party API keys |
totp_secret | 2FA authenticator secrets (auto-generates codes) |
oauth_token | OAuth access/refresh tokens |
cookie | Session cookies |
TOTP Support
Store a TOTP secret alongside a password for sites with 2FA:
{
"service": "github.com",
"identifier": "bot@example.com",
"value": "MyP@ssw0rd",
"totp_secret": "JBSWY3DPEHPK3PXP"
}When Steel Browser detects a one-time password field during login, it auto-generates and fills the current TOTP code.
Namespace Isolation
For multi-user credential separation:
{
"service": "github.com",
"identifier": "fred@example.com",
"value": "fred-password",
"namespace": "agent:fred"
}{
"service": "github.com",
"identifier": "jane@example.com",
"value": "jane-password",
"namespace": "agent:jane"
}When logging in, specify the namespace to use the correct credentials:
login_with_credential(login_url="https://github.com/login", service="github.com", namespace="agent:fred")Threat Model
| Threat | Protection |
|---|---|
| DB dump / SQL injection | Envelope encryption — credentials encrypted with per-org DEK, DEK encrypted with master KEK |
| Agent leaks password in conversation | No MCP tool returns credential values. login_with_credential uses Steel injection with blurred fields |
| Prompt injection extracts secrets | Passwords never enter the conversation context |
| Screenshot captures password | Steel blurs fields immediately after injection |
| One org compromised | Per-org DEK isolation — other orgs unaffected |
| Credential access without authorization | Every access logged in audit trail |
| Expired credentials used | Automatic expiry checking before use |