tracker: "secure by default" mode; 16.1.0
This commit is contained in:
parent
4bac12308a
commit
e7d309dadf
9 changed files with 31 additions and 13 deletions
|
|
@ -1,3 +1,7 @@
|
|||
## 16.1.0
|
||||
|
||||
- new `privateMode` option to hide all possible data from tracking
|
||||
|
||||
## 16.0.3
|
||||
|
||||
- better handling for local svg spritemaps
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "16.0.3",
|
||||
"version": "16.1.0",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
|
|||
|
|
@ -357,6 +357,9 @@ export default abstract class Observer {
|
|||
if (name === 'href' || value.length > 1e5) {
|
||||
value = ''
|
||||
}
|
||||
if (['alt', 'placeholder'].includes(name) && this.app.sanitizer.privateMode) {
|
||||
value = value.replaceAll(/./g, '*')
|
||||
}
|
||||
this.app.attributeSender.sendSetAttribute(id, name, value)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,12 +41,13 @@ export interface Options {
|
|||
export const stringWiper = (input: string) =>
|
||||
input
|
||||
.trim()
|
||||
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█')
|
||||
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '*')
|
||||
|
||||
export default class Sanitizer {
|
||||
private readonly obscured: Set<number> = new Set()
|
||||
private readonly hidden: Set<number> = new Set()
|
||||
private readonly options: Options
|
||||
public readonly privateMode: boolean
|
||||
private readonly app: App
|
||||
|
||||
constructor(params: { app: App; options?: Partial<Options> }) {
|
||||
|
|
@ -57,16 +58,17 @@ export default class Sanitizer {
|
|||
privateMode: false,
|
||||
domSanitizer: undefined,
|
||||
}
|
||||
this.privateMode = params.options?.privateMode ?? false
|
||||
this.options = Object.assign(defaultOptions, params.options)
|
||||
}
|
||||
|
||||
handleNode(id: number, parentID: number, node: Node) {
|
||||
if (this.options.privateMode) {
|
||||
if (isElementNode(node) && !hasOpenreplayAttribute(node, 'unmask')) {
|
||||
this.obscured.add(id)
|
||||
return this.obscured.add(id)
|
||||
}
|
||||
if (isTextNode(node) && !hasOpenreplayAttribute(node.parentNode as Element, 'unmask')) {
|
||||
this.obscured.add(id)
|
||||
return this.obscured.add(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -108,9 +108,13 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
return
|
||||
}
|
||||
|
||||
const sendConsoleLog = app.safe((level: string, args: unknown[]): void =>
|
||||
app.send(ConsoleLog(level, printf(args))),
|
||||
)
|
||||
const sendConsoleLog = app.safe((level: string, args: unknown[]): void => {
|
||||
let logMsg = printf(args)
|
||||
if (app.sanitizer.privateMode) {
|
||||
logMsg = logMsg.replaceAll(/./g, '*')
|
||||
}
|
||||
app.send(ConsoleLog(level, logMsg))
|
||||
})
|
||||
|
||||
let n = 0
|
||||
const reset = (): void => {
|
||||
|
|
|
|||
|
|
@ -205,7 +205,10 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
inputTime: number,
|
||||
) {
|
||||
const { value, mask } = getInputValue(id, node)
|
||||
const label = getInputLabel(node)
|
||||
let label = getInputLabel(node)
|
||||
if (app.sanitizer.privateMode) {
|
||||
label = label.replaceAll(/./g, '*')
|
||||
}
|
||||
|
||||
app.send(InputChange(id, value, mask !== 0, label, hesitationTime, inputTime))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,11 +230,12 @@ export default function (app: App, options?: MouseHandlerOptions): void {
|
|||
const normalizedY = roundNumber(clickY / contentHeight)
|
||||
|
||||
sendMouseMove()
|
||||
const label = getTargetLabel(target)
|
||||
app.send(
|
||||
MouseClick(
|
||||
id,
|
||||
mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0,
|
||||
getTargetLabel(target),
|
||||
app.sanitizer.privateMode ? label.replaceAll(/./g, '*') : label,
|
||||
isClickable(target) && !disableClickmaps ? getSelector(id, target, options) : '',
|
||||
normalizedX,
|
||||
normalizedY,
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export default function (app: App, opts: Partial<Options> = {}) {
|
|||
}
|
||||
|
||||
function sanitize(reqResInfo: RequestResponseData) {
|
||||
if (!options.capturePayload) {
|
||||
if (!options.capturePayload || app.sanitizer.privateMode) {
|
||||
// @ts-ignore
|
||||
delete reqResInfo.request.body
|
||||
delete reqResInfo.response.body
|
||||
|
|
@ -136,18 +136,19 @@ export default function (app: App, opts: Partial<Options> = {}) {
|
|||
if (options.useProxy) {
|
||||
return createNetworkProxy(
|
||||
context,
|
||||
options.ignoreHeaders,
|
||||
app.sanitizer.privateMode ? true : options.ignoreHeaders,
|
||||
setSessionTokenHeader,
|
||||
sanitize,
|
||||
(message) => {
|
||||
if (options.failuresOnly && message.status < 400) {
|
||||
return
|
||||
}
|
||||
const url = app.sanitizer.privateMode ? '************' : message.url
|
||||
app.send(
|
||||
NetworkRequest(
|
||||
message.requestType,
|
||||
message.method,
|
||||
message.url,
|
||||
url,
|
||||
message.request,
|
||||
message.response,
|
||||
message.status,
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0,
|
||||
entry.encodedBodySize || 0,
|
||||
entry.decodedBodySize || 0,
|
||||
entry.name,
|
||||
app.sanitizer.privateMode ? entry.name.replaceAll(/./g, '*') : entry.name,
|
||||
entry.initiatorType,
|
||||
entry.transferSize,
|
||||
// @ts-ignore
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue