Skip to main content
Version: Browser SDK

CSP, iframes and security requirements

The Screeb tag is a third-party script that runs inside your page. If your site enforces a Content Security Policy, is served inside an iframe, or restricts browser permissions, you need to grant Screeb a small, explicit set of privileges.

This page lists exactly what the tag needs, what happens when a requirement is missing, and which features keep working anyway.

tip

Screeb never requires 'unsafe-eval'. The tag contains no eval, no new Function, and no WebAssembly, so a policy that forbids dynamic code evaluation is fully supported.

Content Security Policy​

The short version​

This policy runs every Screeb feature:

default-src 'self';
script-src 'self' https://*.screeb.app;
connect-src 'self' https://*.screeb.app wss://*.screeb.app https://*.s3.fr-par.scw.cloud;
style-src 'self' https://*.screeb.app;
img-src 'self' data: blob: https://*.screeb.app;
font-src 'self' https://*.screeb.app;
media-src 'self' blob: https://*.screeb.app;
frame-src 'self';

No Screeb origin appears in frame-src: the frames the tag creates carry no src. Add your booking provider's origin there if you use booking questions.

That is the whole policy β€” no 'unsafe-inline' anywhere, for scripts or for styles, provided you install the tag without an inline snippet. See Installing without an inline script.

If you route Screeb through your own domain (see Custom Collector URL), replace *.screeb.app with your proxy hostname in every directive above.

Directives Screeb does not need​

You can leave these as strict as you like:

DirectiveWhy it is not needed
'unsafe-eval'No eval, no new Function, no WebAssembly. Browser capability detection is feature-based.
script-src 'unsafe-inline'Only the optional HTML snippet needs it. The attribute install below has no inline script; neither does the npm SDK.
style-src-attr 'unsafe-inline'The tag writes no style attribute. Everything it styles at runtime goes through CSSOM or its own stylesheet, neither of which CSP governs.
worker-srcThe tag starts no Web Worker, Shared Worker or Service Worker.
object-srcNo object, embed or applet elements.
base-uri, form-actionThe tag renders no form and never rewrites base.
prefetch-srcThe tag issues no prefetch or preload link. Older Screeb documentation asked for this; it is obsolete.
font-src blob:Fonts moved off blob URLs in tag 0.48.1 and are now loaded by URL through the Font Loading API. Also obsolete.

Where Screeb loads from​

OriginUsed forDirectives
https://t.screeb.appLoader tag.js, the versioned feature bundles (core.js, widget.js, editor.js, inspector.js), their stylesheets, webfonts, UI translations, notification sounds, recoloured emojiscript-src, style-src, font-src, connect-src
https://rpc.screeb.appREST API: survey configuration, identity, event capture, session-replay batches, upload authorisationconnect-src
wss://centipede.screeb.appReal-time channel carrying survey questions and answersconnect-src
https://r.screeb.appSDK error and health reportingconnect-src
https://static.screeb.appImages, audio and video attached to your questions, and reading back uploaded answer filesimg-src, media-src
https://emoji.screeb.appEmoji glyphs (SVG)img-src
https://*.s3.fr-par.scw.cloudPre-signed upload of file, voice, video and screenshot answersconnect-src
https://admin.screeb.apppostMessage peer for the survey builder only β€” never fetched from your pagenone

All Screeb origins are HTTPS/WSS only. The tag refuses any non-HTTPS custom endpoint, so a policy containing upgrade-insecure-requests or block-all-mixed-content changes nothing.

What each directive controls​

script-src β€” mandatory​

The loader and the feature bundles are ES modules served from the tag origin. The bundles run inside frames the tag creates on your page; those frames have no src, so they inherit your page's CSP β€” your script-src governs them too.

If missing: nothing loads. The tag reports the failure and every pending $screeb() call rejects quickly instead of hanging. Your page is otherwise unaffected.

connect-src β€” mandatory​

Covers the REST API, the report endpoint, the WebSocket channel, and fetches of the tag's own translations, sounds and stylesheet text.

wss: is a distinct scheme: connect-src https://*.screeb.app does not authorise wss://centipede.screeb.app. List both.

If missing: see the matrix β€” the REST endpoint and the WebSocket have very different blast radii.

style-src​

Widget, editor and inspector CSS ship as real stylesheets from the tag origin. The tag sets no style attribute anywhere β€” anything it styles at runtime (recoloured emoji, widget geometry) is applied through CSSOM, which CSP does not govern β€” so style-src-attr 'unsafe-inline' is never needed.

