Security
BundleLLM is designed so that user API keys never touch your servers.
Architecture
Your Site (SDK) → LLM Provider API
↑ ↑
Your code User's key
(no keys) (in their browser)
The SDK runs on your page and calls LLM providers directly from the user’s browser. API keys are stored in the user’s localStorage. they never pass through your server or BundleLLM’s servers.
HTTPS Requirement
The SDK requires HTTPS in production. On non-HTTPS origins (excluding localhost), the SDK logs a console warning because API keys stored in localStorage may be exposed over insecure connections. The analytics API rejects events from non-HTTPS origins.
Key Storage
- API keys are stored in the browser’s localStorage on your domain
- Keys are validated against the provider API before storing
- Users can clear their key at any time by clicking “Disconnect”
- Clearing browser site data also removes stored keys
- Site owners can set
sessionTTLto auto-expire stored credentials after a duration (see SDK Reference)
API Key Validation
When a user enters an API key, the SDK validates it before connecting:
- Anthropic:
POST /v1/messageswith minimal payload - OpenRouter:
GET /api/v1/auth/key
Invalid keys are rejected immediately. A 429 (rate limited) response is treated as valid. The key works, the user is just rate limited.
OAuth Security
For OpenRouter OAuth, the SDK opens a popup to our OAuth redirect handler:
- SDK generates PKCE code verifier + challenge
- Popup redirects to OpenRouter’s auth page
- User authorizes, OpenRouter redirects back with a code
- Our server exchanges the code for an API key via PKCE
- The key is returned to the SDK popup via
postMessage(origin-restricted) - Our server never stores the key
Protections:
- PKCE (S256) prevents authorization code interception
- postMessage origin validation. Key only sent to the opener’s verified origin
- XSS protection. All values escaped in the callback HTML
- Rate limiting. 10 OAuth starts per minute per IP
- State expiry. PKCE state tokens expire after 5 minutes
Response Rendering
The chat widget renders LLM responses as formatted HTML (markdown). To prevent injection from LLM output:
- All response text is HTML-escaped before markdown processing
- Links are restricted to absolute
http:andhttps:URLs only.javascript:,data:, relative paths, and other protocols are stripped. - Links include
rel="noopener noreferrer"to preventwindow.openeraccess and Referer leakage - Inline code spans are isolated before bold/italic/link processing to prevent formatting collisions
Markdown rendering can be disabled with renderChat('#chat', { markdown: false }), which uses textContent instead of innerHTML.
What the SDK Can Do
- Show a provider picker for users to connect
- Store the user’s API key in their browser
- Send chat messages directly to the provider
- Display streaming responses with markdown formatting and token usage
What the SDK Cannot Do
- Access API keys from JavaScript on your page (stored in localStorage under a specific key)
- Send keys to BundleLLM servers (the OAuth server only handles the exchange, never stores keys)
- Make requests without the user’s explicit connection
Site Owner Obligations
Per the Terms of Service:
- Do not intercept, log, or transmit users’ API keys
- Provide a disconnect button
- Display token usage per message
- Show which provider the user is connected to
Reporting Issues
If you find a security vulnerability, please email [email protected].