Content Security Policy for third-party widgets

A feedback widget needs script-src to load its script, connect-src to send submissions to its API, and style-src to apply its styles. A strict style-src without unsafe-inline can block the widget's injected stylesheet, so test with your real policy before release.

By · Last updated

What a Content Security Policy does to a widget

A Content Security Policy is a response header that tells the browser where a page may load scripts, styles, images and network requests from. Sites use it to block injected code. A third-party feedback widget is exactly the kind of code a policy can stop, because it loads from another domain and talks to that domain when someone sends feedback.

The rules apply per directive. If your policy has no entry that allows the widget, the browser refuses the request and logs the violation. The page keeps working, but the feedback button may do nothing, or the widget may appear without its styles.

Which directives the widget needs

The Escuta Produto widget touches three directives:

Directive Why the widget needs it
script-src To load widget.js from escutaproduto.com with the script tag on your page.
connect-src To send each submission with a fetch call to the API on the same origin as the script.
style-src To apply the styles the widget writes into its own shadow root.

The widget makes no other network request on your page. It loads no fonts, no images and no second script. It uses the system font stack, and its only icon is an emoji in the button text. So if your policy has img-src or font-src rules, you do not need to add escutaproduto.com to them.

The API address is built from the origin of the script tag, so connect-src must include the same origin as script-src. If you load the script from a different host, both entries must match that host, and the submission will go to that host too.

Write the entries for your own domain

Here is a policy that allows the widget and keeps the rest of the page strict. Adjust the 'self' entries to match what your site already needs:

Content-Security-Policy: script-src 'self' https://escutaproduto.com; connect-src 'self' https://escutaproduto.com; style-src 'self' 'unsafe-inline'

If your site sets its policy with a meta tag instead of a header, use the same directives in the content attribute. Meta-tag policies cannot set frame-ancestors, so keep that directive in the header if you use it.

Note that default-src is a fallback. If you have default-src 'self' and no script-src, the browser will block the widget script, because 'self' does not include escutaproduto.com. Add the explicit entries rather than relying on the fallback.

Style-src is the one people miss

The widget writes a style element into its shadow root. A style element is governed by style-src, and under a policy that lacks 'unsafe-inline' the browser blocks it. The widget then shows unstyled elements, or a panel with no layout, while the script itself still runs.

You have three options, in order of preference:

  1. Keep 'unsafe-inline' in style-src if your policy allows it. It is the simplest and the most reliable choice for this widget.
  2. Use a hash. The CSS text depends on the data-position setting, so a hash works only for one position and breaks when the value changes. Hashes are fragile for this widget.
  3. Use a nonce. The widget does not set a nonce on the element it creates, so a nonce in your policy will not allow it.

Be honest about the trade-off. Allowing 'unsafe-inline' for styles loosens the policy, but inline styles are a smaller risk than inline scripts. Your script rules are where the strong protection matters, and those stay strict.

Use a nonce only on your own script tag

Many sites use a nonce to allow their own inline scripts. If your policy has no host allowlist for scripts and relies on nonces alone, the script tag that loads widget.js must carry the nonce, because the widget cannot add one for itself:

<script nonce="YOUR_NONCE" src="https://escutaproduto.com/widget.js" data-key="pk_your_product_key" defer></script>

The widget itself has no inline script and no eval. Everything it does runs from the file it loads, so the nonce on that one tag is enough.

Debug a blocked request

When something fails, open the browser console and read the violation. It names the directive that blocked the request, such as script-src, connect-src or style-src, and the URL. Match that to the table above and add the missing entry.

There are two separate failures that look similar from the visitor's side. A CSP block stops the request before it leaves the browser, so nothing reaches the server. A server refusal arrives as a response. For example, if the page's origin is not on your allowed-origins list, the API answers with a 403, and the widget shows the error message under the send button. The fix there is in the product settings, not in your policy.

Roll out changes in report-only mode first. The header Content-Security-Policy-Report-Only takes the same directives but does not block anything. It logs what would have been blocked, so you can see the widget's requests before you enforce the policy.

Set up the policy for Escuta Produto

Follow this order on a staging copy of your site:

  1. Add escutaproduto.com to script-src and connect-src, and decide how to handle style-src.
  2. Load a page with the widget and open the panel. Check the console for violations.
  3. Send a test message from the panel and confirm it appears in the inbox.
  4. Add your production origin to the allowed origins in the product settings, or the submission will return a 403.
  5. Only then move the policy from report-only to enforced.

Server-side calls to the REST API do not pass through a browser policy, so they need no entry. For setup steps and every attribute, see the widget docs. For the wider question of how a widget is isolated from your page, read why embeddable widgets use Shadow DOM. For the performance cost of loading the script, see how to keep a feedback widget from slowing your site.

Frequently asked questions

Which Content Security Policy entries does a feedback widget need?

Script-src to load the widget file, connect-src to send submissions to the API on the same origin, and style-src to apply the widget's styles. Add the widget's host to the first two, and check the third against your policy.

Why does a feedback widget look unstyled under my policy?

The widget writes its styles into an inline style element. A style-src rule without unsafe-inline blocks that element, so the widget loads but its styles do not apply. Allow inline styles or test the change before you enforce it.

How do I find out what my Content Security Policy is blocking?

Open the browser console on the page and read the violation messages. Each one names the blocked directive and the URL. Running the policy in report-only mode first lets you see the blocks without breaking the page.