Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions browser-extension/common/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ export interface SessionRecordingConfig {
widgetPosition?: { top?: number; bottom?: number; left?: number; right?: number };
recordingStartTime?: number;
previousSession?: any;
// Per-recording relay auth (RQ-3095 / RQ-3096): a shared secret token and the
// top-frame origin, threaded to every frame so cross-origin iframe relay is
// authenticated (token) and not broadcast to untrusted frames (trustedOrigin).
relayToken?: string;
trustedOrigin?: string;
}

export interface RulePair {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ const sendStartRecordingEvent = async (sessionRecordingConfig: SessionRecordingC
showWidget,
widgetPosition,
previousSession,
relayToken,
trustedOrigin,
} = sessionRecordingConfig;

const isIFrame = isIframe();
Expand All @@ -95,6 +97,10 @@ const sendStartRecordingEvent = async (sessionRecordingConfig: SessionRecordingC
maxDuration: (sessionRecordingConfig.maxDuration || 5) * 60 * 1000, // minutes -> milliseconds
previousSession: !isIFrame ? previousSession : null,
localStorage: true,
// Relay auth: top frame uses relayToken to validate incoming events; iframes use
// both to authenticate and to target the relay at the top origin (RQ-3095/RQ-3096).
relayToken,
trustedOrigin,
});

sessionRecorderState.isExplicitRecording = explicit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ import { isExtensionEnabled } from "../../utils";

const CONFIG_STORAGE_KEY = "sessionRecordingConfig";

const getTopFrameOrigin = (url?: string): string | undefined => {
try {
return url ? new URL(url).origin : undefined;
} catch {
return undefined;
}
};

// Ensures the per-recording relay auth fields (RQ-3095 / RQ-3096) are present and
// stable across every frame of the tab: one random token, generated once and reused
// (so top and iframes share it), plus the top-frame origin used as the relay
// postMessage targetOrigin. Both are broadcast to all frames via startRecording.
const withRelayAuth = (data: Record<string, any>, topUrl?: string): Record<string, any> => ({
...data,
relayToken: data.relayToken ?? crypto.randomUUID(),
trustedOrigin: getTopFrameOrigin(topUrl),
});

const getSessionRecordingConfig = async (url: string): Promise<SessionRecordingConfig> => {
const sessionRecordingConfig = await getRecord<SessionRecordingConfig>(CONFIG_STORAGE_KEY);

Expand Down Expand Up @@ -100,7 +118,7 @@ export const startRecordingExplicitly = async (tab: chrome.tabs.Tab, showWidget:
return;
}

const sessionRecordingData = { explicit: true, showWidget };
const sessionRecordingData = withRelayAuth({ explicit: true, showWidget }, tab.url);
tabService.setData(tab.id, TAB_SERVICE_DATA.SESSION_RECORDING, sessionRecordingData);

startRecording(tab.id, sessionRecordingData);
Expand Down Expand Up @@ -145,6 +163,11 @@ export const handleSessionRecordingOnClientPageLoad = async (tab: chrome.tabs.Ta
}

if (sessionRecordingData) {
// Attach/keep the stable relay token + top origin before broadcasting so every
// frame (top and iframes, including ones that load later) receives the same values.
sessionRecordingData = withRelayAuth(sessionRecordingData, tab.url);
tabService.setData(tab.id, TAB_SERVICE_DATA.SESSION_RECORDING, sessionRecordingData);

startRecording(tab.id, sessionRecordingData).then(() => {
tabService.setData(tab.id, TAB_SERVICE_DATA.SESSION_RECORDING, {
...sessionRecordingData,
Expand Down