Skip to content

Commit 12f8b83

Browse files
committed
refactor sensitive data capture
1 parent 5f5604d commit 12f8b83

File tree

4 files changed

+181
-171
lines changed

4 files changed

+181
-171
lines changed

packages/browser/src/__tests__/autocapture-utils.test.ts

Lines changed: 67 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
/// <reference lib="dom" />
22

3-
import sinon from 'sinon'
4-
53
import {
64
getSafeText,
7-
shouldCaptureDomEvent,
8-
shouldCaptureElement,
5+
shouldAutocaptureEvent,
96
isSensitiveElement,
107
shouldCaptureValue,
118
isAngularStyleAttr,
@@ -14,6 +11,8 @@ import {
1411
getElementsChainString,
1512
getClassNames,
1613
makeSafeText,
14+
isExplicitNoCapture,
15+
isExplicitCapture,
1716
} from '../autocapture-utils'
1817
import { document } from '../utils/globals'
1918
import { makeMouseEvent } from './autocapture.test'
@@ -121,6 +120,30 @@ describe(`Autocapture utility functions`, () => {
121120
el.innerHTML = `Mixed "double" and 'single' quotes`
122121
expect(getSafeText(el)).toBe(`Mixed "double" and 'single' quotes`)
123122
})
123+
124+
it(`should collect text from sensitive elements with ph-include class`, () => {
125+
const input = document!.createElement(`input`)
126+
input.className = `ph-include`
127+
input.appendChild(document.createTextNode(`Hello world`))
128+
document!.body.appendChild(input)
129+
expect(getSafeText(input)).toBe(`Hello world`)
130+
})
131+
132+
it(`shouldn't collect text from elements with ph-no-capture class`, () => {
133+
const el = document!.createElement(`div`)
134+
el.className = `ph-no-capture`
135+
el.innerHTML = `Hello world`
136+
document!.body.appendChild(el)
137+
expect(getSafeText(el)).toBe(``)
138+
})
139+
140+
it(`shouldn't collect text from elements with ph-sensitive class`, () => {
141+
const el = document!.createElement(`div`)
142+
el.className = `ph-sensitive`
143+
el.innerHTML = `Hello world`
144+
document!.body.appendChild(el)
145+
expect(getSafeText(el)).toBe(``)
146+
})
124147
})
125148

126149
describe(`makeSafeText`, () => {
@@ -175,22 +198,22 @@ describe(`Autocapture utility functions`, () => {
175198
describe(`shouldCaptureDomEvent`, () => {
176199
it(`should capture "submit" events on <form> elements`, () => {
177200
expect(
178-
shouldCaptureDomEvent(document!.createElement(`form`), {
201+
shouldAutocaptureEvent(document!.createElement(`form`), {
179202
type: `submit`,
180203
} as unknown as Event)
181204
).toBe(true)
182205
})
183206

184207
it.each([`input`, `SELECT`, `textarea`])(`should capture "change" events on <%s> elements`, (tagName) => {
185208
expect(
186-
shouldCaptureDomEvent(document!.createElement(tagName), {
209+
shouldAutocaptureEvent(document!.createElement(tagName), {
187210
type: `change`,
188211
} as unknown as Event)
189212
).toBe(true)
190213
})
191214

192215
it.each([`A`, `a`])(`should capture "click" events on <%s> elements`, (tagName) => {
193-
expect(shouldCaptureDomEvent(document!.createElement(tagName), makeMouseEvent({}))).toBe(true)
216+
expect(shouldAutocaptureEvent(document!.createElement(tagName), makeMouseEvent({}))).toBe(true)
194217
})
195218

196219
it(`should capture "click" events on <button> elements`, () => {
@@ -200,22 +223,22 @@ describe(`Autocapture utility functions`, () => {
200223
const button3 = document!.createElement(`input`)
201224
button3.setAttribute(`type`, `submit`)
202225
;[button1, button2, button3].forEach((button) => {
203-
expect(shouldCaptureDomEvent(button, makeMouseEvent({}))).toBe(true)
226+
expect(shouldAutocaptureEvent(button, makeMouseEvent({}))).toBe(true)
204227
})
205228
})
206229

207230
it(`should protect against bad inputs`, () => {
208-
expect(shouldCaptureDomEvent(null as unknown as Element, makeMouseEvent({}))).toBe(false)
209-
expect(shouldCaptureDomEvent(undefined as unknown as Element, makeMouseEvent({}))).toBe(false)
210-
expect(shouldCaptureDomEvent(`div` as unknown as Element, makeMouseEvent({}))).toBe(false)
231+
expect(shouldAutocaptureEvent(null as unknown as Element, makeMouseEvent({}))).toBe(false)
232+
expect(shouldAutocaptureEvent(undefined as unknown as Element, makeMouseEvent({}))).toBe(false)
233+
expect(shouldAutocaptureEvent(`div` as unknown as Element, makeMouseEvent({}))).toBe(false)
211234
})
212235

213236
it(`should NOT capture "click" events on <form> elements`, () => {
214-
expect(shouldCaptureDomEvent(document!.createElement(`form`), makeMouseEvent({}))).toBe(false)
237+
expect(shouldAutocaptureEvent(document!.createElement(`form`), makeMouseEvent({}))).toBe(false)
215238
})
216239

217240
it.each([`html`, 'body'])(`should NOT capture "click" events on <%s> elements`, (tagName) => {
218-
expect(shouldCaptureDomEvent(document!.createElement(tagName), makeMouseEvent({}))).toBe(false)
241+
expect(shouldAutocaptureEvent(document!.createElement(tagName), makeMouseEvent({}))).toBe(false)
219242
})
220243

221244
describe('css selector allowlist', () => {
@@ -317,7 +340,7 @@ describe(`Autocapture utility functions`, () => {
317340
],
318341
])('correctly respects the allow list: %s', (_, clickTarget, autoCaptureConfig, shouldCapture) => {
319342
expect(
320-
shouldCaptureDomEvent(clickTarget, makeMouseEvent({}), autoCaptureConfig as AutocaptureConfig)
343+
shouldAutocaptureEvent(clickTarget, makeMouseEvent({}), autoCaptureConfig as AutocaptureConfig)
321344
).toBe(shouldCapture)
322345
})
323346
})
@@ -348,110 +371,64 @@ describe(`Autocapture utility functions`, () => {
348371
})
349372
})
350373

351-
describe(`shouldCaptureElement`, () => {
374+
describe(`isExplicitCapture`, () => {
375+
it(`should return true for elements with class "ph-include"`, () => {
376+
const el = document!.createElement(`div`)
377+
el.className = `test1 ph-include test2`
378+
expect(isExplicitCapture(el)).toBe(true)
379+
})
380+
381+
it(`should return false for elements without class "ph-include"`, () => {
382+
const el = document!.createElement(`div`)
383+
el.className = `test1 test2`
384+
expect(isExplicitCapture(el)).toBe(false)
385+
})
386+
})
387+
388+
describe(`isExplicitNoCapture`, () => {
352389
let el: HTMLDivElement
353-
let input: HTMLInputElement
354390
let parent1: HTMLDivElement
355391
let parent2: HTMLDivElement
356392

357393
beforeEach(() => {
358394
el = document!.createElement(`div`)
359-
input = document!.createElement(`input`)
360395
parent1 = document!.createElement(`div`)
361396
parent2 = document!.createElement(`div`)
362397
parent1.appendChild(el)
363-
parent1.appendChild(input)
364398
parent2.appendChild(parent1)
365399
document!.body.appendChild(parent2)
366400
})
367401

368-
it(`should include sensitive elements with class "ph-include"`, () => {
369-
el.className = `test1 ph-include test2`
370-
expect(shouldCaptureElement(el)).toBe(true)
371-
})
372-
373-
it(`should never include inputs with class "ph-sensitive"`, () => {
402+
it(`should return true for elements with class "ph-sensitive"`, () => {
374403
el.className = `test1 ph-include ph-sensitive test2`
375-
expect(shouldCaptureElement(el)).toBe(false)
404+
expect(isExplicitNoCapture(el)).toBe(true)
376405
})
377406

378-
it(`should not include elements with class "ph-no-capture" as properties`, () => {
407+
it(`should return true for elements with class "ph-no-capture"`, () => {
379408
el.className = `test1 ph-no-capture test2`
380-
expect(shouldCaptureElement(el)).toBe(false)
409+
expect(isExplicitNoCapture(el)).toBe(true)
381410
})
382411

383-
it(`should not include elements with a parent that have class "ph-no-capture" as properties`, () => {
384-
expect(shouldCaptureElement(el)).toBe(true)
412+
it(`should return true for elements with a parent that has class "ph-no-capture"`, () => {
413+
expect(isExplicitNoCapture(el)).toBe(false)
385414

386415
parent2.className = `ph-no-capture`
387416

388-
expect(shouldCaptureElement(el)).toBe(false)
417+
expect(isExplicitNoCapture(el)).toBe(true)
389418
})
419+
})
390420

391-
it(`should not include hidden fields`, () => {
421+
describe(`isSensitiveElement - hidden and password inputs`, () => {
422+
it(`should return true for hidden fields`, () => {
423+
const input = document!.createElement(`input`)
392424
input.type = `hidden`
393-
expect(shouldCaptureElement(input)).toBe(false)
425+
expect(isSensitiveElement(input)).toBe(true)
394426
})
395427

396-
it(`should not include password fields`, () => {
428+
it(`should return true for password fields`, () => {
429+
const input = document!.createElement(`input`)
397430
input.type = `password`
398-
expect(shouldCaptureElement(input)).toBe(false)
399-
})
400-
401-
it(`should not include fields with sensitive names`, () => {
402-
const sensitiveNames = [
403-
`cc_name`,
404-
`card-num`,
405-
`ccnum`,
406-
`credit-card_number`,
407-
`credit_card[number]`,
408-
`csc num`,
409-
`CVC`,
410-
`Expiration`,
411-
`password`,
412-
`pwd`,
413-
`routing`,
414-
`routing-number`,
415-
`security code`,
416-
`seccode`,
417-
`security number`,
418-
`social sec`,
419-
`SsN`,
420-
]
421-
sensitiveNames.forEach((name) => {
422-
input.name = ''
423-
expect(shouldCaptureElement(input)).toBe(true)
424-
425-
input.name = name
426-
expect(shouldCaptureElement(input)).toBe(false)
427-
})
428-
})
429-
430-
// See https://github.com/posthog/posthog-js/issues/165
431-
// Under specific circumstances a bug caused .replace to be called on a DOM element
432-
// instead of a string, removing the element from the page. Ensure this issue is mitigated.
433-
it(`shouldn't inadvertently replace DOM nodes`, () => {
434-
// setup
435-
;(el as any).replace = sinon.spy()
436-
437-
// test
438-
input.name = el as any
439-
shouldCaptureElement(parent1) // previously this would cause el.replace to be called
440-
expect((el as any).replace.called).toBe(false)
441-
input.name = ''
442-
443-
parent1.id = el as any
444-
shouldCaptureElement(parent2) // previously this would cause el.replace to be called
445-
expect((el as any).replace.called).toBe(false)
446-
parent1.id = ''
447-
448-
input.type = el as any
449-
shouldCaptureElement(parent2) // previously this would cause el.replace to be called
450-
expect((el as any).replace.called).toBe(false)
451-
input.type = ''
452-
453-
// cleanup
454-
;(el as any).replace = undefined
431+
expect(isSensitiveElement(input)).toBe(true)
455432
})
456433
})
457434

0 commit comments

Comments
 (0)