Content Security Policy
The SDK works under a strict Content Security Policy. It uses no eval, no new Function, no string timers, no inline event handlers and no injected inline scripts or styles. The one exception is the optional worker probes below: their small worker scripts are built in the page as blob: URLs. The hosted loader does not bypass CSP, consent, ad blockers or browser privacy controls: when a policy blocks the script or the Telltale server connection, collection simply does not happen.
Never add 'unsafe-inline', 'unsafe-eval' or a wildcard source for Telltale. The examples name this Telltale server, https://app.trytelltale.dev. If your pages use a different collector, put its exact origin in every directive instead.
Option 1: nonce with strict-dynamic (recommended)
Generate a fresh, unpredictable nonce for every HTML response. Put it on the SDK tag and on any script that initializes it.
Content-Security-Policy: script-src 'nonce-RANDOM_PER_RESPONSE' 'strict-dynamic'; connect-src 'self' https://app.trytelltale.dev; object-src 'none'; base-uri 'none'<script nonce="RANDOM_PER_RESPONSE" src="https://app.trytelltale.dev/sdk/v1.js"></script>
<script nonce="RANDOM_PER_RESPONSE" src="/telltale-init.js"></script>With 'strict-dynamic', scripts that a nonced script creates are also trusted, so a tag manager that is itself nonced can load the SDK. strict-dynamic does not cover network requests: connect-src must still name the collector origin, because the SDK sends evidence with fetch.
Option 2: exact host allowlist
When you cannot issue per-response nonces, list the exact origins. Keep initialization code in a same-origin file rather than an inline block.
Content-Security-Policy: script-src 'self' https://app.trytelltale.dev; connect-src 'self' https://app.trytelltale.dev; object-src 'none'; base-uri 'none'If your page must keep a small inline initializer, allow that exact script with its SHA-256 hash ('sha256-...') instead of weakening script-src; do not use 'unsafe-inline'.
script-src-elem
If your policy defines script-src-elem, browsers use it instead of script-src for script elements, including the SDK tag and anything a tag manager inserts. Put the same sources in it: the nonce with 'strict-dynamic' for Option 1, or 'self' and the collector origin for Option 2. A policy that sets script-src-elem without them blocks the SDK even when script-src would allow it. script-src-attr is not needed, because the SDK uses no inline event handlers.
Content-Security-Policy: script-src 'nonce-RANDOM_PER_RESPONSE' 'strict-dynamic'; script-src-elem 'nonce-RANDOM_PER_RESPONSE' 'strict-dynamic'; connect-src 'self' https://app.trytelltale.dev; object-src 'none'; base-uri 'none'Optional worker probes
Two SDK probes compare worker and page runtime values. Each start creates two short-lived workers from blob: URLs: a dedicated Worker (navigator values and, where available, WebGL labels) and a SharedWorker (the user agent string). Both scripts are fixed text in the SDK, built in the page; no collector or page data goes into them.
To allow them, add worker-src blob: to your policy (worker-src 'self' blob: if your own pages also start workers). Without a worker-src directive, browsers apply child-src and then script-src to workers, so under Option 1 or Option 2 above both workers are blocked: each probe then records an error label, every other part of collection continues, and the browser reports two CSP violations (worker-src or script-src, blocked URI blob) per page view. Those two reports are expected when you leave the probes blocked. This is the only optional source; nothing else needs relaxing.
Trusted Types. new Worker(url) and new SharedWorker(url) are TrustedScriptURL sinks. With require-trusted-types-for 'script' enforced, both calls are refused and raise Trusted Types violations (in Report-Only mode they are only reported), and the probes record error labels as above. The SDK does not create a Trusted Types policy. Collection and everything else work under Trusted Types, because the SDK uses no other script sink.
Pinning the SDK with Subresource Integrity
The Telltale server serves the browser SDK at three URLs:
| URL | Caching | Use |
|---|---|---|
| https://app.trytelltale.dev/sdk/v1.8cefa3306b522996.js | One year, immutable | Pinned installs with Subresource Integrity. These bytes never change. |
| https://app.trytelltale.dev/sdk/v1.js | Five minutes, then revalidated with its ETag | Always the current v1 build. |
| https://app.trytelltale.dev/sdk.js | Five minutes | The original alias of /sdk/v1.js. Existing tags keep working. |
All three are compressed (Brotli or gzip) and answer HEAD. The current build's immutable URL is also in the Link and X-Observatory-SDK response headers of /sdk/v1.js (a historical name, stable and kept on purpose so existing installs keep working; see Renamed names).
To pin the current build, use its hashed URL with the integrity value below. crossorigin="anonymous" is required: the browser checks integrity only on a CORS response, and the SDK files are served with Access-Control-Allow-Origin: * and no credentials.
<script src="https://app.trytelltale.dev/sdk/v1.8cefa3306b522996.js" integrity="sha384-CkbHPkBjjOqi54cng1gnrGPJKK1pEdkMAN7GZ9mznQ9vpJNrRoUD3dPHG0YGh2ei" crossorigin="anonymous" data-telltale-key="YOUR_SITE_PUBLISHABLE_KEY"></script>A pinned page keeps running the exact build you reviewed until you change the tag. Earlier builds stay available at their hashed URLs after the Telltale server is upgraded, so an upgrade never breaks a pinned page. Do not put an integrity attribute on /sdk/v1.js or /sdk.js: their bytes change when the Telltale server is upgraded, and the browser would then refuse the script.
The CSP sources do not change: the hashed URL is on the same collector origin.
Hosted loader under CSP
The single-tag loader reads its data attributes, or the pre-loader window.TelltaleConfig object, from document.currentScript. Both work under either policy above. The configuration object must be defined by a script your policy already trusts (a nonced, hashed or same-origin file), never through an inline handler.
Checking the policy
Load a page with the browser console open and confirm there are no CSP violation reports for sdk.js or the collector origin, other than the two expected blob: worker reports when you have not allowed worker-src blob: (and the matching Trusted Types reports when you enforce Trusted Types). Use Content-Security-Policy-Report-Only first if you are introducing a policy. A blocked collector is visible as a refused connection, not a verdict; unknown is not human.