Reference

Privacy engineering

What the browser script needs, what it stores and when, and what it sends.

Content Security Policy

The script talks only to its own origin. Add these sources to your policy:

script-src https://nolgard.com;
connect-src https://nolgard.com;
worker-src blob:;
  • connect-src: the script calls /v1/challenge and /v1/collect, and /v1/stepup/* or /v1/appeal only if you call stepUp() or appeal(). All requests send no cookies.
  • worker-src blob:: one short-lived worker built from a blob URL reads browser and GPU details. If your policy blocks it, collection still works without those signals.
  • No style-src, img-src or frame-src entry is needed. The script sets styles through the DOM style API, draws on canvases it never loads images into, and uses a hidden iframe with no src. It does not use eval.

Nonces. With a nonce-based policy, put your nonce on the tag: <script nonce="..." src="https://nolgard.com/v1/client.js" data-site-key="SITE_KEY" defer>. With 'strict-dynamic', host sources are ignored, so the nonce is required; a script you insert from a nonced script is trusted too.

When the client writes storage

Measured in Chrome on 25 October 2026:

MomentCookielocalStorageIndexedDB
Script loaded, Nolgard.ready resolvednonenonenone
Page idle, no execute()nonenonenone
After the first execute() or gate()__nf_vid__nf_viddatabase __nf, store v

The value is one random visitor id, the same in all three places. The cookie is first-party on your domain, Path=/, SameSite=Lax, Secure on HTTPS, 400 days, renewed when used. A missing copy is restored from the others. sessionStorage is never used. The id lets us recognize the same browser at the next signup after the user clears one store or changes email.

No-storage mode

Add data-storage="none" to the script tag:

<script src="https://nolgard.com/v1/client.js" data-site-key="SITE_KEY" data-storage="none" defer></script>

The script then reads and writes no cookie, localStorage or IndexedDB, and sends no visitor id. Linking falls back to the device fingerprint and the email. Repeat signups are caught less often: a user who comes back in the same browser with a new email links only if the fingerprint matches.

Pinning the script with SRI

/v1/client.js changes with each release and is cached for 5 minutes. To pin it, read GET https://nolgard.com/v1/client-manifest.json. It returns {"url":"/v1/client.<hash>.js","integrity":"sha384-..."}. The hashed file is served with immutable caching and CORS, so <script src="https://nolgard.com/v1/client.<hash>.js" integrity="sha384-..." crossorigin="anonymous"> works.

A pinned build keeps loading, but verify only accepts tokens from the current and the previous release. Refresh the pinned URL and hash from the manifest on every Nolgard release, or keep using /v1/client.js.

Global Privacy Control

The script does not change its behaviour when the browser sends GPC. GPC is a signal to opt out of the sale or sharing of personal data. We do not sell visitor data and do not use it for advertising. A farm could also switch GPC on to remove the visitor id. If you want to honour GPC with less storage, read navigator.globalPrivacyControl and add data-storage="none" when it is true.

Load on form focus

To keep the script off pages and visitors that never start a signup, insert it when the form gets focus:

const form = document.querySelector('#signup');
form.addEventListener('focusin', () => {
  const s = document.createElement('script');
  s.src = 'https://nolgard.com/v1/client.js';
  s.dataset.siteKey = 'SITE_KEY';
  document.head.append(s);
}, { once: true });

The script makes one /v1/challenge request when it loads and starts recording interaction timing from that moment, so focus is the latest point to load it. Before submit, wait for window.Nolgard to exist, then call execute(). If it never appears, send the form without a token and follow the missing-token path below.

Single-page apps

Load the script once per page load. It survives route changes, and window.Nolgard stays the same object. Call execute() at each submit, not at route change: a token is single-use and expires after 5 minutes. Do not insert the script again on every route.

Ad blockers and Tor

Ad blockers, Brave Shields and Tor Browser at the Safest level can block the script. The form then has no token, and verify returns missing-token. That is not a bot verdict. Handle it as described in the error table: email confirmation, then a reduced grant.

Page URLs and referrers

On each execute() or gate(), the script sends the full page URL (location.href, path and query) and document.referrer, each up to 2,048 characters. They are stored with the event and deleted with it, see retention.

On sites where a URL can reveal health, sexuality, religion or other special category data (GDPR Article 9), keep that out of the URL where you call execute():

  • Run the check on a neutral signup URL, not on a URL with a profile, condition or search term in it.
  • Remove sensitive query parameters with history.replaceState before calling execute().
  • Keep your Referrer-Policy at strict-origin-when-cross-origin or stricter, so links from other sites arrive as an origin only. Same-site referrers still carry the full path.
  • Never put emails or other personal data in action names or URLs.