If missing: the tag degrades in three steps rather than rendering an unstyled panel in front of your users:

  1. A stylesheet link β€” the normal path.
  2. Constructed stylesheets (adoptedStyleSheets) β€” CSSOM, which style-src does not govern, so this works even under style-src 'none'. Requires Chrome 73+, Firefox 101+, Safari 16.4+.
  3. An inline style element β€” for the opposite policy, one that allows 'unsafe-inline' but not the Screeb origin.

Every fallback is logged as an error, because only you can fix the policy. If all three fail β€” an old browser under a policy that refuses both the origin and inline styles β€” no survey, in-app message or feedback button renders at all, while tracking and targeting keep working normally. This is the CSP mistake with the least visible symptoms; the health inspector ranks it first for that reason.

img-src​

Question images and logos (static.screeb.app), emoji (emoji.screeb.app), recoloured emoji (t.screeb.app), data: for the transparent placeholder a masked emoji is painted over, and blob: for the thumbnail of a file or screenshot a respondent has just attached.

If missing: broken images. Text, navigation and answer submission are unaffected.

font-src​

Survey webfonts (Inter, Rubik, Montserrat, your custom brand font…) are declared through the Font Loading API against t.screeb.app/assets/fonts.

If missing: the widget falls back to the system font stack. Everything else works.

media-src​

Audio and video attached to your questions (static.screeb.app), plus blob: for two local-only cases: playing back the voice or video answer a respondent just recorded, and the notification sound.

If missing: media does not play; the survey stays answerable.

frame-src​

Needed only for booking questions, which embed your scheduling tool (Calendly, Microsoft Bookings, HubSpot Meetings…) in an iframe. You must add that provider's origin yourself β€” it is not a Screeb domain, and the tag only ever loads the URL you configured on the question (over HTTPS; anything else is refused before it reaches the frame).

No Screeb origin belongs here. The frames the tag creates for its own bundles carry no src at all β€” they inherit your page's origin and policy, so frame-src 'self' covers them.

If missing: the booking question renders an empty box and cannot be completed. Other questions are unaffected.

Installing without an inline script​

The classic HTML snippet is an inline <script>, which script-src blocks unless you allow 'unsafe-inline'. Four ways around it, best first:

1. Declare the channel id on the script tag. No inline script at all:

<script
async
id="$screeb"
src="https://t.screeb.app/tag.js"
data-channel-id="<website-id>"
></script>

The tag reads data-channel-id off its own element and initialises itself once it is ready. Keep id="$screeb" β€” the tag uses that element to work out which origin it was served from.

This install carries the channel id and nothing else.

If you also call $screeb('identity', …), $screeb('track', …) or pass init options, you still need JavaScript β€” and it must define the small queue stub first. On its own the script tag does not create window.$screeb until the tag has finished loading, so calling it directly races the download and throws $screeb is not a function. The stub queues your calls until the tag is ready:

// `typeof`, not `||`: the loader tag's id="$screeb" makes window.$screeb the
// script element itself until the tag finishes loading, and an element is
// truthy β€” so `||` would keep it and calling it throws.
if (typeof window.$screeb !== "function") {
window.$screeb = function () {
var args = arguments;
return new Promise(function (ok, ko) {
(window.$screeb.q = window.$screeb.q || []).push({ v: 1, args: args, ok: ok, ko: ko });
});
};
}

$screeb('identity', 'user-id', { email: 'support@screeb.app' });

That file is a normal script, so script-src 'self' covers it β€” no inline allowance needed. Do not combine the attribute with your own init call: if the page calls init itself, that call wins and the attribute is ignored.

2. Use the npm package β€” @screeb/sdk-browser (or the React, Angular, Vue, Svelte or Ionic wrapper). Your bundler emits a normal file covered by script-src 'self'.

3. Nonce β€” keep the snippet and add your per-response nonce to it. The loader injects its own scripts with a src, so no nonce propagation is needed:

<script nonce="YOUR_REQUEST_NONCE">
/* Screeb snippet */
</script>

4. Hash β€” compute the SHA-256 of the snippet body and add script-src 'sha256-…'. Remember to recompute it whenever you change the init options.

Subresource Integrity is not supported

https://t.screeb.app/tag.js is a mutable pointer updated on every release, and the loader fetches versioned bundles at runtime without integrity attributes. Adding an integrity attribute to the snippet will break the tag on the next release. Use script-src allowlisting and require-trusted-types-for for supply-chain control instead.

Running inside an iframe​

If your page is embedded in another document β€” a portal, a marketplace shell, a partner site, a native webview β€” that embedder's sandbox attribute and permission policy apply to Screeb too.

sandbox tokens​

TokenNeeded forWithout it
allow-scriptsEverythingThe tag cannot run at all.
allow-same-originEverythingThe frame gets an opaque origin: localStorage throws and anonymous mode refuses to initialise (see Storage requirements). The tag also cannot read the host DOM, so URL, scroll and element targeting stop.
allow-popups and allow-popups-to-escape-sandboxCTA buttons and links that open in a new tabwindow.open returns null; the CTA does nothing.
allow-top-navigation-by-user-activationCTA buttons that navigate the current tabThe tag first tries window.top.location, then falls back to an anchor with target="_top", which this token permits. Without either, the navigation is silently blocked.

allow-forms, allow-modals and allow-downloads are not required: the tag renders no form, never calls alert/confirm/prompt, and triggers no download.

Permissions Policy (the allow attribute)​

Frames the tag creates on your page are same-origin, so they inherit your page's permissions automatically β€” you do not need to annotate them. But if your page is framed, its embedder must delegate:

<iframe
src="https://app.example.com"
allow="microphone; camera; display-capture; fullscreen; clipboard-write"
></iframe>
PermissionScreeb featureWithout it
microphoneVoice answers on open-text questionsThe record button fails; typing still works.
cameraVideo answers on open-text questionsSame β€” the other answer modes still work.
display-captureScreenshot capture on open-text questionsCapture fails; file upload still works.
fullscreenFullscreen button on video questionsVideo plays inline only.
clipboard-write"Copy to clipboard" contextual actionsThe action silently does nothing.
autoplay (optional)Auto-starting video in a questionThe respondent presses play manually.

Cross-origin frames​

When Screeb runs in one origin and the content you target lives in another, the browser blocks all access between them and several targeting rules stop resolving. That is a separate topic with its own page: Cross-origin pages and iframes.

Cross-Origin-Opener-Policy and the survey builder​

The visual survey builder opens your site in a popup and talks to it through window.opener and window.name.

A page that sends Cross-Origin-Opener-Policy: same-origin triggers a browsing-context-group switch that severs that relationship: the builder sees the popup as closed, window.opener is null, and window.name is reset. This is mandated by the specification and cannot be worked around from Screeb's side.

Workarounds, in order of preference:

  1. Use the builder's "Open with URL parameters" action. It bootstraps the editor through the page URL and syncs over the API instead of postMessage, so it is immune to COOP. The builder then reflects your changes on refetch rather than live.
  2. Relax the header to Cross-Origin-Opener-Policy: restrict-properties or same-origin-allow-popups on the pages you edit surveys on.

Cross-Origin-Embedder-Policy: require-corp is not compatible with the tag: Screeb's assets are not served with Cross-Origin-Resource-Policy: cross-origin.

frame-ancestors is not involved β€” the builder uses a popup, never an iframe.

Other security requirements​

Storage requirements​

Screeb sets no cookies, in your page or anywhere else. Identity and display history live in localStorage and sessionStorage on your own origin, so no third-party-cookie or Storage Access permission is involved and browser anti-tracking modes do not affect the tag.

SituationBehaviour
Storage availableNormal operation.
Storage blocked or full, user identified via $screeb('identity', …)Falls back to an in-memory store. The session works, but display history and quotas reset on every page load, so a respondent may see the same survey again.
Storage blocked, anonymous modeinit fails with an explicit error. Anonymous mode needs a persistent identifier β€” without one, every page load would be a new respondent.

Network and transport​

  • All endpoints are HTTPS/WSS. The tag rejects a non-HTTPS custom endpoint at initialisation.
  • API requests are sent in CORS mode and without credentials β€” no cookies and no Authorization header from your session are attached.
  • Requests carry the browser's Origin header, which is what the allowed-domains check below reads. Referrer-Policy does not affect it.

Allowed domains​

Workspaces on a plan with Custom Domains can restrict which origins may talk to the API (Screeb Admin β†’ Settings β†’ Custom Domains). Requests whose Origin is not on the list are rejected with 403 origin not allowed. localhost, 127.0.0.1 and ::1 are always accepted, and requests with no Origin header (mobile SDKs, server-to-server) are not checked.

If you also override the SDK endpoints, the tag validates them against the same list at startup and logs a console warning on mismatch.

Frame messaging trust model​

The tag communicates between the host page and its own frames with postMessage. Incoming messages are accepted only from three origins: your own page, https://t.screeb.app, and https://admin.screeb.app. Everything else is dropped before it is parsed.

