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
|
## 16.0.3
|
||||||
|
|
||||||
- better handling for local svg spritemaps
|
- better handling for local svg spritemaps
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@openreplay/tracker",
|
"name": "@openreplay/tracker",
|
||||||
"description": "The OpenReplay tracker main package",
|
"description": "The OpenReplay tracker main package",
|
||||||
"version": "16.0.3",
|
"version": "16.1.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"logging",
|
"logging",
|
||||||
"replay"
|
"replay"
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,9 @@ export default abstract class Observer {
|
||||||
if (name === 'href' || value.length > 1e5) {
|
if (name === 'href' || value.length > 1e5) {
|
||||||
value = ''
|
value = ''
|
||||||
}
|
}
|
||||||
|
if (['alt', 'placeholder'].includes(name) && this.app.sanitizer.privateMode) {
|
||||||
|
value = value.replaceAll(/./g, '*')
|
||||||
|
}
|
||||||
this.app.attributeSender.sendSetAttribute(id, name, value)
|
this.app.attributeSender.sendSetAttribute(id, name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,13 @@ export interface Options {
|
||||||
export const stringWiper = (input: string) =>
|
export const stringWiper = (input: string) =>
|
||||||
input
|
input
|
||||||
.trim()
|
.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 {
|
export default class Sanitizer {
|
||||||
private readonly obscured: Set<number> = new Set()
|
private readonly obscured: Set<number> = new Set()
|
||||||
private readonly hidden: Set<number> = new Set()
|
private readonly hidden: Set<number> = new Set()
|
||||||
private readonly options: Options
|
private readonly options: Options
|
||||||
|
public readonly privateMode: boolean
|
||||||
private readonly app: App
|
private readonly app: App
|
||||||
|
|
||||||
constructor(params: { app: App; options?: Partial<Options> }) {
|
constructor(params: { app: App; options?: Partial<Options> }) {
|
||||||
|
|
@ -57,16 +58,17 @@ export default class Sanitizer {
|
||||||
privateMode: false,
|
privateMode: false,
|
||||||
domSanitizer: undefined,
|
domSanitizer: undefined,
|
||||||
}
|
}
|
||||||
|
this.privateMode = params.options?.privateMode ?? false
|
||||||
this.options = Object.assign(defaultOptions, params.options)
|
this.options = Object.assign(defaultOptions, params.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNode(id: number, parentID: number, node: Node) {
|
handleNode(id: number, parentID: number, node: Node) {
|
||||||
if (this.options.privateMode) {
|
if (this.options.privateMode) {
|
||||||
if (isElementNode(node) && !hasOpenreplayAttribute(node, 'unmask')) {
|
if (isElementNode(node) && !hasOpenreplayAttribute(node, 'unmask')) {
|
||||||
this.obscured.add(id)
|
return this.obscured.add(id)
|
||||||
}
|
}
|
||||||
if (isTextNode(node) && !hasOpenreplayAttribute(node.parentNode as Element, 'unmask')) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendConsoleLog = app.safe((level: string, args: unknown[]): void =>
|
const sendConsoleLog = app.safe((level: string, args: unknown[]): void => {
|
||||||
app.send(ConsoleLog(level, printf(args))),
|
let logMsg = printf(args)
|
||||||
)
|
if (app.sanitizer.privateMode) {
|
||||||
|
logMsg = logMsg.replaceAll(/./g, '*')
|
||||||
|
}
|
||||||
|
app.send(ConsoleLog(level, logMsg))
|
||||||
|
})
|
||||||
|
|
||||||
let n = 0
|
let n = 0
|
||||||
const reset = (): void => {
|
const reset = (): void => {
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,10 @@ export default function (app: App, opts: Partial<Options>): void {
|
||||||
inputTime: number,
|
inputTime: number,
|
||||||
) {
|
) {
|
||||||
const { value, mask } = getInputValue(id, node)
|
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))
|
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)
|
const normalizedY = roundNumber(clickY / contentHeight)
|
||||||
|
|
||||||
sendMouseMove()
|
sendMouseMove()
|
||||||
|
const label = getTargetLabel(target)
|
||||||
app.send(
|
app.send(
|
||||||
MouseClick(
|
MouseClick(
|
||||||
id,
|
id,
|
||||||
mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0,
|
mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0,
|
||||||
getTargetLabel(target),
|
app.sanitizer.privateMode ? label.replaceAll(/./g, '*') : label,
|
||||||
isClickable(target) && !disableClickmaps ? getSelector(id, target, options) : '',
|
isClickable(target) && !disableClickmaps ? getSelector(id, target, options) : '',
|
||||||
normalizedX,
|
normalizedX,
|
||||||
normalizedY,
|
normalizedY,
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ export default function (app: App, opts: Partial<Options> = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitize(reqResInfo: RequestResponseData) {
|
function sanitize(reqResInfo: RequestResponseData) {
|
||||||
if (!options.capturePayload) {
|
if (!options.capturePayload || app.sanitizer.privateMode) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
delete reqResInfo.request.body
|
delete reqResInfo.request.body
|
||||||
delete reqResInfo.response.body
|
delete reqResInfo.response.body
|
||||||
|
|
@ -136,18 +136,19 @@ export default function (app: App, opts: Partial<Options> = {}) {
|
||||||
if (options.useProxy) {
|
if (options.useProxy) {
|
||||||
return createNetworkProxy(
|
return createNetworkProxy(
|
||||||
context,
|
context,
|
||||||
options.ignoreHeaders,
|
app.sanitizer.privateMode ? true : options.ignoreHeaders,
|
||||||
setSessionTokenHeader,
|
setSessionTokenHeader,
|
||||||
sanitize,
|
sanitize,
|
||||||
(message) => {
|
(message) => {
|
||||||
if (options.failuresOnly && message.status < 400) {
|
if (options.failuresOnly && message.status < 400) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const url = app.sanitizer.privateMode ? '************' : message.url
|
||||||
app.send(
|
app.send(
|
||||||
NetworkRequest(
|
NetworkRequest(
|
||||||
message.requestType,
|
message.requestType,
|
||||||
message.method,
|
message.method,
|
||||||
message.url,
|
url,
|
||||||
message.request,
|
message.request,
|
||||||
message.response,
|
message.response,
|
||||||
message.status,
|
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.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0,
|
||||||
entry.encodedBodySize || 0,
|
entry.encodedBodySize || 0,
|
||||||
entry.decodedBodySize || 0,
|
entry.decodedBodySize || 0,
|
||||||
entry.name,
|
app.sanitizer.privateMode ? entry.name.replaceAll(/./g, '*') : entry.name,
|
||||||
entry.initiatorType,
|
entry.initiatorType,
|
||||||
entry.transferSize,
|
entry.transferSize,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue