/** * TikTok Privacy and Network Security (PNS) Runtime - Deobfuscated * Original file: index.js * * This is TikTok's Privacy and Network Security (PNS) system that: * - Controls web API usage and ensures compliance with privacy policies * - Monitors and intercepts network requests for security * - Implements cookie consent management * - Provides comprehensive analytics and tracking * - Manages service worker communication * - Enforces content security policies */ "use strict"; /** * Core Constants and Event Types */ const EVENT_TYPES = { MAIN_THREAD: "main_thread", OUT_APP: "out_app", COOKIE_SET_BY_DOCUMENT: "cookie_set_by_document", COOKIE_BLOCKED_ON_START: "cookie_blocked_on_start", GENERAL_FETCH: "general_fetch", REQUEST_LOG: "request_log", WEBAPI: "webapi", STORAGE_USE: "storage_use", SW_INCOMPAT: "sw_incompat", READY_FOR_MSG: "ready_for_msg", FORCE_UPDATE_SW: "force_update_sw", FREQUENCY: "frequency", COST_TIME: "cost_time", MAIN_THREAD_CTX: "main_thread_ctx", NETWORK_RULE: "network_rule" }; const SW_EVENTS = { RUNTIME_SW_EVENT: "__PNS_RUNTIME_SW_EVENT__", RUNTIME_SE_ERROR: "__PNS_RUNTIME_SE_ERROR__", RUNTIME: "__PNS_RUNTIME__" }; /** * Global Runtime Instance Manager * Creates and manages the global PNS runtime instance */ function createGlobalRuntime(globalName = getGlobalName()) { let runtime = globalThis[globalName]; if (!runtime) { runtime = { pendingEvents: [], pendingConfig: {}, pendingListeners: {}, errors: [], /** * Push event to pending queue */ pushEvent: function(eventName, eventDetail = null, source = EVENT_TYPES.MAIN_THREAD, options) { addToQueue(runtime.pendingEvents, { eventName, eventDetail, source, options }, 100); }, /** * Push error to error queue */ pushError: function(error) { addToQueue(runtime.errors, error, 20); }, pageContextObservers: [] }; globalThis[globalName] = runtime; } return runtime; } /** * Get global name from script parameters or use default */ function getGlobalName() { const scriptSrc = document.currentScript?.src; try { const url = new URL(scriptSrc); return url.searchParams.get("globalName") || SW_EVENTS.RUNTIME; } catch (error) { return SW_EVENTS.RUNTIME; } } /** * Add item to queue with size limit */ function addToQueue(queue, item, maxSize) { queue.splice(0, queue.length - maxSize + 1); queue.push(item); } /** * Privacy and Network Security Core Classes */ /** * Cookie Consent Manager * Handles cookie blocking and consent management */ class CookieConsentManager { constructor(config) { this.config = config; this.blockedCookies = this.getBlockedCookies(config.blockers); } /** * Get list of cookies to block based on domain matching */ getBlockedCookies(blockers = []) { for (const blocker of blockers) { const { domains = [], cookies = [] } = blocker; if (domains.some(domain => location.hostname.endsWith(domain))) { return cookies; } } return []; } /** * Hook document.cookie setter to intercept cookie operations */ hookCookieSetter(reportCallback) { const originalDescriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie'); Object.defineProperty(document, 'cookie', { set: (value) => { const cookieData = this.processCookieSet(value, reportCallback); if (!cookieData._blocked) { originalDescriptor.set.call(document, value); } return cookieData._blocked; }, get: originalDescriptor.get, configurable: true }); } /** * Process cookie setting with privacy rules */ processCookieSet(cookieValue, reportCallback) { const cookieData = { rawValue: cookieValue, name: this.getCookieName(cookieValue), _time: Date.now(), _blocked: false, _sample_rate: this.config.sampleRate, _stack_rate: 0, _rule_names: [] }; // Check if cookie should be blocked cookieData._blocked = this.blockedCookies.includes(cookieData.name); // Apply privacy rules and report if needed if (Math.random() < cookieData._sample_rate) { reportCallback(cookieData); } return cookieData; } getCookieName(cookieString) { const name = cookieString.split('=')[0]; return name ? name.trim() : undefined; } } /** * Network Request Interceptor * Intercepts and processes all network requests for security and privacy */ class NetworkInterceptor { constructor(config, callbacks) { this.config = config; this.callbacks = callbacks; this.originalFetch = window.fetch; this.originalXHR = window.XMLHttpRequest; this.hookFetch(); this.hookXMLHttpRequest(); } /** * Hook fetch API for request interception */ hookFetch() { const self = this; window.fetch = function(...args) { const request = new Request(...args); const requestData = self.extractRequestData(request); // Apply security rules const processedData = self.applySecurityRules(requestData); if (processedData._blocked) { return Promise.resolve(new Response("Request blocked", { status: 410, statusText: "Request blocked by privacy policy" })); } // Report request for analytics self.callbacks.report(processedData); return self.originalFetch.apply(this, args); }; } /** * Hook XMLHttpRequest for legacy request interception */ hookXMLHttpRequest() { const self = this; const OriginalXHR = this.originalXHR; window.XMLHttpRequest = class extends OriginalXHR { constructor() { super(); this.__pumbaa_detail = {}; } open(method, url, ...args) { this.__pumbaa_detail = { method: method.toUpperCase(), request_url: new URL(url, location.href).href, _request_time: Date.now(), _blocked: false }; return super.open(method, url, ...args); } send(body) { const processedData = self.applySecurityRules(this.__pumbaa_detail); if (processedData._blocked) { this.status = 410; this.statusText = "Request blocked"; return; } self.callbacks.report(processedData); return super.send(body); } }; } /** * Extract request data for processing */ extractRequestData(request) { const url = new URL(request.url); return { request_url: request.url, request_host: url.host, request_path: url.pathname, search: url.search, method: request.method, headers: this.extractHeaders(request.headers), _request_time: Date.now(), _blocked: false, _sample_rate: 0 }; } /** * Extract headers from request */ extractHeaders(headers) { const headerMap = {}; headers.forEach((value, key) => { headerMap[key] = value; }); return headerMap; } /** * Apply security and privacy rules to requests */ applySecurityRules(requestData) { // Check against blocklist/allowlist if (this.isRequestBlocked(requestData.request_url)) { requestData._blocked = true; requestData["x-pns-block"] = "1"; } // Apply URL replacement rules const modifiedUrl = this.applyUrlReplacement(requestData.request_url); if (modifiedUrl !== requestData.request_url) { requestData.request_url = modifiedUrl; requestData["x-pns-replace"] = "1"; requestData._replaced_fields = ["url"]; } return requestData; } /** * Check if request should be blocked */ isRequestBlocked(url) { const { blocklist = [], allowlist = [] } = this.config; // Check blocklist if (blocklist.some(blocked => url.startsWith(blocked))) { return true; } // Check allowlist (if exists, URL must be in it) if (allowlist.length > 0) { return !allowlist.some(allowed => url.startsWith(allowed)); } return false; } /** * Apply URL replacement rules (e.g., HTTP to HTTPS) */ applyUrlReplacement(url) { const { replace = {} } = this.config; for (const [pattern, replacement] of Object.entries(replace)) { if (url.startsWith(pattern)) { return replacement + url.substring(pattern.length); } } return url; } } /** * Web API Monitor * Monitors usage of sensitive web APIs */ class WebAPIMonitor { constructor(config, reportCallback) { this.config = config; this.reportCallback = reportCallback; this.hookSensitiveAPIs(); } /** * Hook sensitive web APIs for monitoring */ hookSensitiveAPIs() { const apis = this.config.apis || []; apis.forEach(api => { this.hookAPI(api); }); } /** * Hook individual API */ hookAPI(apiConfig) { const { apiObj, apiName, apiType, sampleRate, block } = apiConfig; const target = this.getAPITarget(apiObj); if (!target) return; const originalMethod = target[apiName]; if (typeof originalMethod !== 'function') return; const self = this; target[apiName] = function(...args) { // Report API usage if (Math.random() < sampleRate) { self.reportCallback({ apiRule: apiConfig, args: args, _blocked: block }); } // Block if configured if (block) { console.warn(`API ${apiName} blocked by privacy policy`); return; } return originalMethod.apply(this, args); }; } /** * Get API target object */ getAPITarget(apiObj) { if (!apiObj) return window; const parts = apiObj.split('.'); let target = window; for (const part of parts) { target = target[part]; if (!target) return null; } return target; } } /** * Page Context Manager * Manages page context and navigation tracking */ class PageContextManager { constructor() { this.observers = []; this.context = this.buildInitialContext(); this.setupNavigationTracking(); } /** * Build initial page context */ buildInitialContext() { const url = new URL(location.href); return { url: url.href, host: url.host, path: url.pathname, search: url.search, hash: url.hash, region: this.getRegion(), business: this.getBusiness(), env: this.getEnvironment() }; } /** * Setup navigation change tracking */ setupNavigationTracking() { // Track popstate events window.addEventListener('popstate', () => { this.updateContext(this.buildInitialContext()); }); // Hook history API ['pushState', 'replaceState'].forEach(method => { const original = History.prototype[method]; History.prototype[method] = function(...args) { original.apply(this, args); // Update context after navigation setTimeout(() => { this.updateContext(this.buildInitialContext()); }, 0); }; }); } /** * Update page context and notify observers */ updateContext(newContext) { const changes = this.getContextChanges(this.context, newContext); if (Object.keys(changes).length > 0) { Object.assign(this.context, changes); // Notify observers this.observers.forEach(observer => { if (!observer.fields || observer.fields.some(field => field in changes)) { observer.func(this.context); } }); } } /** * Get context changes */ getContextChanges(oldContext, newContext) { const changes = {}; for (const key in newContext) { if (oldContext[key] !== newContext[key]) { changes[key] = newContext[key]; } } return changes; } /** * Add context observer */ addObserver(callback, fields) { this.observers.push({ func: callback, fields }); } getRegion() { // Extract region from meta tags or config return document.querySelector('meta[name="pumbaa-ctx"]')?.content?.region || 'unknown'; } getBusiness() { // Extract business context return document.querySelector('meta[name="pumbaa-web-config"]')?.content?.business || 'tiktok'; } getEnvironment() { // Determine environment (prod, staging, dev) return location.hostname.includes('tiktok.com') ? 'prod' : 'dev'; } } /** * Main PNS Runtime Class * Orchestrates all privacy and security components */ class PNSRuntime { constructor() { this.config = this.loadConfiguration(); this.pageContext = new PageContextManager(); this.setupComponents(); } /** * Load PNS configuration from embedded script or meta tags */ loadConfiguration() { // Try to load from embedded script tag const configScript = document.getElementById('pumbaa-rule'); if (configScript) { try { return JSON.parse(configScript.textContent); } catch (error) { console.warn('Failed to parse PNS config:', error); } } // Fallback to default configuration return this.getDefaultConfig(); } /** * Setup all PNS components */ setupComponents() { const runtime = createGlobalRuntime(); // Setup cookie consent management if (this.config.cookie?.enabled) { const cookieManager = new CookieConsentManager(this.config.cookie); cookieManager.hookCookieSetter((data) => { runtime.pushEvent(EVENT_TYPES.COOKIE_SET_BY_DOCUMENT, data); }); } // Setup network interception if (this.config.network?.enabled) { const networkInterceptor = new NetworkInterceptor(this.config.network, { report: (data) => { runtime.pushEvent(EVENT_TYPES.GENERAL_FETCH, data); } }); } // Setup web API monitoring if (this.config.webapi?.enabled) { const apiMonitor = new WebAPIMonitor(this.config.webapi, (data) => { runtime.pushEvent(EVENT_TYPES.WEBAPI, data); }); } // Setup service worker communication this.setupServiceWorkerCommunication(runtime); } /** * Setup service worker communication for enhanced security */ setupServiceWorkerCommunication(runtime) { if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', (event) => { if (event.data.event === SW_EVENTS.RUNTIME_SW_EVENT) { // Handle service worker events runtime.pushEvent(event.data.eventName, event.data.data); } }); navigator.serviceWorker.ready.then((registration) => { const sw = registration.active || navigator.serviceWorker.controller; if (sw) { // Send configuration to service worker sw.postMessage({ eventName: EVENT_TYPES.READY_FOR_MSG, source: EVENT_TYPES.MAIN_THREAD, data: this.pageContext.context }); } }); } } /** * Get default configuration if none provided */ getDefaultConfig() { return { cookie: { enabled: true, sampleRate: 0.07, blockers: [] }, network: { enabled: true, sampleRate: 0.03, intercept: [] }, webapi: { enabled: true, apis: [] } }; } } /** * Initialize PNS Runtime * Main entry point that starts the privacy and security system */ function initializePNS() { // Check if already initialized if (window.__PUMBAA_RUN_FLAG__) { return; } window.__PUMBAA_RUN_FLAG__ = true; try { // Initialize the PNS runtime const pnsRuntime = new PNSRuntime(); console.log('TikTok Privacy and Network Security (PNS) initialized'); // Load core PNS module const script = document.createElement('script'); script.src = './core.js?globalName=' + getGlobalName(); script.crossOrigin = 'anonymous'; script.async = true; // Copy dataset from current script if (document.currentScript) { Object.assign(script.dataset, document.currentScript.dataset); } document.head.appendChild(script); document.head.removeChild(script); } catch (error) { console.error('Failed to initialize PNS:', error); // Report error to global runtime const runtime = createGlobalRuntime(); runtime.pushError(error); } } // Auto-initialize if conditions are met if (typeof window !== 'undefined' && window.Symbol && !(/ByteLocale/g.test(navigator.userAgent))) { initializePNS(); }