Being one of those origins is not enough on its own: each is also bound to the identity it is allowed to claim, so a message from one trusted origin cannot impersonate another part of the tag to reach handlers it should not. The loader always addresses its frames with its own origin, and never trusts an origin supplied inside a message.

Ad blockers and proxying​

Ad blockers commonly block *.screeb.app regardless of your CSP β€” the symptom looks identical to a script-src failure. Enterprise plans can serve the tag and its API from your own domain; see Custom Collector URL and Reverse proxy templates. Remember to update your CSP to the proxied origins when you do.

Feature requirement matrix​

Degrades answers one question: if the requirement is not met, does the rest of Screeb keep working?

FeatureRequirementIf not metDegrades?
Tag loads at allscript-src https://*.screeb.appNothing loads; $screeb() calls reject with a reported error❌ Total β€” but your page is unaffected
HTML snippet installscript-src 'unsafe-inline', a nonce, or a hashThe snippet never executes❌ Total β€” switch to the data-channel-id install, which needs none of them
Survey configuration, targeting, event tracking, identityconnect-src https://*.screeb.appNo configuration is fetched; nothing is captured❌ Total
Answering a survey or in-app messageconnect-src wss://*.screeb.appThe widget displays, then fails to submit once its reconnect budget runs out❌ The survey is unusable
Survey / message renderingstyle-src https://*.screeb.appFalls back to constructed stylesheets, then to an inline style element⚠️ Yes on modern browsers. ❌ On older browsers under style-src 'none': nothing renders while tracking keeps working
Brand webfontfont-src https://*.screeb.appThe system font stack is usedβœ… Cosmetic only
Question images, logos, emojiimg-src https://*.screeb.app data:Broken imagesβœ… The survey stays answerable
Coloured / keycap emojistyle-src https://*.screeb.app β€” the same entry as the widget stylesheetFalls back with the rest of the stylesheetβœ… Needs no directive of its own
Audio and video in questionsmedia-src https://*.screeb.appMedia does not playβœ… Other questions unaffected
Fullscreen videoallow="fullscreen" when your page is framedInline playback onlyβœ…
Notification soundmedia-src blob:Silentβœ…
Voice answersallow="microphone" when your page is framedRecording failsβœ… Typed answers still work
Video answersallow="camera" when your page is framedRecording failsβœ…
Screenshot answersallow="display-capture" when your page is framedCapture failsβœ… File upload still works
Playing back a recorded answer before sendingmedia-src blob:The preview does not playβœ… The answer still uploads
Attachment thumbnailsimg-src blob:No preview thumbnailβœ… The upload still works
Uploading file / voice / video / screenshot answersconnect-src https://*.s3.fr-par.scw.cloudThe upload failsβœ… The rest of the survey completes
Booking questionsframe-src + your booking provider's originEmpty embed❌ For that question only
"Copy to clipboard" actionsallow="clipboard-write" when your page is framedNothing is copiedβœ…
CTA opening a new tabsandbox allow-popups allow-popups-to-escape-sandboxThe CTA does nothingβœ…
CTA navigating the current tabsandbox allow-top-navigation-by-user-activationNavigation is blockedβœ…
Session replayconnect-src https://*.screeb.app and a readable same-origin host pageRecording does not startβœ… Surveys are unaffected
Anonymous respondentslocalStorage writable, sandbox allow-same-origininit fails with an explicit error❌ Identify your users instead
Identified respondentsnone β€” falls back to in-memory storageDisplay history and quotas reset each page loadβœ…
URL, scroll, element and exit-intent targetingSame-origin access to the host pageThe rule never matches⚠️ Time, event and manual triggers still work
Survey builder on your siteNo Cross-Origin-Opener-Policy: same-originPopup detection and live sync are severed⚠️ Use "Open with URL parameters"
SDK error reportingconnect-src https://r.screeb.appScreeb support loses diagnosticsβœ… Invisible to respondents

Checking your setup​

Run this in your browser console on a page where Screeb is installed:

$screeb('debug');

The integration health report probes each directive live β€” API reachability, the WebSocket channel, the widget stylesheet, fonts and media β€” and names the directive to fix for each failure.

CSP violations caused by Screeb are also logged to the console with a link to this documentation. To catch them yourself:

document.addEventListener('securitypolicyviolation', (e) => {
console.log(e.violatedDirective, e.blockedURI);
});

If something still does not add up, the Troubleshooting page covers the non-CSP causes.