Skip to content

Commit

Permalink
update signal redaction logic to only obfuscate interaction values
Browse files Browse the repository at this point in the history
  • Loading branch information
silesky committed Sep 27, 2024
1 parent bedea03 commit bfb8e22
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 14 deletions.
31 changes: 29 additions & 2 deletions packages/signals/signals/src/core/client/__tests__/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe(SignalsIngestClient, () => {
await client.init({ writeKey: 'test' })
})

it('makes a track call via the analytics api', async () => {
it('makes an instrumentation track call via the analytics api', async () => {
expect(client).toBeTruthy()
const ctx = await client.send({
type: 'instrumentation',
Expand All @@ -32,9 +32,36 @@ describe(SignalsIngestClient, () => {
index: 0,
data: {
rawEvent: {
foo: 'XXX',
foo: 'bar',
},
},
})
})
it('makes a network track call via the analytics api', async () => {
expect(client).toBeTruthy()
const ctx = await client.send({
type: 'network',
data: {
action: 'Request',
data: {
hello: 'how are you',
},
method: 'post',
url: 'http://foo.com',
},
})

expect(ctx!.event.type).toEqual('track')
expect(ctx!.event.properties!.type).toBe('network')
expect(ctx!.event.properties!.data).toMatchInlineSnapshot(`
{
"action": "Request",
"data": {
"hello": "XXX",
},
"method": "post",
"url": "http://foo.com",
}
`)
})
})
62 changes: 60 additions & 2 deletions packages/signals/signals/src/core/client/__tests__/redact.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { redactJsonValues } from '../redact'
import {
createInstrumentationSignal,
createInteractionSignal,
createNetworkSignal,
} from '../../../types'
import { redactJsonValues, redactSignalData } from '../redact'

describe('redactJsonValues', () => {
describe(redactJsonValues, () => {
it('should redact string values in an object', () => {
const obj = { name: 'John Doe', age: '30' }
const expected = { name: 'XXX', age: 'XXX' }
Expand Down Expand Up @@ -54,3 +59,56 @@ describe('redactJsonValues', () => {
expect(redactJsonValues(obj, 3)).toEqual(expected)
})
})

describe(redactSignalData, () => {
it('should return the signal as is if the type is "instrumentation"', () => {
const signal = createInstrumentationSignal({
foo: 123,
} as any)
expect(redactSignalData(signal)).toEqual(signal)
})

it('should return the signal as is if the type is "userDefined"', () => {
const signal = { type: 'userDefined', data: { value: 'secret' } } as const
expect(redactSignalData(signal)).toEqual(signal)
})

it('should redact the value in the "target" property if the type is "interaction"', () => {
const signal = createInteractionSignal({
eventType: 'change',
target: { value: 'secret' },
})
const expected = createInteractionSignal({
eventType: 'change',
target: { value: 'XXX' },
})
expect(redactSignalData(signal)).toEqual(expected)
})

it('should redact the values in the "data" property if the type is "network"', () => {
const signal = createNetworkSignal({
action: 'Request',
method: 'post',
url: 'http://foo.com',
data: { name: 'John Doe', age: 30 },
})
const expected = createNetworkSignal({
action: 'Request',
method: 'post',
url: 'http://foo.com',
data: { name: 'XXX', age: 999 },
})
expect(redactSignalData(signal)).toEqual(expected)
})

it('should not mutate the original signal object', () => {
const originalSignal = createInteractionSignal({
eventType: 'click',
target: { value: 'sensitiveData' },
})
const originalSignalCopy = JSON.parse(JSON.stringify(originalSignal))

redactSignalData(originalSignal)
expect(originalSignal).toEqual(originalSignalCopy)
})
})
10 changes: 4 additions & 6 deletions packages/signals/signals/src/core/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Analytics, segmentio } from '@segment/analytics-next'
import { logger } from '../../lib/logger'
import { Signal } from '../../types'
import { redactJsonValues } from './redact'
import { redactSignalData } from './redact'

export class SignalsIngestSettings {
flushAt: number
Expand Down Expand Up @@ -74,20 +74,18 @@ export class SignalsIngestClient {
throw new Error('Please initialize before calling this method.')
}
const disableRedaction = this.settings.shouldDisableSignalRedaction()
const data = disableRedaction
? signal.data
: redactJsonValues(signal.data, 2)
const cleanSignal = disableRedaction ? signal : redactSignalData(signal)

if (disableRedaction) {
logger.debug('Sending unredacted data to segment', data)
logger.debug('Sending unredacted data to segment', cleanSignal)
}

const MAGIC_EVENT_NAME = 'Segment Signal Generated'

return this.analytics.track(MAGIC_EVENT_NAME, {
index: this.index++,
type: signal.type,
data: data,
data: cleanSignal.data,
})
}

Expand Down
18 changes: 16 additions & 2 deletions packages/signals/signals/src/core/client/redact.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
function redact(value: unknown) {
import { Signal } from '../../types'

export const redactSignalData = (signalArg: Signal): Signal => {
const signal = structuredClone(signalArg)
if (signal.type === 'interaction') {
if ('target' in signal.data && 'value' in signal.data.target) {
signal.data.target.value = redactJsonValues(signal.data.target.value)
}
} else if (signal.type === 'network') {
signal.data = redactJsonValues(signal.data, 2)
}
return signal
}

function redactPrimitive(value: unknown) {
const type = typeof value
if (type === 'boolean') {
return true
Expand Down Expand Up @@ -31,7 +45,7 @@ export function redactJsonValues(data: unknown, redactAfterDepth = 0): any {
return redactedData
}
} else if (redactAfterDepth <= 0) {
const ret = redact(data)
const ret = redactPrimitive(data)
return ret
} else {
return data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('SignalsRuntime', () => {
beforeEach(() => {
signal1 = createInstrumentationSignal({ type: 'track' })
signal2 = createInteractionSignal({ eventType: 'submit', submitter: {} })
signal3 = createInteractionSignal({ eventType: 'change' })
signal3 = createInteractionSignal({ eventType: 'change', target: {} })
signalsRuntime = createSignalsRuntime([signal1, signal2, signal3])
})

Expand Down
2 changes: 1 addition & 1 deletion packages/signals/signals/src/types/signals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type SubmitData = {

type ChangeData = {
eventType: 'change'
[key: string]: unknown
target: SerializedTarget
}

export type InteractionSignal = AppSignal<'interaction', InteractionData>
Expand Down

0 comments on commit bfb8e22

Please sign in to comment.