# Allowed sites and security

Allowed sites, Content Security Policy lines, what the widget stores and what OneStar keeps.

A form takes answers only on the sites you list for it. This page covers that list, the Content Security Policy lines your site may need, what the widget keeps in a visitor's browser and what OneStar keeps about each answer.

## Allowed sites

On the form's **Install** tab, **Allowed sites** lists the origins whose pages may show the form and send answers to it. A form lists up to 20.

- An origin is a scheme, a host and a port, with no path: `https://example.com`, `https://app.example.com`, `https://example.com:8443`. Each subdomain is its own entry, and wildcards are not accepted.
- Sites must use `https`. The one exception is your own machine: `http://localhost` and `http://127.0.0.1` on any port. Typing `localhost:3000` with no scheme means `http://localhost:3000`.
- Answers from `localhost` and `127.0.0.1` are always test answers, even on a live form: the form shows a Test ribbon, and the answers never count in Results or toward your monthly answers. They are removed after a week. So you can leave `localhost` on the list while you build. See [Testing](https://onestar.sh/docs/feedback/testing.md).
- With no sites listed, only the form's own page on OneStar (its link or QR code) takes answers.

The form's own page at `onestar.sh/f/<slug>` and the page for apps need no entry here; they need the form to be shared by link or publicly on its **Share** tab.

### Pages on other sites

A page on a site that is not on the list never shows the form:

- An inline form hides itself. An overlay closes once opened, and the popover's floating tab goes away.
- The element fires `onestar:error` with the code `origin_refused` and the refused origin ([Events and errors](https://onestar.sh/docs/feedback/events.md)).
- The browser console names the origin to add: `OneStar: form f_hemsz66yupbd76d2 does not allow https://example.com. Add https://example.com under Install, Allowed sites in OneStar.`

OneStar also lists recent sites like that on the **Install** tab, under "Tried to show this form but not on the list", so you can add yours with one click. Each entry is only an origin, as the browser named it; nothing about the visitor or the page is kept, and a site not seen for a month drops off the list. Because a request from outside a browser can name any site, treat the list as a hint and add only sites you know are yours.

### The Any site switch

**Any site** lets every site show your form and send answers into your results. Leave it off unless the form is meant to be put on other people's pages. With it on, anyone can copy your snippet onto a page of theirs and fill your results with answers you never asked for. Answers still record which site they came from, so you can filter them, but they count all the same.

### What the list protects

Allowed sites stop browsers on other sites, which is what a stranger's page would use. A script running outside a browser can claim to be any site, so the list alone does not stop it. Limits per network address and per form bound what such a script can send, and the checks below drop most of what it tries.

### Framing the form's own page

The form's own page on OneStar may be shown in a frame only by the sites on its allowed list. The **Any site** switch does not widen this, and a form that is not shared has no page to frame.

## Content Security Policy

If your site sends a Content Security Policy, allow the script host and the API host:

```text CSP
script-src https://embed.onestar.sh
connect-src https://api.onestar.sh
```

No `style-src` change is needed in current browsers: the form's styles are built as stylesheets inside its shadow root, never inline style attributes. A browser without that feature gets a style element inside the shadow root instead, which a strict `style-src` refuses.

When the widget code cannot load (a `script-src` without `https://embed.onestar.sh`, an ad blocker, a proxy), the console says so and the element fires `onestar:error` with `load_failed`. When the API cannot be reached (often a `connect-src` without `https://api.onestar.sh`), it fires `network`.

## What the widget stores in the browser

Nothing is written to the visitor's browser before they interact with the form. After that the widget keeps three kinds of entry in the browser's local storage, under your site's origin:

| Key | Written when | Holds |
|---|---|---|
| `onestar:device` | The person first picks or types an answer | A random device key, made in the browser. It means nothing outside your site and holds nothing about the person. |
| `onestar:asked:<form>` | An answer to that form is saved | A marker, so a later visit may look for replies to it. |
| `onestar:answered:<form>` | An answer is finished, on a form that allows one answer per person | The form version, so the form shows its thank you instead of asking again. |

No cookies, and nothing else. Every request to OneStar is sent without cookies or other credentials. When the browser blocks storage (some private modes, sandboxed frames), the widget keeps these in memory for the page view instead, and the form still works.

The loader reads the device key and the asked marker on load, but only for a popover, slide-in or modal with a button, to put the unread dot on it. It never creates either one.

## What an answer carries

Besides the answers themselves, each answer sends:

- the form's version and how it was shown (its mode and any placement name),
- the device key, when the browser has one,
- the page's path, without its query string or fragment, which can hold tokens and email addresses,
- only the origin of the referring page, never its full address,
- the browser's language,
- anything your page added with `onestar.set` ([Context and placements](https://onestar.sh/docs/feedback/context-and-placements.md)),
- your signed token, for forms for signed in users ([Signed in users](https://onestar.sh/docs/feedback/identify-users.md)).

The visitor's network address is kept only as a one way hash, for a day, to apply same day limits, then removed from the answer.

## What OneStar keeps

- **Contact details** are kept only when the form asks for them (an email or an identifier you name, such as a Player ID) (for a form of signed in users, the email in your [signed token](https://onestar.sh/docs/feedback/identify-users.md), when the form is set to keep it). They are sealed, shown to no one but you and the person who left them, never on a public page, and never in events, webhooks or anything sent for analysis.
- **Written answers** are stored as written, for you. Personal details such as email addresses and phone numbers are scrubbed out before any text is analyzed.
- **Loads** of the form are counts only: the day, the site the browser named, the mode and the placement. Nothing about the visitor, and nothing stored in their browser. The site is not checked, and Results says so.

## Checks on every answer

- The form hands out a short lived token each time it loads, and an answer needs one. An answer from a page that never loaded the form is refused, and the widget offers to reload a form left open for very long.
- An answer sent sooner after loading than a person could send it is refused once; the widget waits a moment and sends it again.
- A hidden field that people never see catches simple bots. An answer with it filled in is dropped quietly.
- Each answer is sent with a key of its own. When a send fails on the network, the widget sends the identical answer once more with the same key, so a retry never counts twice.
- Answers are checked against the live version of the form: unknown questions, answers to questions that were not shown and options the question does not have are refused with a plain message for each.

## Custom CSS

Custom CSS (Max) runs inside the form's shadow root on your own site only. It cannot import files, load URLs or use backslash escapes, and it is never applied on the form's own page on OneStar or on the page for apps. See [Theming](https://onestar.sh/docs/feedback/theming.md).
