@@ -2,7 +2,36 @@ import type { CaptureResult } from 'posthog-js'
22
33const REDACTED = '[redacted]'
44
5- const REDACTION_RULES : { pattern : RegExp ; replacement : string } [ ] = [
5+ // Property keys (substring match) whose values can carry sensitive data: URLs
6+ // may embed tokens, user agents fingerprint users. Redacted following PostHog's
7+ // recommended before_send pattern:
8+ // https://posthog.com/tutorials/web-redact-properties
9+ const REDACTED_PROPERTY_KEYS = [
10+ 'url' ,
11+ 'href' ,
12+ 'pathname' ,
13+ 'referrer' ,
14+ 'host' ,
15+ 'user_agent' ,
16+ ]
17+
18+ function filterProperties (
19+ properties : Record < string , unknown >
20+ ) : Record < string , unknown > {
21+ return Object . entries ( properties ) . reduce < Record < string , unknown > > (
22+ ( acc , [ key , value ] ) => {
23+ acc [ key ] = REDACTED_PROPERTY_KEYS . some ( ( prop ) => key . includes ( prop ) )
24+ ? null
25+ : value
26+ return acc
27+ } ,
28+ { }
29+ )
30+ }
31+
32+ // Secret values that may be embedded in the exception message/stack text, which
33+ // (unlike URL properties) we keep rather than null so errors stay debuggable.
34+ const SECRET_VALUE_RULES : { pattern : RegExp ; replacement : string } [ ] = [
635 // JWTs (header.payload.signature)
736 {
837 pattern : / \b e y J [ A - Z a - z 0 - 9 _ - ] + \. [ A - Z a - z 0 - 9 _ - ] + \. [ A - Z a - z 0 - 9 _ - ] + / g,
@@ -21,34 +50,37 @@ const REDACTION_RULES: { pattern: RegExp; replacement: string }[] = [
2150 } ,
2251]
2352
24- function redact ( value : string ) : string {
25- return REDACTION_RULES . reduce (
53+ function redactSecrets ( value : string ) : string {
54+ return SECRET_VALUE_RULES . reduce (
2655 ( acc , { pattern, replacement } ) => acc . replace ( pattern , replacement ) ,
2756 value
2857 )
2958}
3059
3160/**
32- * `before_send` hook that strips credentials from $exception event text before
33- * it leaves the browser, so raw error messages/stacks can't leak secrets to
34- * PostHog. Covers both manual captureException and automatic capture_exceptions.
61+ * `before_send` hook that scrubs $exception events before they leave the
62+ * browser: nulls URL/user-agent properties (token leak + fingerprinting) and
63+ * strips embedded secrets from the error message text. Scoped to exceptions so
64+ * web analytics keeps its URL/referrer context.
3565 */
3666export function sanitizePostHogEvent (
3767 event : CaptureResult | null
3868) : CaptureResult | null {
3969 if ( ! event || event . event !== '$exception' ) return event
4070
41- const props = event . properties
42- if ( ! props ) return event
71+ event . properties = filterProperties ( event . properties ?? { } )
72+ if ( event . $set ) event . $set = filterProperties ( event . $set )
73+ if ( event . $set_once ) event . $set_once = filterProperties ( event . $set_once )
4374
44- if ( typeof props . $exception_message === 'string' ) {
45- props . $exception_message = redact ( props . $exception_message )
75+ const message = event . properties . $exception_message
76+ if ( typeof message === 'string' ) {
77+ event . properties . $exception_message = redactSecrets ( message )
4678 }
4779
48- if ( Array . isArray ( props . $exception_list ) ) {
49- for ( const item of props . $exception_list ) {
80+ if ( Array . isArray ( event . properties . $exception_list ) ) {
81+ for ( const item of event . properties . $exception_list ) {
5082 if ( item && typeof item . value === 'string' ) {
51- item . value = redact ( item . value )
83+ item . value = redactSecrets ( item . value )
5284 }
5385 }
5486 }
0 commit comments