Security Implications of FadCam's Remote Streaming: Token-Based Authentication and E2E Encryption
FadCam's remote streaming implements defense-in-depth through short-lived handoff tokens, session-isolated JWTs, and optional end-to-end encryption to prevent unauthorized access, replay attacks, and server-side data exposure.
FadCam is an open-source Android surveillance application that supports remote streaming via cloud infrastructure. Understanding the security implications of FadCam's remote streaming requires analyzing its token-based authentication flow and cryptographic safeguards implemented in the web-based remote interface. The architecture deliberately isolates cloud streaming from local LAN access through strict hostname validation and multi-layered session management.
Authentication Flow and Token Exchange
Handoff Token to Session Exchange
The remote streaming initiation relies on a single-use handoff token exchanged for persistent session credentials. When a user clicks "Open Stream" in the Lab interface, the system generates a handoff JWT embedded in the URL (?token=). The FadCamRemote.exchangeHandoffToken() method in app/src/main/assets/web/js/fadcam-remote.js immediately exchanges this token via the Supabase Edge Function exchange-handoff-token.
Upon successful exchange, the function returns a session hint, user record, stream-access JWT, and an optional E2E verify tag. The client immediately sanitizes the URL using cleanUrl.searchParams.delete('token') and window.history.replaceState to ensure the handoff token never persists in browser history or the address bar.
Stream-Access JWT Management
The stream-access JWT is stored in localStorage under the key CLOUD_CONFIG.STORAGE_KEYS.STREAM_TOKEN, not in cookies or URL parameters. The FadCamRemote.getStreamToken() method retrieves this value when constructing HLS requests. This design prevents cross-site request forgery (CSRF) attacks and ensures tokens are only sent to explicitly configured streaming origins.
Session Validation and Security Checks
Server-Side Token Verification
Before reusing any cached session, FadCamRemote.initStreamContext() validates the JWT against the Supabase Edge Function verify-stream-token. This server-side check ensures the token has not been revoked or expired due to policy changes. If the server returns 401 Unauthorized, the client immediately wipes all stored credentials from localStorage and redirects to the Lab login page.
Client-Side Expiry Handling
As a defense against clock skew or temporary offline usage, the client parses the JWT payload using atob(parts[1]) and validates the exp claim locally. If initStreamContext() detects an expired token client-side, it proactively clears CLOUD_CONFIG.STORAGE_KEYS.SESSION and CLOUD_CONFIG.STORAGE_KEYS.STREAM_TOKEN, forcing a fresh authentication flow before any network requests expose the stale token.
End-to-End Encryption Architecture
For streams marked with an e2e-verify-tag, FadCam implements end-to-end encryption of HLS fragments. The FadCamRemote.checkAndShowE2EUnlock() method checks IndexedDB for an existing decryption key via E2EKeyManager.isInitialized(). If absent, the UI presents a password modal that requires the user's LabPass to derive the encryption key.
The FadCamFragLoader class (when present in app/src/main/assets/web/js/services/FadCamFragLoader.js) intercepts each media segment downloaded by HLS.js. It decrypts fragments on-the-fly using the derived key, ensuring that even if the streaming server (live.fadseclab.com) is compromised, the video content remains ciphertext without the user's password.
Network Security and Access Controls
Hostname Whitelisting
The FadCamRemote.isWebAccess() function enforces strict origin validation before enabling any cloud features. Remote streaming functionality activates only on whitelisted hostnames (fadseclab.com, localhost). LAN/IP-based accesses bypass the remote stack entirely, preventing accidental exposure of cloud tokens when accessing the device directly via local network addresses.
Request Rate Limiting
The HlsService configuration in app/src/main/assets/web/js/services/HlsService.js implements DoS protection through fragLoadPolicy and manifestLoadPolicy settings. These policies limit retry attempts and implement exponential back-off delays when encountering 4xx or 5xx errors, preventing brute-force attacks against the token validation endpoints.
Secure Token Cleanup
The AuthService.logout() method (located in app/src/main/assets/web/js/services/AuthService.js) performs complete session termination. It removes tokens from localStorage, clears IndexedDB encryption keys, and notifies the server to invalidate the session server-side, ensuring that stolen tokens cannot be replayed even if extracted from a compromised browser profile.
Code Implementation Examples
Starting a Remote Stream
// Extract device ID from path and initialize secure context
const deviceId = FadCamRemote.getStreamDeviceId(); // → "abc123"
FadCamRemote.initStreamContext(deviceId); // Handles token exchange and validation
Source: FadCamRemote.initStreamContext() in app/src/main/assets/web/js/fadcam-remote.js.
Retrieving the HLS Playlist
const playlistUrl = FadCamRemote.getRelayHlsUrl();
// → https://live.fadseclab.com:8443/stream/<user>/<device>/live.m3u8
Source: FadCamRemote.getRelayHlsUrl() in app/src/main/assets/web/js/fadcam-remote.js.
Automatic JWT Injection in HLS Requests
// Inside HlsService._getHlsConfig()
baseConfig.xhrSetup = function (xhr, url) {
if ((url.includes('/stream/') || url.includes('live.fadseclab.com')) && !url.includes('token=')) {
const separator = url.includes('?') ? '&' : '?';
const authUrl = `${url}${separator}token=${encodeURIComponent(streamToken)}`;
xhr.open('GET', authUrl, true);
}
};
Source: HlsService._getHlsConfig() in app/src/main/assets/web/js/services/HlsService.js.
Handling Token Expiration
// Server returned 401 or local expiry detected
localStorage.removeItem(CLOUD_CONFIG.STORAGE_KEYS.SESSION);
localStorage.removeItem(CLOUD_CONFIG.STORAGE_KEYS.STREAM_TOKEN);
window.location.href = CLOUD_CONFIG.LAB_URL; // Force re-authentication
Source: initStreamContext() error handling in app/src/main/assets/web/js/fadcam-remote.js.
E2E Encryption Unlock Flow
// Check for encrypted stream and prompt for LabPass if needed
if (verifyTag && !(await E2EKeyManager.isInitialized())) {
FadCamRemote.showE2EUnlockModal();
}
Source: FadCamRemote.checkAndShowE2EUnlock() in app/src/main/assets/web/js/fadcam-remote.js.
Summary
- Short-lived tokens: Handoff tokens are exchanged immediately and deleted from the URL, while stream JWTs are validated on every request.
- Defense in depth: Server-side verification via
verify-stream-tokencombined with client-sideexpclaim parsing prevents session hijacking. - E2E encryption: Optional fragment-level encryption ensures video content remains inaccessible even if the streaming infrastructure is compromised.
- Origin isolation: Strict hostname whitelisting separates cloud and local LAN access paths, preventing token leakage to unauthorized origins.
- Automatic cleanup: 401 responses trigger immediate local storage wiping, ensuring revoked tokens cannot persist for replay attacks.
Frequently Asked Questions
How does FadCam prevent replay attacks using stolen tokens?
FadCam mitigates replay attacks through short-lived handoff tokens and server-side session validation. The initial handoff token is single-use and exchanged immediately upon page load. Subsequent stream-access JWTs are validated by the verify-stream-token Edge Function on every HLS segment request. If a token is revoked or expired, the server returns 401, triggering immediate client-side deletion of all session data via localStorage.removeItem().
What happens when a stream token expires during active viewing?
When the JWT exp claim lapses or the server returns 401, FadCamRemote.initStreamContext() clears both CLOUD_CONFIG.STORAGE_KEYS.SESSION and CLOUD_CONFIG.STORAGE_KEYS.STREAM_TOKEN from localStorage. The user is then redirected to the Lab authentication URL (CLOUD_CONFIG.LAB_URL) to obtain fresh credentials, ensuring no stale tokens remain in the browser.
Is video content encrypted during remote streaming?
Remote streaming supports optional end-to-end encryption when the e2e-verify-tag is present in the session metadata. In this mode, HLS fragments are encrypted server-side and decrypted client-side by FadCamFragLoader using a key derived from the user's LabPass. Without the correct password, intercepted stream segments remain unreadable ciphertext, protecting against server compromise and man-in-the-middle attacks.
How does FadCam distinguish between local and remote access?
The FadCamRemote.isWebAccess() function performs strict hostname validation, enabling cloud features only on fadseclab.com and localhost. Direct IP or local network access bypasses the remote streaming stack entirely, preventing accidental exposure of cloud authentication tokens when users access the device via LAN. This architectural separation ensures local usage never invokes exchangeHandoffToken() or stores cloud JWTs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →