Skip to main content

Sentinel

Sentinel is Traveloka's client-side security and fraud-prevention SDK. It runs in the browser, silently collecting behavioural signals (fetches, clicks, navigations, tracking events, app-state transitions, and custom events), then batching and reporting them to the Fraud & Security Platform (FSP). Outbound requests can optionally be protected with end-to-end encryption via CryptoKit.

High-Level Architecture

┌─────────────────────────────────────────────────────────┐
│ SentinelManager │
│ (React component, entry-point) │
└──────────────────────┬──────────────────────────────────┘
│ mounts

┌─────────────────────────────────────────────────────────┐
│ Sentinel │
│ (orchestrator – lifecycle & wiring) │
│ │
│ ┌──────────┐ ┌────────────┐ ┌─────────────────────┐ │
│ │ Token │ │ Config │ │ SignalStore │ │
│ │ Manager │ │ Manager │ │ (in-memory + persist)│ │
│ └──────────┘ └────────────┘ └─────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Collectors │ │
│ │ Fetch · Click · Tracking · Navigation │ │
│ │ Custom · AppState │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ FetchInterceptor │ │ Reporter │ │
│ │ (middleware pipe) │ │ (batched flush to FSP) │ │
│ └──────────────────┘ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ CryptoKit + Middleware │ │
│ │ (AES-GCM encrypt/decrypt layer) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

Key Components

ComponentFilePurpose
SentinelManagersentinel-manager.tsxReact wrapper; creates & initialises Sentinel
Sentinelsentinel.tsCore orchestrator
ConfigManagerconfig-manager.tsServer-driven feature-flag & path matching
TokenManagertoken-manager.tsPersistent session token via localStorage
Collectorscollector/Six signal collectors
FetchInterceptorinterceptor/window.fetch monkey-patch & middleware chain
Reporterreporter/Batched signal flushing with retry
SignalStorestorage/Dual-bucket signal storage with persistence
CryptoKitcrypto-kit/AES-GCM request/response encryption
Theiatheia/Device fingerprinting & telemetry

Signal Types

Sentinel captures six categories of signal, each identified by signalType:

TypeData ShapeCollected By
fetch{ host, url }FetchCollector
click{ identifier, screen }ClickCollector
tracking{ eventName }TrackingCollector
navigation{ screenName }NavigationCollector
custom{ name, payload }CustomCollector
appState{ state }AppStateCollector

Every signal is wrapped in a Signal envelope:

interface Signal<T extends SignalData = SignalData> {
id: string; // ULID (time-sortable unique ID)
ts: number; // creation timestamp (ms)
signalType: SignalType;
signal: T;
}

SentinelManager

SentinelManager is a headless React component (renders null) that serves as the entry-point. Mount it once in the application shell.

import { SentinelManager } from '@traveloka/core';

function App({ children }) {
return (
<>
<SentinelManager />
{children}
</>
);
}

It guards on CryptoKit.isProtectionSupported() — if the Web Crypto API is unavailable, Sentinel does not start.

Sentinel Class

The Sentinel class orchestrates the entire lifecycle:

Initialisation (initialize())

  1. Generates device fingerprint via Theia.getMeta() and Theia.getProperties().
  2. Sends an InitializeRequest to POST /sen/i (domain fsp).
  3. Receives a session token and server-side config.
  4. Stores the token via TokenManager.
  5. Applies the config via ConfigManager.
  6. If enabled, starts all collectors and the reporter.

Initialisation is wrapped in a RetryQueue (up to 3 attempts with exponential back-off starting at 200 ms).

Start / Stop

start() activates all six collectors and the reporter. stop() deactivates them. The config manager dynamically toggles between these states when new config arrives (e.g. kill-switch from backend).

Signal Handling

When any collector emits a Signal:

  1. Guard: if not enabled via ConfigManager, discard.
  2. Store in SignalStore (context + reporter buckets).
  3. Tell the Reporter to flush if the size threshold is met.

Fetch Interception

During construction, Sentinel creates a FetchInterceptor that:

  • Injects sentinel context (token + recent signals) into outgoing POST requests to protected paths.
  • Feeds fetch & tracking collectors with request metadata.
  • Applies the CryptoKit encryption middleware.

Custom Signal API

Domains can emit ad-hoc signals via:

window.__SEN.collect('payment_method_selected', { method: 'credit_card' });

Beacon Support

handleBeaconCall(url, payload) extracts the x-domain from query params and routes it through the same fetch/tracking collection pipeline.

Global Access

The Sentinel instance is attached to window.__SEN for debugging and programmatic access.

API Endpoints

EndpointMethodPurpose
/sen/iPOSTInitialise session — exchange fingerprint for token + config
/sen/ssPOSTSubmit signal batch — returns updated token + config

Both are routed through the fsp API domain.

Lifecycle Sequence

SentinelManager mount
└─► CryptoKit.isProtectionSupported()?
├─ No → exit
└─ Yes → new Sentinel()
├─ SignalStore (load from persistent storage)
├─ TokenManager (read token from localStorage)
├─ ConfigManager (defaults: disabled)
├─ FetchInterceptor (patch window.fetch)
├─ Reporter (wiring)
└─ Collectors (wiring)
sentinel.initialize()
├─ POST /sen/i { token, fingerprint }
├─ Store new token
├─ Apply config
└─ config.enabled?
├─ Yes → start() all collectors + reporter
└─ No → idle (await config change)

Configuration

The server returns a SentinelConfig:

interface SentinelConfig {
enabled: boolean;
contextSignalTypeSize: number; // max signals per type in context bucket
signalThresholdMs: number; // time-based flush interval (ms)
signalThresholdSize: number; // size-based flush trigger
paths: Array<SentinelPathConfig>; // URL patterns for request protection
}

See ConfigManager for details on path matching and normalisation.

Error Handling & Logging

Log KeyScenario
sen.sdk_init_failedInitialisation failed after retries
sen.reporter_flush_failedReporter could not submit signals
sen.pst_unsupportedPersistent storage not available
sen.pst_load_failedFailed to read persisted signals
cryptokit.protection_unavailableWeb Crypto API missing
cryptokit.handle_request_errorEncryption failure
cryptokit.handle_response_errorDecryption failure

SSR Safety

All browser-dependent code (DOM, window, document, crypto) is guarded. Sentinel is a no-op during server-side rendering.

Security Considerations

  • Tokens are stored in localStorage with a well-known key from @traveloka/app-meta.
  • Signals persisted to localStorage are XOR-obfuscated and compressed (deflate) — this provides tamper resistance, not cryptographic confidentiality.
  • Protected API requests are encrypted end-to-end with AES-128-GCM via CryptoKit.
  • Sentinel never collects PII; signals are limited to structural identifiers (URLs, element selectors, event names).

Last updated: 2026-02-24