Skip to content

Commit a75f935

Browse files
fix: Change obfuscator to read directly from obfuscation rules configuration (#1327)
1 parent ac9a02c commit a75f935

File tree

9 files changed

+80
-230
lines changed

9 files changed

+80
-230
lines changed

src/common/util/obfuscate.js

Lines changed: 27 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getConfigurationValue } from '../config/init'
21
import { isFileProtocol } from '../url/protocol'
32
import { warn } from './console'
43

@@ -21,18 +20,15 @@ import { warn } from './console'
2120
*/
2221

2322
export class Obfuscator {
24-
/**
25-
* @type {ObfuscationRuleValidation[]}
26-
*/
27-
#ruleValidationCache
28-
29-
constructor (agentIdentifier) {
30-
this.#ruleValidationCache = Obfuscator.getRuleValidationCache(agentIdentifier)
31-
Obfuscator.logObfuscationRuleErrors(this.#ruleValidationCache)
23+
constructor (agentRef) {
24+
this.agentRef = agentRef
25+
this.warnedRegexMissing = false
26+
this.warnedInvalidRegex = false
27+
this.warnedInvalidReplacement = false
3228
}
3329

34-
get ruleValidationCache () {
35-
return this.#ruleValidationCache
30+
get obfuscateConfigRules () {
31+
return this.agentRef.init.obfuscate || []
3632
}
3733

3834
/**
@@ -44,44 +40,44 @@ export class Obfuscator {
4440
// if input is not of type string or is an empty string, short-circuit
4541
if (typeof input !== 'string' || input.trim().length === 0) return input
4642

47-
return this.#ruleValidationCache
48-
.filter(ruleValidation => ruleValidation.isValid)
49-
.reduce((input, ruleValidation) => {
50-
const { rule } = ruleValidation
51-
return input.replace(rule.regex, rule.replacement || '*')
52-
}, input)
53-
}
54-
55-
/**
56-
* Returns an array of obfuscation rules to be applied to harvested payloads
57-
* @param {string} agentIdentifier The agent identifier to get rules for
58-
* @returns {ObfuscationRuleValidation[]} The array of rules or validation states
59-
*/
60-
static getRuleValidationCache (agentIdentifier) {
61-
/**
62-
* @type {ObfuscationRule[]}
63-
*/
64-
let rules = getConfigurationValue(agentIdentifier, 'obfuscate') || []
43+
const rules = (this.obfuscateConfigRules).map(rule => this.validateObfuscationRule(rule))
6544
if (isFileProtocol()) {
6645
rules.push({
6746
regex: /^file:\/\/(.*)/,
6847
replacement: atob('ZmlsZTovL09CRlVTQ0FURUQ=')
6948
})
7049
}
7150

72-
return rules.map(rule => Obfuscator.validateObfuscationRule(rule))
51+
return rules
52+
.filter(ruleValidation => ruleValidation.isValid)
53+
.reduce((input, ruleValidation) => {
54+
const { rule } = ruleValidation
55+
return input.replace(rule.regex, rule.replacement || '*')
56+
}, input)
7357
}
7458

7559
/**
7660
* Validates an obfuscation rule and provides errors if any are found.
7761
* @param {ObfuscationRule} rule The rule to validate
7862
* @returns {ObfuscationRuleValidation} The validation state of the rule
7963
*/
80-
static validateObfuscationRule (rule) {
64+
validateObfuscationRule (rule) {
8165
const regexMissingDetected = Boolean(rule.regex === undefined)
8266
const invalidRegexDetected = Boolean(rule.regex !== undefined && typeof rule.regex !== 'string' && !(rule.regex instanceof RegExp))
8367
const invalidReplacementDetected = Boolean(rule.replacement && typeof rule.replacement !== 'string')
8468

69+
if (regexMissingDetected && !this.warnedRegexMissing) {
70+
warn(12, rule)
71+
this.warnedRegexMissing = true
72+
} else if (invalidRegexDetected && !this.warnedInvalidRegex) {
73+
warn(13, rule)
74+
this.warnedInvalidRegex = true
75+
}
76+
if (invalidReplacementDetected && !this.warnedInvalidReplacement) {
77+
warn(14, rule)
78+
this.warnedInvalidReplacement = true
79+
}
80+
8581
return {
8682
rule,
8783
isValid: !regexMissingDetected && !invalidRegexDetected && !invalidReplacementDetected,
@@ -92,20 +88,4 @@ export class Obfuscator {
9288
}
9389
}
9490
}
95-
96-
/**
97-
* Logs any obfuscation rule errors to the console. This is called when an obfuscator
98-
* instance is created.
99-
* @param {ObfuscationRuleValidation[]} ruleValidationCache The cache of rule validation states
100-
*/
101-
static logObfuscationRuleErrors (ruleValidationCache) {
102-
for (const ruleValidation of ruleValidationCache) {
103-
const { rule, isValid, errors } = ruleValidation
104-
if (isValid) continue
105-
106-
if (errors.regexMissingDetected) warn(12, rule)
107-
else if (errors.invalidRegexDetected) warn(13, rule)
108-
if (errors.invalidReplacementDetected) warn(14, rule)
109-
}
110-
}
11191
}

src/features/metrics/aggregate/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,8 @@ export class Aggregate extends AggregateBase {
8787
}
8888

8989
// Capture SMs to assess customer engagement with the obfuscation config
90-
const ruleValidations = this.obfuscator.ruleValidationCache
91-
if (ruleValidations.length > 0) {
90+
if (this.obfuscator.obfuscateConfigRules.length > 0) {
9291
this.storeSupportabilityMetrics('Generic/Obfuscate/Detected')
93-
if (ruleValidations.filter(ruleValidation => !ruleValidation.isValid).length > 0) this.storeSupportabilityMetrics('Generic/Obfuscate/Invalid')
9492
}
9593

9694
// Check if proxy for either chunks or beacon is being used

src/features/utils/aggregate-base.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export class AggregateBase extends FeatureBase {
142142
* This method should run after checkConfiguration, which may reset the agent's info/runtime object that is used here.
143143
*/
144144
doOnceForAllAggregate (agentRef) {
145-
if (!agentRef.runtime.obfuscator) agentRef.runtime.obfuscator = new Obfuscator(this.agentIdentifier)
145+
if (!agentRef.runtime.obfuscator) agentRef.runtime.obfuscator = new Obfuscator(agentRef)
146146
this.obfuscator = agentRef.runtime.obfuscator
147147

148148
if (!agentRef.mainAppKey) agentRef.mainAppKey = { licenseKey: agentRef.info.licenseKey, appId: agentRef.info.applicationID }

tests/components/spa/serializer.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const fieldPropMap = {
4343
beforeEach(() => {
4444
jest.mocked(runtimeModule.getRuntime).mockReturnValue({
4545
origin: 'localhost',
46-
obfuscator: new Obfuscator(agentIdentifier)
46+
obfuscator: new Obfuscator({ init: { obfuscate: [] } })
4747
})
4848
})
4949

tests/specs/metrics.e2e.js

Lines changed: 1 addition & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe('metrics', () => {
9595
}]))
9696
})
9797

98-
it('should create SMs for valid obfuscation rules', async () => {
98+
it('should create SMs when obfuscation rules are detected', async () => {
9999
await browser.url(await browser.testHandle.assetURL('obfuscate-pii-valid.html'))
100100
.then(() => browser.waitForAgentLoad())
101101

@@ -109,69 +109,5 @@ describe('metrics', () => {
109109
params: { name: 'Generic/Obfuscate/Detected' },
110110
stats: { c: 1 }
111111
}]))
112-
expect(supportabilityMetrics).not.toEqual(expect.arrayContaining([{
113-
params: { name: 'Generic/Obfuscate/Invalid' },
114-
stats: { c: 1 }
115-
}]))
116-
})
117-
118-
it('should create SMs for obfuscation rule containing invalid regex type', async () => {
119-
await browser.url(await browser.testHandle.assetURL('obfuscate-pii-invalid-regex-type.html'))
120-
.then(() => browser.waitForAgentLoad())
121-
122-
const [supportabilityMetricsHarvests] = await Promise.all([
123-
supportabilityMetricsCapture.waitForResult({ totalCount: 1 }),
124-
await browser.url(await browser.testHandle.assetURL('/')) // Setup expects before navigating
125-
])
126-
127-
const supportabilityMetrics = supportabilityMetricsHarvests[0].request.body.sm
128-
expect(supportabilityMetrics).toEqual(expect.arrayContaining([{
129-
params: { name: 'Generic/Obfuscate/Detected' },
130-
stats: { c: 1 }
131-
}]))
132-
expect(supportabilityMetrics).toEqual(expect.arrayContaining([{
133-
params: { name: 'Generic/Obfuscate/Invalid' },
134-
stats: { c: 1 }
135-
}]))
136-
})
137-
138-
it('should create SMs for obfuscation rule containing undefined regex type', async () => {
139-
await browser.url(await browser.testHandle.assetURL('obfuscate-pii-invalid-regex-undefined.html'))
140-
.then(() => browser.waitForAgentLoad())
141-
142-
const [supportabilityMetricsHarvests] = await Promise.all([
143-
supportabilityMetricsCapture.waitForResult({ totalCount: 1 }),
144-
await browser.url(await browser.testHandle.assetURL('/')) // Setup expects before navigating
145-
])
146-
147-
const supportabilityMetrics = supportabilityMetricsHarvests[0].request.body.sm
148-
expect(supportabilityMetrics).toEqual(expect.arrayContaining([{
149-
params: { name: 'Generic/Obfuscate/Detected' },
150-
stats: { c: 1 }
151-
}]))
152-
expect(supportabilityMetrics).toEqual(expect.arrayContaining([{
153-
params: { name: 'Generic/Obfuscate/Invalid' },
154-
stats: { c: 1 }
155-
}]))
156-
})
157-
158-
it('should create SMs for obfuscation rule containing invalid replacement type', async () => {
159-
await browser.url(await browser.testHandle.assetURL('obfuscate-pii-invalid-replacement-type.html'))
160-
.then(() => browser.waitForAgentLoad())
161-
162-
const [supportabilityMetricsHarvests] = await Promise.all([
163-
supportabilityMetricsCapture.waitForResult({ totalCount: 1 }),
164-
await browser.url(await browser.testHandle.assetURL('/')) // Setup expects before navigating
165-
])
166-
167-
const supportabilityMetrics = supportabilityMetricsHarvests[0].request.body.sm
168-
expect(supportabilityMetrics).toEqual(expect.arrayContaining([{
169-
params: { name: 'Generic/Obfuscate/Detected' },
170-
stats: { c: 1 }
171-
}]))
172-
expect(supportabilityMetrics).toEqual(expect.arrayContaining([{
173-
params: { name: 'Generic/Obfuscate/Invalid' },
174-
stats: { c: 1 }
175-
}]))
176112
})
177113
})

tests/specs/obfuscate.e2e.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,46 @@ describe('obfuscate rules', () => {
9595
checkPayload(harvest.request.query)
9696
})
9797
})
98+
99+
it('should apply to obfuscation rules added after init', async () => {
100+
const [insightsHarvests] = await Promise.all([
101+
insightsCapture.waitForResult({ totalCount: 1 }),
102+
browser.url(await browser.testHandle.assetURL('obfuscate-pii.html', config))
103+
.then(() => browser.waitForAgentLoad())
104+
])
105+
106+
expect(insightsHarvests.length).toBeGreaterThan(0)
107+
insightsHarvests.forEach(harvest => {
108+
checkPayload(harvest.request.body)
109+
checkPayload(harvest.request.query)
110+
})
111+
112+
const [insightsHarvests2] = await Promise.all([
113+
insightsCapture.waitForResult({ totalCount: 2 }),
114+
browser.execute(function () {
115+
Object.values(newrelic.initializedAgents)[0].init.obfuscate.push({
116+
regex: 'newrule',
117+
replacement: 'OBFUSCATED'
118+
})
119+
window.newrelic.addPageAction('my newrule pageaction', { foo: 'bar' })
120+
})
121+
])
122+
123+
expect(insightsHarvests2.length).toBeGreaterThan(0)
124+
insightsHarvests2.forEach(harvest => {
125+
checkPayload(harvest.request.body, 'newrule')
126+
checkPayload(harvest.request.query, 'newrule')
127+
})
128+
})
98129
})
99130

100-
function checkPayload (payload) {
131+
function checkPayload (payload, customStringCheck) {
101132
expect(payload).toBeDefined() // payload exists
102133

103134
var strPayload = JSON.stringify(payload)
104135

105136
expect(strPayload.includes('pii')).toBeFalsy() // pii was obfuscated
106137
expect(strPayload.includes('bam-test')).toBeFalsy() // bam-test was obfuscated
107138
expect(strPayload.includes('fakeid')).toBeFalsy() // fakeid was obfuscated
139+
if (customStringCheck) expect(strPayload.includes(customStringCheck)).toBeFalsy()
108140
}

0 commit comments

Comments
 (0)