# Security and CSP

Source: https://docs.revoplyai.com/widget/security-and-csp/

> The Content-Security-Policy directives the widget needs, what it stores in the browser, and how to stop other sites using your channel.

## Content-Security-Policy [#content-security-policy]

On a site with a Content-Security-Policy, allow the widget's script, frame and
configuration request:

```text
script-src  https://widget.revoplyai.com;
frame-src   https://widget.revoplyai.com;
connect-src https://api.revoplyai.com;
```

| Directive     | Why                                                                                                                                                                       |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `script-src`  | `embed.js` itself.                                                                                                                                                        |
| `frame-src`   | The panel is an iframe on the widget's origin.                                                                                                                            |
| `connect-src` | `embed.js` fetches the channel's settings so it can draw the launcher before the panel exists. The panel's own requests fall under the widget origin's policy, not yours. |

The launcher's icons are SVG elements in your page, not images, so `img-src` needs
nothing for the widget.

`embed.js` adds one `<style>` element for the launcher. A policy with `style-src` but no
`'unsafe-inline'` drops the launcher's styling; if you cannot allow inline styles, use the
[iframe](/widget/#iframe) instead of the script tag.

The widget does not use `eval`, loads no web fonts and makes no third-party requests.

## What the widget stores [#what-the-widget-stores]

The panel runs on the widget's origin, not your site's, so it cannot read your site's
storage and your site cannot read its storage.

On the widget's origin:

| Key                                | Holds                                | Kept                         |
| ---------------------------------- | ------------------------------------ | ---------------------------- |
| `revoply_widget_{channelId}`       | Session token and conversation id    | 72 hours after last activity |
| `revoply_widget_draft_{channelId}` | A message the visitor started typing | Until it is sent             |
| `revoply_widget_recent_emoji`      | Recently used emoji                  | Until cleared                |

On your site's origin, written by `embed.js` so the launcher can appear before the panel
loads:

| Key                                  | Holds                                        |
| ------------------------------------ | -------------------------------------------- |
| `revoply_widget_cfg_{channelId}`     | Brand colour, bot name and welcome message   |
| `revoply_widget_engaged_{channelId}` | Whether this visitor has a conversation      |
| `revoply_widget_teaser_{channelId}`  | Whether they closed the greeting bubble      |
| `revoply_widget_open_{channelId}`    | Whether the panel is open (`sessionStorage`) |

None of them hold message content.

## The session token [#the-session-token]

The session token is a bearer token for one conversation, kept in the widget origin's
`localStorage` for up to 72 hours of inactivity. It gives access to that one visitor's
conversation on that browser, and nothing at the account level. It is kept in
`localStorage` rather than a cookie because browsers block third-party cookies in
iframes, which would lose visitors' conversations.

## Restricting which sites may use your channel [#restricting-which-sites-may-use-your-channel]

Your channel id is in the page source of every site that runs the widget, so it can be
copied. In the widget channel's settings, list your domains under **Allowed domains**
(one per line, subdomains included, up to 20); requests for the widget's settings from
any other site are then refused.

This stops another website from running your widget and using your assistant. It is not
a security boundary: the check reads the `Origin` header, which browsers send honestly but
a script outside a browser can set to anything.
