> ## Documentation Index
> Fetch the complete documentation index at: https://docs.karta.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat widget security & privacy

> The CSP directives your page needs, the origin-allowlist and abuse model, and the data path for the embeddable chat widget.

This page covers the security and privacy model for the [embeddable chat
widget](/sdks/widget/quickstart): what your page's Content-Security-Policy needs,
how access is gated, the abuse controls, and where chat data goes.

## Content-Security-Policy

If your site sends a CSP, allow the widget the following. These assume the
default runtime origin `https://agent.karta.sh`; if you set a custom
`data-base-url`, use that origin instead.

```
script-src  https://cdn.karta.sh;
connect-src https://agent.karta.sh;
frame-src   https://agent.karta.sh;
```

What each is for:

* **`script-src https://cdn.karta.sh`** - the async loader (`karta.js`) and the
  code it lazily imports are served from the CDN.
* **`connect-src https://agent.karta.sh`** - from your page the loader `fetch`es
  the embed token mint (`/v1/embed/session-tokens`) and runtime config
  (`/v1/embed/config`) from the runtime origin. Use your `data-base-url` origin
  here if you override it.
* **`frame-src https://agent.karta.sh`** - the widget UI runs in a cross-origin
  iframe served from the runtime origin. The streaming session traffic runs
  inside that iframe against its own origin, so it needs nothing more from your
  page's CSP.

<Note>
  The widget UI is isolated in a cross-origin iframe, so its styles and scripts
  are scoped to the iframe's own document and never touch your page - you do not
  need `style-src 'unsafe-inline'` for the widget. It also uses no `eval` or
  `new Function`, so no `script-src 'unsafe-eval'` is needed.
</Note>

## Origin allowlist

The publishable `pk_live_...` embed key is meant to ship in your page's HTML - it
is not a secret. Its blast radius is fenced by the **allowed-origins allowlist**,
not by hiding the key.

* The browser sends its real `Origin` header on the token mint. Karta reads
  **that header** (never a body-supplied value, which a hostile page could
  spoof) and refuses the mint if the origin is not on the key's allowlist.
* The match is exact on scheme + host + port. There are no wildcards - list each
  origin you embed on.
* Rotating or disabling the key on the [Embed tab](/sdks/widget/theming) cuts off
  token minting immediately.

## Hosted agent pages

The public `/{org}/{agent}` and `/{org}/{agent}/embed` pages are served from the
agent origin. They are top-level or iframe documents, so they do not depend on
JavaScript from your page.

When a hosted page runs without an explicit `?key=pk_live_xxx`, it gets runtime
config from `/v1/embed/first-party-config` and mints a browser-safe session token
through `/v1/embed/first-party-token` on the same origin. Those endpoints are for
Karta-hosted pages. For your own page, use a publishable embed key with
`/v1/embed/session-tokens`, or mint a [session token](/security/session-tokens)
from your backend.

## The safety model

The widget never holds a long-lived credential, and anonymous traffic is bounded:

* **Short-lived session tokens.** The embed key is exchanged for a short-lived,
  agent-scoped [session token](/security/session-tokens) per session. The
  browser only ever holds that expiring token, never an API key.
* **Anonymous spend cap.** Each key has a hard ceiling on what anonymous
  (non-[verified](/sdks/widget/identity)) traffic can cost per period, set on the
  Embed tab. On hit, minting stops.
* **Rate limits.** The token-mint edge is rate-limited per IP, as a backstop
  against a key being hammered from one source.

### Graceful limits

When a usage, spend, or rate limit is hit, the widget does not show a raw error.
It shows a calm "check back later" state, **locks the composer** so the user
cannot keep hammering a capped agent, and emits a first-class
[`limit_reached` event](/sdks/widget/command-api#events) (with an optional
`retryAfterSeconds`) so your page can respond - for example, surfacing a contact
form. A hard cap has no client-side recovery within the page's lifetime; the
limit clears server-side on its period boundary.

## Privacy & data path

Be accurate with your users about where their chat goes:

* **Chat content reaches Karta and the model provider.** Messages the end user
  types are sent to Karta and on to the model provider that backs the agent (your
  own, under [BYOK](/platform/byok), or Karta's). See
  [Trust & compliance](/security/trust#operator-access) for operator-access
  guidance.
* **Soft identity is advisory.** A soft `userId` is metadata, never an auth
  credential, and any page can set it. Use
  [verified identity](/sdks/widget/identity) to bind a trusted subject, and a
  signed [`context` bag](/sdks/widget/identity#telling-the-agent-about-your-user)
  when the agent needs facts about the user it can rely on.
* **The optional pre-chat consent string.** The widget can show a
  [pre-chat disclosure](/sdks/widget/theming#pre-chat-consent) before the first
  message - with an optional Accept gate - to tell users the chat is AI-assisted
  and link your privacy policy. The disclosure text is sanitized through an
  escape-first renderer, so it is safe to author but cannot contain executable
  markup.

### What you should disclose

As the site embedding the widget, you are the data controller for your users'
chats. Disclose, per your jurisdiction, that:

* conversations are processed by an AI agent and sent to Karta and a model
  provider to generate replies;
* what you log or retain on your side. The widget keeps no chat transcript in
  the page; conversation continuity comes from server-side session state, so a
  reload can resume the session;
* how a user can reach a human, if you wire the
  [`escalate`](/sdks/widget/command-api#events) hook.

The built-in [pre-chat consent string](/sdks/widget/theming#pre-chat-consent) is
one place to surface this.

## Related

<CardGroup cols={2}>
  <Card title="Session tokens" icon="ticket" href="/security/session-tokens">
    The short-lived, agent-scoped credential the embed key is exchanged for.
  </Card>

  <Card title="Identity" icon="user-check" href="/sdks/widget/identity">
    Anonymous, soft, and verified (HMAC) end-user identity.
  </Card>

  <Card title="Trust & compliance" icon="shield-halved" href="/security/trust">
    The platform's broader security posture and operator access.
  </Card>

  <Card title="BYOK" icon="key" href="/platform/byok">
    Keep your model-provider credentials under your own control.
  </Card>
</CardGroup>
