mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
fix(web): redact invite replay requests
This commit is contained in:
1 parent
57c7ecebe6
commit
15c7894bec
2 files changed
+46
-2
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
import { readFile } from 'node:fs/promises'
|
import { readFile } from 'node:fs/promises'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { redactInvitePaths } from './providers'
|
import type { CapturedNetworkRequest } from 'posthog-js'
|
||||||
|
import { redactInviteNetworkRequest, redactInvitePaths } from './providers'
|
||||||
|
|
||||||
const pageViewPath = new URL('./PostHogPageView.tsx', import.meta.url)
|
const pageViewPath = new URL('./PostHogPageView.tsx', import.meta.url)
|
||||||
const providerPath = new URL('./providers.tsx', import.meta.url)
|
const providerPath = new URL('./providers.tsx', import.meta.url)
|
||||||
@@ -20,6 +21,7 @@ describe('PostHog invite privacy boundary', () => {
|
|||||||
expect(source).toContain('capture_pageleave: false')
|
expect(source).toContain('capture_pageleave: false')
|
||||||
expect(source).toContain('get_current_url: redactInviteUrl')
|
expect(source).toContain('get_current_url: redactInviteUrl')
|
||||||
expect(source).toContain('before_send: redactInvitePathsBeforeSend')
|
expect(source).toContain('before_send: redactInvitePathsBeforeSend')
|
||||||
|
expect(source).toContain('maskCapturedNetworkRequestFn: redactInviteNetworkRequest')
|
||||||
expect(source).toContain("url.pathname.startsWith('/join/')")
|
expect(source).toContain("url.pathname.startsWith('/join/')")
|
||||||
expect(source).toContain('/join/[invite]')
|
expect(source).toContain('/join/[invite]')
|
||||||
})
|
})
|
||||||
@@ -43,4 +45,32 @@ describe('PostHog invite privacy boundary', () => {
|
|||||||
expect(serialized).toContain('/join/[invite]')
|
expect(serialized).toContain('/join/[invite]')
|
||||||
expect(serialized).toContain('join_page_viewed')
|
expect(serialized).toContain('join_page_viewed')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('redacts invite codes from replay network request URLs, headers, and bodies without mutating the capture', () => {
|
||||||
|
const inviteCode = 'A4K9BT2Q'
|
||||||
|
const request = {
|
||||||
|
name: `https://sauci.app/join/${inviteCode}`,
|
||||||
|
entryType: 'resource',
|
||||||
|
startTime: 1,
|
||||||
|
duration: 2,
|
||||||
|
requestHeaders: {
|
||||||
|
referer: `https://sauci.app/join/${inviteCode}`,
|
||||||
|
},
|
||||||
|
requestBody: JSON.stringify({ invite_path: `/join/${inviteCode}` }),
|
||||||
|
responseHeaders: {
|
||||||
|
location: `https://sauci.app/join/${inviteCode}`,
|
||||||
|
},
|
||||||
|
responseBody: JSON.stringify({ referrer: `https://sauci.app/join/${inviteCode}` }),
|
||||||
|
} as CapturedNetworkRequest
|
||||||
|
|
||||||
|
const redacted = redactInviteNetworkRequest(request)
|
||||||
|
const serialized = JSON.stringify(redacted)
|
||||||
|
|
||||||
|
expect(redacted).not.toBe(request)
|
||||||
|
expect(redacted.requestHeaders).not.toBe(request.requestHeaders)
|
||||||
|
expect(redacted.responseHeaders).not.toBe(request.responseHeaders)
|
||||||
|
expect(serialized).not.toContain(inviteCode)
|
||||||
|
expect(serialized).toContain('/join/[invite]')
|
||||||
|
expect(JSON.stringify(request)).toContain(inviteCode)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import posthog from 'posthog-js'
|
import posthog from 'posthog-js'
|
||||||
import { PostHogProvider as PHProvider } from 'posthog-js/react'
|
import { PostHogProvider as PHProvider } from 'posthog-js/react'
|
||||||
import type { CaptureResult } from 'posthog-js'
|
import type { CapturedNetworkRequest, CaptureResult } from 'posthog-js'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { usePathname } from 'next/navigation'
|
import { usePathname } from 'next/navigation'
|
||||||
|
|
||||||
@@ -38,6 +38,17 @@ export function redactInvitePathsBeforeSend(event: CaptureResult | null): Captur
|
|||||||
return event ? redactInvitePaths(event) : null
|
return event ? redactInvitePaths(event) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function redactInviteNetworkRequest(request: CapturedNetworkRequest): CapturedNetworkRequest {
|
||||||
|
return {
|
||||||
|
...request,
|
||||||
|
name: redactInvitePaths(request.name),
|
||||||
|
requestHeaders: request.requestHeaders ? redactInvitePaths({ ...request.requestHeaders }) : request.requestHeaders,
|
||||||
|
requestBody: redactInvitePaths(request.requestBody),
|
||||||
|
responseHeaders: request.responseHeaders ? redactInvitePaths({ ...request.responseHeaders }) : request.responseHeaders,
|
||||||
|
responseBody: redactInvitePaths(request.responseBody),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
|
|
||||||
@@ -51,6 +62,9 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
|||||||
capture_pageleave: false,
|
capture_pageleave: false,
|
||||||
get_current_url: redactInviteUrl,
|
get_current_url: redactInviteUrl,
|
||||||
before_send: redactInvitePathsBeforeSend,
|
before_send: redactInvitePathsBeforeSend,
|
||||||
|
session_recording: {
|
||||||
|
maskCapturedNetworkRequestFn: redactInviteNetworkRequest,
|
||||||
|
},
|
||||||
// This must be part of initialization, not only the navigation effect
|
// This must be part of initialization, not only the navigation effect
|
||||||
// below, so an initially loaded invite page is protected before its DOM
|
// below, so an initially loaded invite page is protected before its DOM
|
||||||
// can be considered for capture or recording.
|
// can be considered for capture or recording.
|
||||||
|
|||||||
Reference in new issue
Block a user