diff --git a/crates/js/lib/src/integrations/sourcepoint/index.ts b/crates/js/lib/src/integrations/sourcepoint/index.ts new file mode 100644 index 00000000..017494d1 --- /dev/null +++ b/crates/js/lib/src/integrations/sourcepoint/index.ts @@ -0,0 +1,8 @@ +import { log } from '../../core/log'; + +import { installSourcepointGuard } from './script_guard'; + +if (typeof window !== 'undefined') { + installSourcepointGuard(); + log.info('Sourcepoint integration initialized'); +} diff --git a/crates/js/lib/src/integrations/sourcepoint/script_guard.ts b/crates/js/lib/src/integrations/sourcepoint/script_guard.ts new file mode 100644 index 00000000..21eaea12 --- /dev/null +++ b/crates/js/lib/src/integrations/sourcepoint/script_guard.ts @@ -0,0 +1,53 @@ +import { createScriptGuard } from '../../shared/script_guard'; + +const SOURCEPOINT_CDN_HOST = 'cdn.privacy-mgmt.com'; + +function normalizeSourcepointUrl(url: string): string | null { + if (!url) return null; + + const trimmed = url.trim(); + if (!trimmed) return null; + + if (trimmed.startsWith('//')) return `https:${trimmed}`; + if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) return trimmed; + + // Bare domain or path — attempt to parse as https URL. + // The host === check in isSourcepointUrl rejects non-matching domains. + return `https://${trimmed}`; +} + +function parseSourcepointUrl(url: string): URL | null { + const normalized = normalizeSourcepointUrl(url); + if (!normalized) return null; + + try { + return new URL(normalized); + } catch { + return null; + } +} + +export function isSourcepointUrl(url: string): boolean { + const parsed = parseSourcepointUrl(url); + return parsed?.host === SOURCEPOINT_CDN_HOST; +} + +export function rewriteSourcepointUrl(originalUrl: string): string { + const parsed = parseSourcepointUrl(originalUrl); + if (!parsed) return originalUrl; + + const query = parsed.search || ''; + + return `${window.location.origin}/integrations/sourcepoint/cdn${parsed.pathname}${query}`; +} + +const guard = createScriptGuard({ + displayName: 'Sourcepoint', + id: 'sourcepoint', + isTargetUrl: isSourcepointUrl, + rewriteUrl: rewriteSourcepointUrl, +}); + +export const installSourcepointGuard = guard.install; +export const isGuardInstalled = guard.isInstalled; +export const resetGuardState = guard.reset; diff --git a/crates/js/lib/test/integrations/sourcepoint/script_guard.test.ts b/crates/js/lib/test/integrations/sourcepoint/script_guard.test.ts new file mode 100644 index 00000000..c075600a --- /dev/null +++ b/crates/js/lib/test/integrations/sourcepoint/script_guard.test.ts @@ -0,0 +1,82 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { + installSourcepointGuard, + isGuardInstalled, + isSourcepointUrl, + resetGuardState, + rewriteSourcepointUrl, +} from '../../../src/integrations/sourcepoint/script_guard'; + +describe('Sourcepoint SDK Script Interception Guard', () => { + let originalAppendChild: typeof Element.prototype.appendChild; + let originalInsertBefore: typeof Element.prototype.insertBefore; + + beforeEach(() => { + resetGuardState(); + originalAppendChild = Element.prototype.appendChild; + originalInsertBefore = Element.prototype.insertBefore; + }); + + afterEach(() => { + resetGuardState(); + }); + + it('detects Sourcepoint CDN URLs', () => { + expect(isSourcepointUrl('https://cdn.privacy-mgmt.com/wrapper/v2/messages')).toBe(true); + expect(isSourcepointUrl('//cdn.privacy-mgmt.com/mms/v2/get_site_data')).toBe(true); + expect(isSourcepointUrl('cdn.privacy-mgmt.com/consent/tcfv2')).toBe(true); + expect(isSourcepointUrl('https://example.com/script.js')).toBe(false); + expect(isSourcepointUrl('https://geo.privacymanager.io/')).toBe(false); + }); + + it('rejects subdomain-spoofing URLs', () => { + expect(isSourcepointUrl('cdn.privacy-mgmt.com.evil.com/script.js')).toBe(false); + expect(isSourcepointUrl('https://cdn.privacy-mgmt.com.evil.com/')).toBe(false); + expect(isSourcepointUrl('notcdn.privacy-mgmt.com/path')).toBe(false); + }); + + it('rewrites CDN URLs to the first-party proxy path', () => { + expect(rewriteSourcepointUrl('https://cdn.privacy-mgmt.com/wrapper/v2/messages?env=prod')).toBe( + `${window.location.origin}/integrations/sourcepoint/cdn/wrapper/v2/messages?env=prod` + ); + }); + + it('installs and resets the guard', () => { + expect(isGuardInstalled()).toBe(false); + installSourcepointGuard(); + expect(isGuardInstalled()).toBe(true); + expect(Element.prototype.appendChild).not.toBe(originalAppendChild); + expect(Element.prototype.insertBefore).not.toBe(originalInsertBefore); + resetGuardState(); + expect(Element.prototype.appendChild).toBe(originalAppendChild); + expect(Element.prototype.insertBefore).toBe(originalInsertBefore); + }); + + it('rewrites dynamically inserted Sourcepoint scripts', () => { + installSourcepointGuard(); + + const container = document.createElement('div'); + const script = document.createElement('script'); + script.src = 'https://cdn.privacy-mgmt.com/wrapperMessagingWithoutDetection.js'; + + container.appendChild(script); + + expect(script.src).toContain( + '/integrations/sourcepoint/cdn/wrapperMessagingWithoutDetection.js' + ); + expect(script.src).not.toContain('cdn.privacy-mgmt.com'); + }); + + it('does not rewrite unrelated scripts', () => { + installSourcepointGuard(); + + const container = document.createElement('div'); + const script = document.createElement('script'); + script.src = 'https://example.com/app.js'; + + container.appendChild(script); + + expect(script.src).toBe('https://example.com/app.js'); + }); +}); diff --git a/crates/trusted-server-core/src/integrations/mod.rs b/crates/trusted-server-core/src/integrations/mod.rs index 92f30219..29657166 100644 --- a/crates/trusted-server-core/src/integrations/mod.rs +++ b/crates/trusted-server-core/src/integrations/mod.rs @@ -16,6 +16,7 @@ pub mod nextjs; pub mod permutive; pub mod prebid; mod registry; +pub mod sourcepoint; pub mod testlight; pub use registry::{ @@ -37,6 +38,7 @@ pub(crate) fn builders() -> &'static [IntegrationBuilder] { permutive::register, lockr::register, didomi::register, + sourcepoint::register, google_tag_manager::register, datadome::register, gpt::register, diff --git a/crates/trusted-server-core/src/integrations/sourcepoint.rs b/crates/trusted-server-core/src/integrations/sourcepoint.rs new file mode 100644 index 00000000..f155c0a5 --- /dev/null +++ b/crates/trusted-server-core/src/integrations/sourcepoint.rs @@ -0,0 +1,902 @@ +//! Sourcepoint integration for first-party CMP (Consent Management Platform) delivery. +//! +//! Proxies Sourcepoint's CDN (`cdn.privacy-mgmt.com`) through Trusted Server so +//! the browser loads consent management assets from first-party paths. +//! +//! ## Rewriting layers +//! +//! | Layer | Mechanism | What it catches | +//! |-------|-----------|-----------------| +//! | HTML attributes | `IntegrationAttributeRewriter` | Static `", + ), + cdn_host = SOURCEPOINT_CDN_HOST, + cdn_prefix = SOURCEPOINT_CDN_PREFIX, + )] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::integrations::{IntegrationDocumentState, IntegrationRegistry}; + use crate::test_support::tests::create_test_settings; + use fastly::http::Method; + use serde_json::json; + + fn config(enabled: bool) -> SourcepointConfig { + SourcepointConfig { + enabled, + rewrite_sdk: true, + cdn_origin: default_cdn_origin(), + cache_ttl_seconds: default_cache_ttl(), + } + } + + #[test] + fn strips_cdn_prefix_from_routes() { + assert_eq!( + SourcepointIntegration::strip_cdn_prefix( + "/integrations/sourcepoint/cdn/wrapper/v2/messages" + ), + Some("/wrapper/v2/messages") + ); + assert_eq!( + SourcepointIntegration::strip_cdn_prefix("/integrations/sourcepoint/cdn"), + Some("/") + ); + assert_eq!( + SourcepointIntegration::strip_cdn_prefix("/some/other/path"), + None + ); + } + + #[test] + fn rewrites_cdn_urls_to_first_party_paths() { + let integration = SourcepointIntegration::new(Arc::new(config(true))); + let ctx = IntegrationAttributeContext { + attribute_name: "src", + request_host: "edge.example.com", + request_scheme: "https", + origin_host: "origin.example.com", + }; + + let rewritten = integration.rewrite( + "src", + "https://cdn.privacy-mgmt.com/mms/v2/get_site_data?account_id=821", + &ctx, + ); + + assert_eq!( + rewritten, + AttributeRewriteAction::replace( + "https://edge.example.com/integrations/sourcepoint/cdn/mms/v2/get_site_data?account_id=821", + ) + ); + } + + #[test] + fn leaves_non_sourcepoint_urls_unchanged() { + let integration = SourcepointIntegration::new(Arc::new(config(true))); + let ctx = IntegrationAttributeContext { + attribute_name: "src", + request_host: "edge.example.com", + request_scheme: "https", + origin_host: "origin.example.com", + }; + + assert_eq!( + integration.rewrite("src", "https://example.com/script.js", &ctx), + AttributeRewriteAction::keep() + ); + } + + #[test] + fn rewrites_quoted_cdn_urls_to_root_relative_paths() { + let input = r#"var fallback="https://cdn.privacy-mgmt.com";var api="https://cdn.privacy-mgmt.com/consent/tcfv2";"#; + let output = SourcepointIntegration::rewrite_script_content(input); + + assert_eq!( + output, + r#"var fallback="/integrations/sourcepoint/cdn";var api="/integrations/sourcepoint/cdn/consent/tcfv2";"# + ); + } + + #[test] + fn rewrites_protocol_relative_cdn_urls() { + let input = r#"url="//cdn.privacy-mgmt.com/mms/v2/get_site_data""#; + let output = SourcepointIntegration::rewrite_script_content(input); + + assert!( + output.contains("\"/integrations/sourcepoint/cdn/mms/v2/get_site_data\""), + "Should rewrite protocol-relative CDN URL. Got: {output}", + ); + } + + #[test] + fn rewrites_origin_plus_unified_chunk_pattern() { + let input = r#"return t.origin+"/unified/4.40.1/"}"#; + let output = SourcepointIntegration::rewrite_script_content(input); + + assert_eq!( + output, + r#"return t.origin+"/integrations/sourcepoint/cdn/unified/4.40.1/"}"# + ); + } + + #[test] + fn rewrites_both_patterns_in_realistic_snippet() { + // Mirrors the real Sourcepoint webpack public path resolution: + // try { ... return t.origin+"/unified/4.40.1/" } + // catch(e) {} return e+"/unified/4.40.1/" + // where e defaults to "https://cdn.privacy-mgmt.com" + let input = concat!( + r#"var e="https://cdn.privacy-mgmt.com";"#, + r#"try{var t=document.createElement("a");"#, + r#"t.href=document.currentScript.src;"#, + r#"return t.origin+"/unified/4.40.1/"}"#, + r#"catch(n){}return e+"/unified/4.40.1/""#, + ); + + let output = SourcepointIntegration::rewrite_script_content(input); + + assert!( + output.contains(r#"var e="/integrations/sourcepoint/cdn";"#), + "Fallback CDN default should be rewritten. Got: {output}", + ); + assert!( + output.contains(r#"t.origin+"/integrations/sourcepoint/cdn/unified/4.40.1/"}"#), + "Origin chunk path should be prefixed. Got: {output}", + ); + assert!( + output.contains(r#"e+"/unified/4.40.1/""#), + "Fallback concatenation should keep /unified/ since e is already rewritten. Got: {output}", + ); + } + + #[test] + fn preserves_non_sourcepoint_urls() { + let input = r#"var cdn="https://example.com/script.js";var x=t.origin+"/assets/app.js""#; + let output = SourcepointIntegration::rewrite_script_content(input); + + assert_eq!(output, input, "Non-Sourcepoint URLs should be untouched"); + } + + #[test] + fn registers_sourcepoint_routes() { + let mut settings = create_test_settings(); + settings + .integrations + .insert_config(SOURCEPOINT_INTEGRATION_ID, &json!({ "enabled": true })) + .expect("should insert config"); + + let registry = IntegrationRegistry::new(&settings).expect("should create registry"); + assert!( + registry.has_route( + &Method::GET, + "/integrations/sourcepoint/cdn/wrapper/v2/messages" + ), + "should register CDN proxy route" + ); + } + + #[test] + fn attribute_rewriter_skips_when_rewrite_disabled() { + let mut cfg = config(true); + cfg.rewrite_sdk = false; + let integration = SourcepointIntegration::new(Arc::new(cfg)); + + assert!( + !integration.handles_attribute("src"), + "should not handle src when rewrite_sdk is false" + ); + assert!( + !integration.handles_attribute("href"), + "should not handle href when rewrite_sdk is false" + ); + } + + #[test] + fn identifies_likely_javascript_paths() { + assert!(SourcepointIntegration::is_likely_javascript_path( + "/unified/4.40.1/gdpr-tcf.bundle.js" + )); + assert!(SourcepointIntegration::is_likely_javascript_path( + "/wrapper/v2/messages" + )); + assert!(SourcepointIntegration::is_likely_javascript_path( + "/wrapperMessagingWithoutDetection.js" + )); + assert!(!SourcepointIntegration::is_likely_javascript_path( + "/mms/v2/get_site_data" + )); + assert!(!SourcepointIntegration::is_likely_javascript_path( + "/consent/tcfv2" + )); + } + + #[test] + fn head_injector_emits_sp_property_trap() { + let integration = SourcepointIntegration::new(Arc::new(config(true))); + let document_state = IntegrationDocumentState::default(); + let ctx = IntegrationHtmlContext { + request_host: "ts.autoblog.com", + request_scheme: "https", + origin_host: "origin.autoblog.com", + document_state: &document_state, + }; + + let inserts = integration.head_inserts(&ctx); + assert_eq!(inserts.len(), 1, "should produce exactly one head insert"); + + let script = &inserts[0]; + assert!( + script.starts_with(""), + "should be wrapped in script tags: {script}", + ); + assert!( + script.contains("cdn.privacy-mgmt.com"), + "should reference the CDN host to rewrite: {script}", + ); + assert!( + script.contains("/integrations/sourcepoint/cdn"), + "should contain the first-party CDN prefix: {script}", + ); + assert!( + script.contains("Object.defineProperty"), + "should install a property trap on window._sp_: {script}", + ); + assert!( + script.contains("baseEndpoint"), + "should patch baseEndpoint in the config: {script}", + ); + assert!( + script.contains("metricUrl"), + "should patch metricUrl: {script}", + ); + } + + #[test] + fn head_injector_returns_empty_when_rewrite_disabled() { + let mut cfg = config(true); + cfg.rewrite_sdk = false; + let integration = SourcepointIntegration::new(Arc::new(cfg)); + let document_state = IntegrationDocumentState::default(); + let ctx = IntegrationHtmlContext { + request_host: "ts.autoblog.com", + request_scheme: "https", + origin_host: "origin.autoblog.com", + document_state: &document_state, + }; + + let inserts = integration.head_inserts(&ctx); + assert!( + inserts.is_empty(), + "should not inject anything when rewrite_sdk is false" + ); + } + + #[test] + fn rejects_cdn_origin_outside_privacy_mgmt_domain() { + let cfg = SourcepointConfig { + enabled: true, + rewrite_sdk: true, + cdn_origin: "http://169.254.169.254".to_string(), + cache_ttl_seconds: default_cache_ttl(), + }; + assert!( + cfg.validate().is_err(), + "should reject cdn_origin not on *.privacy-mgmt.com" + ); + } + + #[test] + fn accepts_valid_cdn_origin() { + let cfg = SourcepointConfig { + enabled: true, + rewrite_sdk: true, + cdn_origin: "https://cdn.privacy-mgmt.com".to_string(), + cache_ttl_seconds: default_cache_ttl(), + }; + assert!( + cfg.validate().is_ok(), + "should accept cdn_origin on *.privacy-mgmt.com" + ); + } + + #[test] + fn rewrites_single_quoted_origin_plus_unified_pattern() { + let input = r#"return t.origin+'/unified/4.40.1/'}"#; + let output = SourcepointIntegration::rewrite_script_content(input); + + assert_eq!( + output, r#"return t.origin+'/integrations/sourcepoint/cdn/unified/4.40.1/'}"#, + "should rewrite single-quoted unified path" + ); + } +} diff --git a/docs/guide/integrations-overview.md b/docs/guide/integrations-overview.md index 1c312584..2274245c 100644 --- a/docs/guide/integrations-overview.md +++ b/docs/guide/integrations-overview.md @@ -4,12 +4,13 @@ Trusted Server provides built-in integrations with popular third-party services, ## Quick Comparison -| Integration | Type | Endpoints | HTML Rewriting | Primary Use Case | Status | -| ------------- | ---------------- | ---------- | ---------------------------- | --------------------------- | ----------- | -| **Prebid** | Proxy + Rewriter | 2-3 routes | Removes Prebid.js scripts | Server-side header bidding | Production | -| **Next.js** | Script Rewriter | None | Rewrites Next.js data | First-party Next.js routing | Production | -| **Permutive** | Proxy + Rewriter | 6 routes | Rewrites SDK URLs | First-party audience data | Production | -| **Testlight** | Proxy + Rewriter | 1 route | Rewrites integration scripts | Testing/development | Development | +| Integration | Type | Endpoints | HTML Rewriting | Primary Use Case | Status | +| --------------- | ---------------- | ---------- | ---------------------------- | --------------------------- | ----------- | +| **Prebid** | Proxy + Rewriter | 2-3 routes | Removes Prebid.js scripts | Server-side header bidding | Production | +| **Next.js** | Script Rewriter | None | Rewrites Next.js data | First-party Next.js routing | Production | +| **Permutive** | Proxy + Rewriter | 6 routes | Rewrites SDK URLs | First-party audience data | Production | +| **Sourcepoint** | Proxy + Rewriter | 2 routes | Rewrites CMP asset URLs | First-party CMP delivery | Development | +| **Testlight** | Proxy + Rewriter | 1 route | Rewrites integration scripts | Testing/development | Development | ## Integration Details @@ -119,6 +120,38 @@ rewrite_sdk = true --- +### Sourcepoint + +**What it does:** Proxies Sourcepoint CMP CDN endpoints through Trusted Server and rewrites publisher references to first-party paths. + +**Key Features:** + +- CDN proxy for `cdn.privacy-mgmt.com` +- HTML attribute rewriting for Sourcepoint assets +- JavaScript body rewriting for webpack chunks and API URLs +- Head-injected `window._sp_` property trap for runtime config +- Client-side script guard for dynamic script insertion + +**Configuration:** + +```toml +[integrations.sourcepoint] +enabled = true +rewrite_sdk = true +cdn_origin = "https://cdn.privacy-mgmt.com" +cache_ttl_seconds = 3600 +``` + +**Endpoints:** + +- `GET/POST /integrations/sourcepoint/cdn/*` - Sourcepoint CDN proxy + +**When to use:** You load Sourcepoint CMP assets and want them to flow through first-party paths without introducing an open-ended proxy. + +**Learn more:** [Sourcepoint Integration](./integrations/sourcepoint.md) + +--- + ### Testlight **What it does:** Testing/development integration for validating the integration system with OpenRTB-like auctions. @@ -198,6 +231,10 @@ Do you use Permutive for audience data? ├─ Yes → Enable Permutive integration └─ No → Skip Permutive +Do you use Sourcepoint for consent management? +├─ Yes → Enable Sourcepoint integration +└─ No → Skip Sourcepoint + Are you developing/testing integrations? ├─ Yes → Enable Testlight integration └─ No → Skip Testlight @@ -205,12 +242,13 @@ Are you developing/testing integrations? ## Performance Considerations -| Integration | Performance Impact | Caching Strategy | Notes | -| ------------- | ------------------ | --------------------------- | -------------------------------------------- | -| **Prebid** | Medium | Response caching possible | Timeout configurable (default 1s) | -| **Next.js** | Low | N/A (streaming rewrite) | Minimal overhead, runs during HTML streaming | -| **Permutive** | Low | SDK cached (1 hour default) | API calls proxied in real-time | -| **Testlight** | Low | No caching | Development use only | +| Integration | Performance Impact | Caching Strategy | Notes | +| --------------- | ------------------ | --------------------------- | -------------------------------------------- | +| **Prebid** | Medium | Response caching possible | Timeout configurable (default 1s) | +| **Next.js** | Low | N/A (streaming rewrite) | Minimal overhead, runs during HTML streaming | +| **Permutive** | Low | SDK cached (1 hour default) | API calls proxied in real-time | +| **Sourcepoint** | Low | CDN cached (1 hour default) | JS rewriting adds minor overhead | +| **Testlight** | Low | No caching | Development use only | ## Environment Variables @@ -230,6 +268,10 @@ TRUSTED_SERVER__INTEGRATIONS__NEXTJS__ENABLED=true TRUSTED_SERVER__INTEGRATIONS__PERMUTIVE__ORGANIZATION_ID="neworg" TRUSTED_SERVER__INTEGRATIONS__PERMUTIVE__WORKSPACE_ID="workspace-123" +# Sourcepoint +TRUSTED_SERVER__INTEGRATIONS__SOURCEPOINT__ENABLED=true +TRUSTED_SERVER__INTEGRATIONS__SOURCEPOINT__CDN_ORIGIN="https://cdn.privacy-mgmt.com" + # Testlight TRUSTED_SERVER__INTEGRATIONS__TESTLIGHT__ENDPOINT="https://test.example.com" ``` diff --git a/docs/guide/integrations/sourcepoint.md b/docs/guide/integrations/sourcepoint.md new file mode 100644 index 00000000..857e88ae --- /dev/null +++ b/docs/guide/integrations/sourcepoint.md @@ -0,0 +1,65 @@ +# Sourcepoint Integration + +Sourcepoint provides consent and privacy messaging for publishers. This integration proxies the Sourcepoint CDN endpoint through Trusted Server so the browser loads it from a first-party path. + +## Overview + +The Sourcepoint integration: + +- Proxies `cdn.privacy-mgmt.com` requests through `/integrations/sourcepoint/cdn/*` +- Rewrites matching `src` and `href` attributes during HTML processing +- Rewrites JavaScript response bodies so webpack chunks and API calls route through the proxy +- Injects a `window._sp_` property trap for config URLs set by Next.js hydration chunks +- Installs a client-side script guard for dynamically inserted Sourcepoint assets + +## Configuration + +Add the following to `trusted-server.toml`: + +```toml +[integrations.sourcepoint] +enabled = true +rewrite_sdk = true +cdn_origin = "https://cdn.privacy-mgmt.com" +cache_ttl_seconds = 3600 +``` + +### Configuration Options + +| Option | Type | Default | Description | +| ------------------- | ------- | ------------------------------ | --------------------------------------------------------------------------------- | +| `enabled` | boolean | `false` | Enable the Sourcepoint integration | +| `rewrite_sdk` | boolean | `true` | Rewrite matching Sourcepoint URLs in HTML | +| `cdn_origin` | string | `https://cdn.privacy-mgmt.com` | Sourcepoint CDN origin | +| `cache_ttl_seconds` | integer | `3600` | Cache TTL applied to successful CDN responses when the origin omits cache headers | + +## Endpoints + +| Method | Path | Description | +| ---------- | --------------------------------- | --------------------------------------------- | +| `GET/POST` | `/integrations/sourcepoint/cdn/*` | Proxy Sourcepoint CDN assets and wrapper APIs | + +## HTML Rewriting + +When `rewrite_sdk = true`, Trusted Server rewrites matching Sourcepoint URLs in HTML responses: + +```html + + + + + +``` + +## Client-Side Guard + +Single-page apps often insert CMP scripts after the initial HTML response. The `sourcepoint` tsjs module installs a DOM insertion guard so dynamically inserted Sourcepoint script and preload URLs are rewritten to first-party paths before the browser fetches them. + +## Notes + +- This version scopes the integration to `cdn.privacy-mgmt.com`. Additional Sourcepoint domains (e.g., `geo.privacymanager.io`) can be added later if publishers require them. + +## See Also + +- [Integration Guide](/guide/integration-guide) +- [Integrations Overview](/guide/integrations-overview) diff --git a/trusted-server.toml b/trusted-server.toml index d9189aaa..f7de4630 100644 --- a/trusted-server.toml +++ b/trusted-server.toml @@ -69,6 +69,12 @@ enabled = false sdk_origin = "https://sdk.privacy-center.org" api_origin = "https://api.privacy-center.org" +[integrations.sourcepoint] +enabled = false +rewrite_sdk = true +cdn_origin = "https://cdn.privacy-mgmt.com" +cache_ttl_seconds = 3600 + [integrations.permutive] enabled = false organization_id = "" @@ -190,4 +196,3 @@ timeout_ms = 1000 [integrations.adserver_mock.context_query_params] permutive_segments = "permutive" -