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
| Component | File | Purpose |
|---|---|---|
SentinelManager | sentinel-manager.tsx | React wrapper; creates & initialises Sentinel |
Sentinel | sentinel.ts | Core orchestrator |
ConfigManager | config-manager.ts | Server-driven feature-flag & path matching |
TokenManager | token-manager.ts | Persistent session token via localStorage |
| Collectors | collector/ | Six signal collectors |
FetchInterceptor | interceptor/ | window.fetch monkey-patch & middleware chain |
Reporter | reporter/ | Batched signal flushing with retry |
SignalStore | storage/ | Dual-bucket signal storage with persistence |
CryptoKit | crypto-kit/ | AES-GCM request/response encryption |
Theia | theia/ | Device fingerprinting & telemetry |
Signal Types
Sentinel captures six categories of signal, each identified by signalType:
| Type | Data Shape | Collected 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())
- Generates device fingerprint via
Theia.getMeta()andTheia.getProperties(). - Sends an
InitializeRequesttoPOST /sen/i(domainfsp). - Receives a session token and server-side config.
- Stores the token via
TokenManager. - Applies the config via
ConfigManager. - 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:
- Guard: if not enabled via
ConfigManager, discard. - Store in
SignalStore(context + reporter buckets). - Tell the
Reporterto flush if the size threshold is met.
Fetch Interception
During construction, Sentinel creates a FetchInterceptor that:
- Injects sentinel context (token + recent signals) into outgoing
POSTrequests to protected paths. - Feeds fetch & tracking collectors with request metadata.
- Applies the
CryptoKitencryption 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
| Endpoint | Method | Purpose |
|---|---|---|
/sen/i | POST | Initialise session — exchange fingerprint for token + config |
/sen/ss | POST | Submit 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 Key | Scenario |
|---|---|
sen.sdk_init_failed | Initialisation failed after retries |
sen.reporter_flush_failed | Reporter could not submit signals |
sen.pst_unsupported | Persistent storage not available |
sen.pst_load_failed | Failed to read persisted signals |
cryptokit.protection_unavailable | Web Crypto API missing |
cryptokit.handle_request_error | Encryption failure |
cryptokit.handle_response_error | Decryption 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
localStoragewith a well-known key from@traveloka/app-meta. - Signals persisted to
localStorageare 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