The threat model
Three things can go wrong with configuration delivered to browsers:
- Someone with legitimate access ships something harmful — a custom HTML tag that exfiltrates form data, a "temporary" script that stays for years.
- The delivery path is tampered with — a compromised CDN edge, a misconfigured proxy, a man-in-the-middle on a hostile network.
- Nobody can tell what changed — the live configuration drifts from what was reviewed.
Declarative templates close the first door
Track has no custom HTML or custom JavaScript tag type. Every browser-side action is a template from a fixed set (Meta pixel, Google tag, TikTok pixel, UET, Insight Tag, and so on) with typed parameters: ids, event mappings, consent purposes. A template cannot contain code; it selects a loader the SDK already ships and fills in identifiers. The configuration bundle is data, validated against a schema on the server and again on the client. There is no eval, no new Function, and nothing in a bundle can make the SDK fetch a script from a URL the template does not already know.
That is a deliberate loss of flexibility. The migration guide lists what has no equivalent; the answer is always "put that logic in the site's own code, where it is reviewed like code".
Signing closes the second
When a version is published, the server serialises the bundle canonically, computes its SHA-256 digest and signs the bytes with an Ed25519 private key that exists only on the publishing service. The published artefact is { payload, digest, keyId, algorithm, signature }.
The SDK ships with the public keys for the site's key ids. Before applying a bundle it recomputes the digest, checks it against the signed one, and verifies the signature with Web Crypto (SubtleCrypto.verify with Ed25519). A bundle that fails verification is discarded and the SDK keeps its last verified bundle from session cache — or runs with no destinations at all rather than an unverified configuration.
Signing does not replace TLS; it protects against everything TLS does not, including a compromised edge that serves valid-looking JSON over a valid certificate.
The manifest
The loader first fetches a small manifest: tracking id, environment, version, bundle URL, digest, key id, published-at timestamp and the kill switch flag. The manifest is fetched fresh; the bundle is cached in session storage by version and digest. This is what lets a kill switch or a rollback take effect on the next page view without cache-busting the bundle itself.
Versioning closes the third
Every publish creates an immutable version with a diff against its predecessor, the actor, the approval that authorised it and the request id. The version history shows exactly what visitors receive, and rollback republishes an earlier version as a new one — signed again, audited again. Drafts and published versions never share storage, so "what is live" is a single fact, not a state of several toggles.
Key management
- Signing keys are generated per environment and never leave the publishing service; the private key is never stored in the database.
- Key ids allow rotation: a new key is added, new versions are signed with it, the old public key remains valid for verification until every site has republished, then it is retired.
- The
key_idon every manifest tells you which key signed the live configuration.
What this means for your security review
You can describe the tag manager to a security team in one paragraph: it delivers signed, schema-validated data to a fixed SDK; it cannot execute site-authored code; every change is versioned, approved and attributable; and a compromise of the delivery path cannot alter behaviour without the signing key. The security page links the current key ids and the SDK's verification source.