UNPKG

client-trace

Version:

A comprehensive client-side security and telemetry library. Features device fingerprinting, bot detection, network tampering analysis, and secure transport.

779 lines (606 loc) 26.2 kB
# client-trace A comprehensive client-side security and telemetry library for modern web applications. `client-trace` provides a suite of modules to detect tampering, identify devices, monitor behavior, and secure data transport. ## Features - **Integrity Verification**: Detect if your client bundle has been modified. - **Network Analysis**: Detect monkey-patched `fetch`/`XHR`, proxies, and timing anomalies. - **Device Fingerprinting**: Lightweight, privacy-friendly device identification. - **Bot Detection**: Analyze mouse movements and click patterns to identify bots. - **Security Monitoring**: Detect script injections, CSP violations, and local storage tampering. - **Secure Transport**: End-to-end encryption (AES-GCM), payload signing (HMAC), and replay protection. ## Use Cases - **Client‑side bundle integrity verification** – `verifyBundleIntegrity` ensures the running JavaScript bundle matches a known hash. - **Session‑token generation** – `generateSessionToken` creates a signed token for API calls. - **Network‑level API tampering detection** – `detectNetworkAPITampering` flags monkey‑patched `fetch`/`XHR`. - **Proxy / VPN detection** – `detectProxy` measures latency and header anomalies. - **Timing‑anomaly detection** – `detectTimingAnomalies` spots abnormal round‑trip times. - **Device fingerprinting** – `getDeviceFingerprint` builds a privacy‑friendly device identifier. - **Bot / headless‑browser detection** – `detectBot` analyses mouse entropy, rapid clicks, and headless flags. - **Script‑injection monitoring** – `detectInjections` watches for unexpected `<script>` tags. - **CSP violation listener** – `listenForCSPViolations` aggregates CSP breach events. - **Local‑storage tampering detection** – `checkStorageIntegrity` validates stored data integrity. - **Secure transport helpers** – `signPayload`, `encryptTelemetry`, `getNonce` provide signing, encryption, and replay protection. - **Aggregated security report** – `collectSecurityReport` runs all checks and returns a single JSON payload. ## Installation ```bash npm install client-trace ``` ## Usage ### Quick Start (Aggregated Report) The easiest way to use `client-trace` is to collect a full security report. ```javascript import { collectSecurityReport } from 'client-trace'; const config = { bundleUrl: '/assets/main.js', expectedBundleHash: 'sha256-hash-of-your-bundle', // Optional pingUrl: '/api/ping', // For proxy/timing detection userUniqueId: 'user-123', // For session token hashedIp: 'hash-of-ip', // Provided by server secret: 'your-shared-secret' // For signing/encryption }; collectSecurityReport(config).then(report => { console.log('Security Report:', report); // Send report to your server }); ``` ### Modular Usage You can also import and use individual modules as needed. #### 1. Bundle Integrity ##### `verifyBundleIntegrity(bundleUrl, expectedHash)` Verifies that your client bundle hasn't been tampered with by comparing its SHA-256 hash against a known good hash. **Parameters:** - `bundleUrl` (string): URL to the JavaScript bundle file (e.g., `/assets/main.js` or `/js/app.bundle.js`) - `expectedHash` (string): Expected SHA-256 hash of the unmodified bundle **Returns:** Promise resolving to an object: ```javascript { integrityOk: boolean, // true if hash matches, false if tampered actualHash: string, // SHA-256 hash of the current bundle expectedHash: string, // The hash you provided error?: string // Error message if hash couldn't be computed } ``` **Example:** ```javascript import { verifyBundleIntegrity } from 'client-trace'; verifyBundleIntegrity('/assets/main.js', 'sha256-abc123def456').then(result => { if (!result.integrityOk) { console.error('ALERT: Bundle has been modified!'); console.error('Expected:', result.expectedHash); console.error('Actual:', result.actualHash); // Consider blocking the app or alerting the user } else { console.log('Bundle integrity verified ✓'); } }); ``` --- ##### `generateSessionToken(userUniqueId, hashedIp, secret, expiryTime)` Creates a cryptographically signed token that binds a user to their IP address and user agent, preventing session token reuse across different machines/networks. **Parameters:** - `userUniqueId` (string): Unique identifier for the user (e.g., user ID from your system) - `hashedIp` (string): Server-provided hash of the user's IP (for privacy). Your server should compute this - `secret` (string): Shared secret between client and server (minimum 32 characters recommended) - `expiryTime` (number, optional): Token expiration time in milliseconds from now (default: 1 hour) **Returns:** Promise resolving to an object: ```javascript { token: string, // Signed JWT-like token signature: string, // HMAC-SHA256 signature issuedAt: number, // Timestamp when token was created expiresAt: number, // Timestamp when token expires userUniqueId: string, // The user ID encoded in the token metadata: object // Additional claims (IP, UA, etc.) } ``` **Example:** ```javascript import { generateSessionToken } from 'client-trace'; const token = await generateSessionToken( 'user-456', 'hash-of-ip-from-server', 'your-secure-shared-secret-32chars', 3600000 // 1 hour ); // Send this token with API requests fetch('/api/secure-endpoint', { headers: { 'X-Session-Token': token.token } }); console.log('Token expires at:', new Date(token.expiresAt)); ``` --- #### 2. Network Tampering Detection ##### `detectNetworkAPITampering()` Checks if the native `fetch` and `XHR` (XMLHttpRequest) APIs have been monkey-patched or replaced, which could indicate malicious browser extensions or script injection. **Parameters:** None **Returns:** An object: ```javascript { tampered: boolean, // true if any tampering detected tamperedFunctions: string[], // List of tampered functions (e.g., ['fetch', 'XMLHttpRequest']) fetchIsNative: boolean, // true if fetch is original xhrIsNative: boolean, // true if XMLHttpRequest is original details: object // Detailed analysis of each API } ``` **Example:** ```javascript import { detectNetworkAPITampering } from 'client-trace'; const result = detectNetworkAPITampering(); if (result.tampered) { console.warn('⚠️ Network APIs have been modified!'); console.warn('Tampered functions:', result.tamperedFunctions); if (!result.fetchIsNative) { console.warn('fetch has been intercepted - consider blocking requests'); } if (!result.xhrIsNative) { console.warn('XMLHttpRequest has been intercepted - consider blocking requests'); } // Take defensive action (e.g., stop sending sensitive data) } else { console.log('Network APIs are clean ✓'); } ``` --- ##### `detectProxy(pingUrl)` Detects if the user is behind a proxy or VPN by analyzing response headers (like `X-Forwarded-For`, `CF-Connecting-IP`) and measuring latency anomalies. **Parameters:** - `pingUrl` (string): URL endpoint on your server to ping for latency testing (e.g., `/api/ping`) **Returns:** Promise resolving to an object: ```javascript { proxyDetected: boolean, // true if proxy/VPN indicators found confidence: number, // 0-1 score indicating likelihood indicators: string[], // List of detected proxy signs (e.g., ['x-forwarded-for', 'unusual-latency']) headerAnalysis: object, // Proxy-related headers detected latencyMs: number, // Round-trip latency to ping endpoint isHighLatency: boolean, // true if latency exceeds threshold details: object // Full analysis } ``` **Example:** ```javascript import { detectProxy } from 'client-trace'; const proxyCheck = await detectProxy('/api/ping'); console.log('Latency:', proxyCheck.latencyMs, 'ms'); console.log('High latency:', proxyCheck.isHighLatency); if (proxyCheck.proxyDetected) { console.warn(`Proxy/VPN detected with ${(proxyCheck.confidence * 100).toFixed(0)}% confidence`); console.warn('Indicators:', proxyCheck.indicators); // Optional: apply stricter security measures if (proxyCheck.confidence > 0.8) { console.warn('High confidence proxy detected - consider additional verification'); } } else { console.log('No proxy detected ✓'); } ``` --- ##### `detectTimingAnomalies(options)` Measures DNS lookup time, TTFB (Time To First Byte), and total request time to detect Man-in-the-Middle (MITM) attacks or unusual network conditions. **Parameters:** - `options` (object, optional): - `testUrl` (string): URL to test (default: `/api/ping`) - `iterations` (number): Number of requests to measure (default: 5) - `thresholdMs` (number): Latency threshold in milliseconds (default: 1000) **Returns:** Promise resolving to an object: ```javascript { anomalyDetected: boolean, // true if timing is abnormal averageLatencyMs: number, // Average latency across all requests minLatencyMs: number, // Minimum latency observed maxLatencyMs: number, // Maximum latency observed variance: number, // Variance in latencies (high = inconsistent) outliers: number[], // Individual latencies that are outliers isConsistent: boolean, // true if latencies are consistent mitmLikely: boolean // true if MITM attack indicators present } ``` **Example:** ```javascript import { detectTimingAnomalies } from 'client-trace'; const timingReport = await detectTimingAnomalies({ testUrl: '/api/ping', iterations: 10, thresholdMs: 1500 }); console.log('Average latency:', timingReport.averageLatencyMs, 'ms'); if (timingReport.anomalyDetected) { console.warn('⚠️ Timing anomalies detected!'); console.warn('MITM likely:', timingReport.mitmLikely); console.warn('Variance:', timingReport.variance, '(high = inconsistent)'); if (timingReport.mitmLikely) { // Consider enhanced security measures } } ``` --- #### 3. Device Fingerprinting ##### `getDeviceFingerprint()` Generates a lightweight, privacy-friendly fingerprint of the user's device by hashing non-unique signals like screen resolution, OS, timezone, and browser capabilities. **Parameters:** None **Returns:** Promise resolving to an object: ```javascript { fingerprintHash: string, // SHA-256 hash of all fingerprint components components: { screenResolution: string, // e.g., "1920x1080" colorDepth: number, // e.g., 24 timezone: string, // e.g., "UTC-5" language: string, // Browser language, e.g., "en-US" platform: string, // e.g., "Win32", "MacIntel" hardwareConcurrency: number, // Number of CPU cores deviceMemory: number, // RAM in GB (approximate) canvasFingerprint: string, // Hash of canvas rendering capabilities webglRenderer: string // GPU renderer info }, stability: number // 0-1: likelihood fingerprint stays same over time } ``` **Example:** ```javascript import { getDeviceFingerprint } from 'client-trace'; const fingerprint = await getDeviceFingerprint(); console.log('Device fingerprint:', fingerprint.fingerprintHash); console.log('Screen resolution:', fingerprint.components.screenResolution); console.log('CPU cores:', fingerprint.components.hardwareConcurrency); console.log('Fingerprint stability:', fingerprint.stability); // Higher = more stable // Store for session tracking (not for device tracking across days) sessionStorage.setItem('deviceId', fingerprint.fingerprintHash); // Can be sent to server for additional analysis fetch('/api/telemetry', { method: 'POST', body: JSON.stringify({ fingerprint: fingerprint.fingerprintHash }) }); ``` --- #### 4. Bot Detection ##### `startBehaviorMonitoring()` Initiates tracking of user behavior signals (mouse movements, click patterns, keyboard activity) in the background. **Call this as early as possible** in your page lifecycle (e.g., in a script tag in `<head>`). **Parameters:** None **Returns:** void **Example:** ```javascript import { startBehaviorMonitoring } from 'client-trace'; // Call immediately on page load startBehaviorMonitoring(); console.log('Behavior monitoring started'); ``` --- ##### `detectBot()` Analyzes collected behavior data to detect if the current user is likely a bot or headless browser. **Parameters:** None **Returns:** An object: ```javascript { botLikely: boolean, // true if bot-like behavior detected confidence: number, // 0-1: how confident we are signals: { mouseEntropy: number, // Randomness of mouse movements (low = bot-like) rapidClickCount: number, // Number of unnaturally rapid clicks hasMouseMovement: boolean, // true if any mouse movement detected hasClickActivity: boolean, // true if any clicks detected hasKeyboardActivity: boolean, // true if any keyboard input detected headlessBrowserIndicators: boolean, // true if running in headless browser screenTouchCapable: boolean // true if device has touch screen }, botScore: number // 0-1: overall bot likelihood score } ``` **Example:** ```javascript import { startBehaviorMonitoring, detectBot } from 'client-trace'; // On page load startBehaviorMonitoring(); // Later, before a sensitive action (e.g., form submission) document.getElementById('submitBtn').addEventListener('click', () => { const botCheck = detectBot(); console.log('Bot likelihood:', (botCheck.botScore * 100).toFixed(0) + '%'); if (botCheck.botLikely) { console.warn('🤖 Bot-like behavior detected!', botCheck.signals); // Options: // 1. Show CAPTCHA // 2. Block submission // 3. Send to server for additional verification if (botCheck.confidence > 0.9) { alert('Please complete a CAPTCHA to continue'); return; } } // Proceed with form submission console.log('Behavior looks human ✓'); }); // Additional signal details console.log('Mouse entropy:', botCheck.signals.mouseEntropy, '(0=none, 1=highly random)'); console.log('Rapid clicks:', botCheck.signals.rapidClickCount); console.log('Headless browser:', botCheck.signals.headlessBrowserIndicators); ``` --- #### 5. Security Monitoring ##### `detectInjections()` Monitors the DOM for unexpected `<script>` tags that could indicate malicious script injection or XSS attacks. **Parameters:** None **Returns:** An object: ```javascript { injectionsDetected: boolean, // true if unknown scripts found injectedScripts: Array<{ src: string, // Script URL or 'inline' trusted: boolean, // false if not in whitelist timestamp: number // When detected }>, trustedScripts: string[], // Scripts you've whitelisted recommendations: string[] // Suggested actions } ``` **Example:** ```javascript import { detectInjections } from 'client-trace'; const injectionReport = detectInjections(); if (injectionReport.injectionsDetected) { console.error('⚠️ Potential script injection detected!'); injectionReport.injectedScripts.forEach(script => { console.error(`Untrusted script: ${script.src}`); }); // Alert the user or server fetch('/api/security-alert', { method: 'POST', body: JSON.stringify({ type: 'script-injection', scripts: injectionReport.injectedScripts }) }); } else { console.log('No unauthorized scripts detected ✓'); } ``` --- ##### `listenForCSPViolations(onViolation)` Listens for Content Security Policy (CSP) violation events and calls a callback whenever a violation occurs. **Parameters:** - `onViolation` (function): Callback function that receives violation details **Returns:** An object: ```javascript { isListening: boolean, // true if listener is active violationCount: number, // Total violations captured stopListening: function, // Call to remove the listener violations: Array<{ blockedUri: string, violatedDirective: string, // e.g., 'script-src' originalPolicy: string, timestamp: number }> } ``` **Example:** ```javascript import { listenForCSPViolations } from 'client-trace'; const cspListener = listenForCSPViolations((violation) => { console.warn('CSP Violation detected:'); console.warn(` Blocked URI: ${violation.blockedUri}`); console.warn(` Directive: ${violation.violatedDirective}`); // Send to your server for analysis fetch('/api/csp-violations', { method: 'POST', body: JSON.stringify(violation) }); }); console.log('CSP violations are being monitored'); // Later, if you want to stop listening: // cspListener.stopListening(); ``` --- ##### `checkStorageIntegrity(storageType, checkInterval)` Verifies that `localStorage` or `sessionStorage` hasn't been modified externally (e.g., by browser extensions or other tabs). **Parameters:** - `storageType` (string): Either `'local'` or `'session'` (default: `'local'`) - `checkInterval` (number): How often to check in milliseconds (default: 5000) **Returns:** An object: ```javascript { isIntact: boolean, // true if storage hasn't been tampered with tamperedKeys: string[], // Keys that have been modified addedKeys: string[], // Keys that were added externally removedKeys: string[], // Keys that were removed externally stopMonitoring: function, // Call to stop integrity checks lastCheckTime: number // Timestamp of last check } ``` **Example:** ```javascript import { checkStorageIntegrity } from 'client-trace'; // Start monitoring localStorage const storageCheck = checkStorageIntegrity('local', 3000); if (!storageCheck.isIntact) { console.error('⚠️ Local storage has been tampered with!'); console.error('Tampered keys:', storageCheck.tamperedKeys); console.error('Added keys:', storageCheck.addedKeys); // Clear potentially compromised data localStorage.clear(); // Alert server fetch('/api/security-alert', { method: 'POST', body: JSON.stringify({ type: 'storage-tampering', tamperedKeys: storageCheck.tamperedKeys }) }); } // Stop monitoring when done // storageCheck.stopMonitoring(); ``` --- #### 6. Secure Transport ##### `signPayload(payload, secret)` Signs a data payload using HMAC-SHA256 to ensure authenticity and prevent tampering in transit. **Parameters:** - `payload` (object or string): Data to sign - `secret` (string): Shared secret (minimum 32 characters recommended) **Returns:** Promise resolving to an object: ```javascript { payload: any, // The original payload signature: string, // HMAC-SHA256 signature (hex) algorithm: string, // Always "HMAC-SHA256" timestamp: number // When signed } ``` **Example:** ```javascript import { signPayload } from 'client-trace'; const data = { userId: '12345', action: 'login', timestamp: Date.now() }; const signed = await signPayload(data, 'your-shared-secret-key'); console.log('Payload:', signed.payload); console.log('Signature:', signed.signature); // Send both payload and signature to server fetch('/api/secure-action', { method: 'POST', body: JSON.stringify(signed), headers: { 'Content-Type': 'application/json' } }); // Server-side: verify using the same secret // Server should recompute: HMAC-SHA256(payload, secret) // and compare with received signature ``` --- ##### `encryptTelemetry(payload, secret)` Encrypts sensitive telemetry data using AES-256-GCM encryption for end-to-end security. **Parameters:** - `payload` (object): Data to encrypt - `secret` (string): Encryption key (minimum 32 characters) **Returns:** Promise resolving to an object: ```javascript { encrypted: string, // Encrypted payload (base64) iv: string, // Initialization vector (base64) authTag: string, // Authentication tag for integrity (base64) algorithm: string, // "AES-256-GCM" nonce: string, // Replay protection nonce timestamp: number // When encrypted } ``` **Example:** ```javascript import { encryptTelemetry } from 'client-trace'; const telemetry = { event: 'user-action', userId: 'user-123', ipAddress: '192.168.1.1', sessionId: 'sess-456' }; const encrypted = await encryptTelemetry(telemetry, 'your-shared-secret-key'); console.log('Encrypted payload:', encrypted.encrypted); console.log('IV:', encrypted.iv); console.log('Auth tag:', encrypted.authTag); // Send encrypted data to server fetch('/api/telemetry', { method: 'POST', body: JSON.stringify({ data: encrypted.encrypted, iv: encrypted.iv, authTag: encrypted.authTag, nonce: encrypted.nonce }) }); // Server-side decryption: // 1. Get IV, authTag, and nonce from request // 2. Verify nonce hasn't been used before (replay protection) // 3. Decrypt using: decipher.update(encrypted, 'base64') + decipher.final() // 4. Verify auth tag ``` --- ##### `decryptTelemetry(encrypted, iv, authTag, secret)` Decrypts telemetry data that was encrypted with `encryptTelemetry`. **Parameters:** - `encrypted` (string): Encrypted payload (base64) - `iv` (string): Initialization vector from encryption (base64) - `authTag` (string): Authentication tag from encryption (base64) - `secret` (string): Same secret used during encryption **Returns:** Promise resolving to an object: ```javascript { decrypted: object, // Original decrypted payload verified: boolean, // true if auth tag is valid algorithm: string // "AES-256-GCM" } ``` **Example:** ```javascript import { decryptTelemetry } from 'client-trace'; // Assuming server sent back encrypted data const response = await fetch('/api/telemetry/config'); const { data, iv, authTag } = await response.json(); const decrypted = await decryptTelemetry(data, iv, authTag, 'your-shared-secret-key'); if (decrypted.verified) { console.log('Decrypted config:', decrypted.decrypted); console.log('Integrity verified ✓'); // Use the decrypted configuration applyConfig(decrypted.decrypted); } else { console.error('Authentication tag verification failed!'); console.error('Data may have been tampered with'); } ``` --- ##### `getNonce()` Generates a unique, rotating nonce for replay protection. Each call returns a new nonce that can be validated on the server to ensure requests aren't replayed. **Parameters:** None **Returns:** An object: ```javascript { nonce: string, // Unique nonce value (hex) timestamp: number, // When nonce was generated expiresAt: number, // When nonce becomes invalid (10 minutes) isValid: boolean // true if nonce hasn't expired } ``` **Example:** ```javascript import { getNonce } from 'client-trace'; // Before making a sensitive API request const nonce = getNonce(); fetch('/api/sensitive-action', { method: 'POST', headers: { 'X-Nonce': nonce.nonce }, body: JSON.stringify({ action: 'transfer-funds', amount: 100 }) }); // Server-side: // 1. Check if nonce has been used before (in a cache/database) // 2. Verify nonce hasn't expired // 3. Mark nonce as "used" // 4. Proceed with action only if nonce is valid and unused // Client-side error handling: if (!nonce.isValid) { console.error('Nonce has expired - get a new one'); } console.log('Nonce expires in:', Math.round((nonce.expiresAt - Date.now()) / 1000), 'seconds'); ``` ## Modules Overview | Category | Module | Description | |----------|--------|-------------| | **Integrity** | `verifyBundleIntegrity` | Checks if the script file matches expected hash. | | | `generateSessionToken` | Creates a signed token binding user to IP/UA. | | **Network** | `detectNetworkAPITampering` | Checks if `fetch` or `XHR` are native code. | | | `detectProxy` | Inspects headers for proxy signatures. | | | `detectTimingAnomalies` | Measures DNS/TTFB to find MITM delays. | | **Fingerprint** | `getDeviceFingerprint` | Hashes non-unique signals (screen, OS, timezone). | | | `detectBot` | Analyzes entropy of mouse moves and clicks. | | **Security** | `detectInjections` | Monitors DOM for new `<script>` tags. | | | `listenForCSPViolations` | Captures CSP violation events. | | | `checkStorageIntegrity` | Verifies `localStorage` hasn't been changed externally. | | **Transport** | `signPayload` | Signs data with HMAC-SHA256. | | | `encryptTelemetry` | Encrypts data with AES-GCM. | | | `getNonce` | Generates rotating nonce for replay protection. | ## License ISC