dd-trace 4.11.1 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -9
- package/index.d.ts +43 -0
- package/package.json +3 -2
- package/packages/datadog-esbuild/index.js +29 -9
- package/packages/datadog-instrumentations/src/cucumber.js +30 -11
- package/packages/datadog-instrumentations/src/jest.js +22 -11
- package/packages/datadog-instrumentations/src/mocha.js +30 -8
- package/packages/datadog-instrumentations/src/next.js +102 -16
- package/packages/datadog-instrumentations/src/openai.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +46 -0
- package/packages/datadog-plugin-cucumber/src/index.js +14 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +17 -8
- package/packages/datadog-plugin-graphql/src/index.js +3 -3
- package/packages/datadog-plugin-jest/src/index.js +10 -2
- package/packages/datadog-plugin-jest/src/util.js +10 -4
- package/packages/datadog-plugin-mocha/src/index.js +14 -2
- package/packages/datadog-plugin-next/src/index.js +11 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +19 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +120 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
- package/packages/dd-trace/src/dogstatsd.js +65 -5
- package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
- package/packages/dd-trace/src/exporters/common/request.js +13 -4
- package/packages/dd-trace/src/opentracing/span.js +13 -13
- package/packages/dd-trace/src/opentracing/tracer.js +3 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
- package/packages/dd-trace/src/plugins/util/test.js +18 -1
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +23 -7
- package/packages/dd-trace/src/proxy.js +23 -2
- package/packages/dd-trace/src/ritm.js +10 -2
- /package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +0 -0
|
@@ -11,8 +11,12 @@ const startCh = channel('apm:pg:query:start')
|
|
|
11
11
|
const finishCh = channel('apm:pg:query:finish')
|
|
12
12
|
const errorCh = channel('apm:pg:query:error')
|
|
13
13
|
|
|
14
|
+
const startPoolQueryCh = channel('datadog:pg:pool:query:start')
|
|
15
|
+
const finishPoolQueryCh = channel('datadog:pg:pool:query:finish')
|
|
16
|
+
|
|
14
17
|
addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
|
|
15
18
|
shimmer.wrap(pg.Client.prototype, 'query', query => wrapQuery(query))
|
|
19
|
+
shimmer.wrap(pg.Pool.prototype, 'query', query => wrapPoolQuery(query))
|
|
16
20
|
return pg
|
|
17
21
|
})
|
|
18
22
|
|
|
@@ -97,3 +101,45 @@ function wrapQuery (query) {
|
|
|
97
101
|
})
|
|
98
102
|
}
|
|
99
103
|
}
|
|
104
|
+
|
|
105
|
+
function wrapPoolQuery (query) {
|
|
106
|
+
return function () {
|
|
107
|
+
if (!startPoolQueryCh.hasSubscribers) {
|
|
108
|
+
return query.apply(this, arguments)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
112
|
+
|
|
113
|
+
const pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
|
|
114
|
+
|
|
115
|
+
return asyncResource.runInAsyncScope(() => {
|
|
116
|
+
startPoolQueryCh.publish({
|
|
117
|
+
query: pgQuery
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const finish = asyncResource.bind(function () {
|
|
121
|
+
finishPoolQueryCh.publish()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const cb = arguments[arguments.length - 1]
|
|
125
|
+
if (typeof cb === 'function') {
|
|
126
|
+
arguments[arguments.length - 1] = shimmer.wrap(cb, function () {
|
|
127
|
+
finish()
|
|
128
|
+
return cb.apply(this, arguments)
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const retval = query.apply(this, arguments)
|
|
133
|
+
|
|
134
|
+
if (retval && retval.then) {
|
|
135
|
+
retval.then(() => {
|
|
136
|
+
finish()
|
|
137
|
+
}).catch(() => {
|
|
138
|
+
finish()
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return retval
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -25,12 +25,24 @@ class CucumberPlugin extends CiPlugin {
|
|
|
25
25
|
|
|
26
26
|
this.sourceRoot = process.cwd()
|
|
27
27
|
|
|
28
|
-
this.addSub('ci:cucumber:session:finish', ({
|
|
28
|
+
this.addSub('ci:cucumber:session:finish', ({
|
|
29
|
+
status,
|
|
30
|
+
isSuitesSkipped,
|
|
31
|
+
numSkippedSuites,
|
|
32
|
+
testCodeCoverageLinesTotal
|
|
33
|
+
}) => {
|
|
29
34
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
|
|
30
35
|
addIntelligentTestRunnerSpanTags(
|
|
31
36
|
this.testSessionSpan,
|
|
32
37
|
this.testModuleSpan,
|
|
33
|
-
{
|
|
38
|
+
{
|
|
39
|
+
isSuitesSkipped,
|
|
40
|
+
isSuitesSkippingEnabled,
|
|
41
|
+
isCodeCoverageEnabled,
|
|
42
|
+
testCodeCoverageLinesTotal,
|
|
43
|
+
skippingCount: numSkippedSuites,
|
|
44
|
+
skippingType: 'suite'
|
|
45
|
+
}
|
|
34
46
|
)
|
|
35
47
|
|
|
36
48
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -20,7 +20,8 @@ const {
|
|
|
20
20
|
finishAllTraceSpans,
|
|
21
21
|
getCoveredFilenamesFromCoverage,
|
|
22
22
|
getTestSuitePath,
|
|
23
|
-
addIntelligentTestRunnerSpanTags
|
|
23
|
+
addIntelligentTestRunnerSpanTags,
|
|
24
|
+
TEST_SKIPPED_BY_ITR
|
|
24
25
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
25
26
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
26
27
|
const log = require('../../dd-trace/src/log')
|
|
@@ -120,6 +121,7 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
|
|
|
120
121
|
|
|
121
122
|
module.exports = (on, config) => {
|
|
122
123
|
let isTestsSkipped = false
|
|
124
|
+
const skippedTests = []
|
|
123
125
|
const tracer = require('../../dd-trace')
|
|
124
126
|
const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
|
|
125
127
|
|
|
@@ -248,19 +250,23 @@ module.exports = (on, config) => {
|
|
|
248
250
|
const cypressTests = tests || []
|
|
249
251
|
const finishedTests = finishedTestsByFile[spec.relative] || []
|
|
250
252
|
|
|
251
|
-
// Get tests that didn't go through `dd:afterEach`
|
|
253
|
+
// Get tests that didn't go through `dd:afterEach`
|
|
252
254
|
// and create a skipped test span for each of them
|
|
253
255
|
cypressTests.filter(({ title }) => {
|
|
254
256
|
const cypressTestName = title.join(' ')
|
|
255
|
-
const isSkippedByItr = testsToSkip.find(test =>
|
|
256
|
-
cypressTestName === test.name && spec.relative === test.suite
|
|
257
|
-
)
|
|
258
257
|
const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
|
|
259
258
|
|
|
260
|
-
return !
|
|
259
|
+
return !isTestFinished
|
|
261
260
|
}).forEach(({ title }) => {
|
|
262
|
-
const
|
|
261
|
+
const cypressTestName = title.join(' ')
|
|
262
|
+
const isSkippedByItr = testsToSkip.find(test =>
|
|
263
|
+
cypressTestName === test.name && spec.relative === test.suite
|
|
264
|
+
)
|
|
265
|
+
const skippedTestSpan = getTestSpan(cypressTestName, spec.relative)
|
|
263
266
|
skippedTestSpan.setTag(TEST_STATUS, 'skip')
|
|
267
|
+
if (isSkippedByItr) {
|
|
268
|
+
skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
|
|
269
|
+
}
|
|
264
270
|
skippedTestSpan.finish()
|
|
265
271
|
})
|
|
266
272
|
|
|
@@ -309,7 +315,9 @@ module.exports = (on, config) => {
|
|
|
309
315
|
{
|
|
310
316
|
isSuitesSkipped: isTestsSkipped,
|
|
311
317
|
isSuitesSkippingEnabled,
|
|
312
|
-
isCodeCoverageEnabled
|
|
318
|
+
isCodeCoverageEnabled,
|
|
319
|
+
skippingType: 'test',
|
|
320
|
+
skippingCount: skippedTests.length
|
|
313
321
|
}
|
|
314
322
|
)
|
|
315
323
|
|
|
@@ -353,6 +361,7 @@ module.exports = (on, config) => {
|
|
|
353
361
|
if (testsToSkip.find(test => {
|
|
354
362
|
return testName === test.name && testSuite === test.suite
|
|
355
363
|
})) {
|
|
364
|
+
skippedTests.push(test)
|
|
356
365
|
isTestsSkipped = true
|
|
357
366
|
return { shouldSkip: true }
|
|
358
367
|
}
|
|
@@ -56,9 +56,9 @@ function getVariablesFilter (config) {
|
|
|
56
56
|
|
|
57
57
|
function getHooks (config) {
|
|
58
58
|
const noop = () => { }
|
|
59
|
-
const execute =
|
|
60
|
-
const parse =
|
|
61
|
-
const validate =
|
|
59
|
+
const execute = config.hooks?.execute || noop
|
|
60
|
+
const parse = config.hooks?.parse || noop
|
|
61
|
+
const validate = config.hooks?.validate || noop
|
|
62
62
|
|
|
63
63
|
return { execute, parse, validate }
|
|
64
64
|
}
|
|
@@ -49,7 +49,8 @@ class JestPlugin extends CiPlugin {
|
|
|
49
49
|
isSuitesSkipped,
|
|
50
50
|
isSuitesSkippingEnabled,
|
|
51
51
|
isCodeCoverageEnabled,
|
|
52
|
-
testCodeCoverageLinesTotal
|
|
52
|
+
testCodeCoverageLinesTotal,
|
|
53
|
+
numSkippedSuites
|
|
53
54
|
}) => {
|
|
54
55
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
55
56
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
@@ -57,7 +58,14 @@ class JestPlugin extends CiPlugin {
|
|
|
57
58
|
addIntelligentTestRunnerSpanTags(
|
|
58
59
|
this.testSessionSpan,
|
|
59
60
|
this.testModuleSpan,
|
|
60
|
-
{
|
|
61
|
+
{
|
|
62
|
+
isSuitesSkipped,
|
|
63
|
+
isSuitesSkippingEnabled,
|
|
64
|
+
isCodeCoverageEnabled,
|
|
65
|
+
testCodeCoverageLinesTotal,
|
|
66
|
+
skippingType: 'suite',
|
|
67
|
+
skippingCount: numSkippedSuites
|
|
68
|
+
}
|
|
61
69
|
)
|
|
62
70
|
|
|
63
71
|
this.testModuleSpan.finish()
|
|
@@ -48,10 +48,16 @@ function getJestTestName (test) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function getJestSuitesToRun (skippableSuites, originalTests, rootDir) {
|
|
51
|
-
return originalTests.
|
|
52
|
-
const relativePath = getTestSuitePath(
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
return originalTests.reduce((acc, test) => {
|
|
52
|
+
const relativePath = getTestSuitePath(test.path, rootDir)
|
|
53
|
+
const shouldBeSkipped = skippableSuites.includes(relativePath)
|
|
54
|
+
if (shouldBeSkipped) {
|
|
55
|
+
acc.skippedSuites.push(relativePath)
|
|
56
|
+
} else {
|
|
57
|
+
acc.suitesToRun.push(test)
|
|
58
|
+
}
|
|
59
|
+
return acc
|
|
60
|
+
}, { skippedSuites: [], suitesToRun: [] })
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
module.exports = { getFormattedJestTestParameters, getJestTestName, getJestSuitesToRun }
|
|
@@ -135,7 +135,12 @@ class MochaPlugin extends CiPlugin {
|
|
|
135
135
|
this._testNameToParams[name] = params
|
|
136
136
|
})
|
|
137
137
|
|
|
138
|
-
this.addSub('ci:mocha:session:finish', ({
|
|
138
|
+
this.addSub('ci:mocha:session:finish', ({
|
|
139
|
+
status,
|
|
140
|
+
isSuitesSkipped,
|
|
141
|
+
testCodeCoverageLinesTotal,
|
|
142
|
+
numSkippedSuites
|
|
143
|
+
}) => {
|
|
139
144
|
if (this.testSessionSpan) {
|
|
140
145
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
|
|
141
146
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -144,7 +149,14 @@ class MochaPlugin extends CiPlugin {
|
|
|
144
149
|
addIntelligentTestRunnerSpanTags(
|
|
145
150
|
this.testSessionSpan,
|
|
146
151
|
this.testModuleSpan,
|
|
147
|
-
{
|
|
152
|
+
{
|
|
153
|
+
isSuitesSkipped,
|
|
154
|
+
isSuitesSkippingEnabled,
|
|
155
|
+
isCodeCoverageEnabled,
|
|
156
|
+
testCodeCoverageLinesTotal,
|
|
157
|
+
skippingCount: numSkippedSuites,
|
|
158
|
+
skippingType: 'suite'
|
|
159
|
+
}
|
|
148
160
|
)
|
|
149
161
|
|
|
150
162
|
this.testModuleSpan.finish()
|
|
@@ -4,6 +4,7 @@ const ServerPlugin = require('../../dd-trace/src/plugins/server')
|
|
|
4
4
|
const { storage } = require('../../datadog-core')
|
|
5
5
|
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
6
6
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
7
|
+
const web = require('../../dd-trace/src/plugins/util/web')
|
|
7
8
|
|
|
8
9
|
class NextPlugin extends ServerPlugin {
|
|
9
10
|
static get id () {
|
|
@@ -16,7 +17,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
16
17
|
this.addSub('apm:next:page:load', message => this.pageLoad(message))
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
bindStart ({ req, res }) {
|
|
20
21
|
const store = storage.getStore()
|
|
21
22
|
const childOf = store ? store.span : store
|
|
22
23
|
const span = this.tracer.startSpan(this.operationName(), {
|
|
@@ -33,9 +34,13 @@ class NextPlugin extends ServerPlugin {
|
|
|
33
34
|
|
|
34
35
|
analyticsSampler.sample(span, this.config.measured, true)
|
|
35
36
|
|
|
36
|
-
this.enter(span, store)
|
|
37
|
-
|
|
38
37
|
this._requests.set(span, req)
|
|
38
|
+
|
|
39
|
+
return { ...store, span }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
error ({ span, error }) {
|
|
43
|
+
this.addError(error, span)
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
finish ({ req, res }) {
|
|
@@ -45,6 +50,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
45
50
|
|
|
46
51
|
const span = store.span
|
|
47
52
|
const error = span.context()._tags['error']
|
|
53
|
+
const page = span.context()._tags['next.page']
|
|
48
54
|
|
|
49
55
|
if (!this.config.validateStatus(res.statusCode) && !error) {
|
|
50
56
|
span.setTag('error', true)
|
|
@@ -54,6 +60,8 @@ class NextPlugin extends ServerPlugin {
|
|
|
54
60
|
'http.status_code': res.statusCode
|
|
55
61
|
})
|
|
56
62
|
|
|
63
|
+
if (page) web.setRoute(req, page)
|
|
64
|
+
|
|
57
65
|
this.config.hooks.request(span, req, res)
|
|
58
66
|
|
|
59
67
|
span.finish()
|
|
@@ -8,7 +8,7 @@ const { getIastContext } = require('../iast-context')
|
|
|
8
8
|
const { addVulnerability } = require('../vulnerability-reporter')
|
|
9
9
|
const { getNodeModulesPaths } = require('../path-line')
|
|
10
10
|
|
|
11
|
-
const EXCLUDED_PATHS = getNodeModulesPaths('mysql2', 'sequelize')
|
|
11
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('mysql2', 'sequelize', 'pg-pool')
|
|
12
12
|
|
|
13
13
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
14
14
|
constructor () {
|
|
@@ -23,7 +23,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
23
23
|
this.addSub('datadog:sequelize:query:start', ({ sql, dialect }) => {
|
|
24
24
|
const parentStore = storage.getStore()
|
|
25
25
|
if (parentStore) {
|
|
26
|
-
this.analyze(sql, dialect.toUpperCase())
|
|
26
|
+
this.analyze(sql, dialect.toUpperCase(), parentStore)
|
|
27
27
|
|
|
28
28
|
storage.enterWith({ ...parentStore, sqlAnalyzed: true, sequelizeParentStore: parentStore })
|
|
29
29
|
}
|
|
@@ -35,6 +35,22 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
35
35
|
storage.enterWith(store.sequelizeParentStore)
|
|
36
36
|
}
|
|
37
37
|
})
|
|
38
|
+
|
|
39
|
+
this.addSub('datadog:pg:pool:query:start', ({ query }) => {
|
|
40
|
+
const parentStore = storage.getStore()
|
|
41
|
+
if (parentStore) {
|
|
42
|
+
this.analyze(query.text, 'POSTGRES', parentStore)
|
|
43
|
+
|
|
44
|
+
storage.enterWith({ ...parentStore, sqlAnalyzed: true, pgPoolParentStore: parentStore })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
this.addSub('datadog:pg:pool:query:finish', () => {
|
|
49
|
+
const store = storage.getStore()
|
|
50
|
+
if (store && store.pgPoolParentStore) {
|
|
51
|
+
storage.enterWith(store.pgPoolParentStore)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
_getEvidence (value, iastContext, dialect) {
|
|
@@ -42,8 +58,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
42
58
|
return { value, ranges, dialect }
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
analyze (value, dialect) {
|
|
46
|
-
const store = storage.getStore()
|
|
61
|
+
analyze (value, dialect, store = storage.getStore()) {
|
|
47
62
|
if (!(store && store.sqlAnalyzed)) {
|
|
48
63
|
const iastContext = getIastContext(store)
|
|
49
64
|
if (this._isInvalidContext(store, iastContext)) return
|
|
@@ -66,7 +66,7 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
|
66
66
|
const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
|
|
67
67
|
parent[taintedProperty] = tainted
|
|
68
68
|
} else {
|
|
69
|
-
parent[
|
|
69
|
+
parent[key] = tainted
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
} else if (typeof value === 'object' && !visited.has(value)) {
|
|
@@ -5,11 +5,20 @@ const telemetryLogs = require('./log')
|
|
|
5
5
|
const { Verbosity, getVerbosity } = require('./verbosity')
|
|
6
6
|
const { initRequestNamespace, finalizeRequestNamespace, globalNamespace } = require('./namespaces')
|
|
7
7
|
|
|
8
|
+
function isIastMetricsEnabled (metrics) {
|
|
9
|
+
// TODO: let DD_TELEMETRY_METRICS_ENABLED as undefined in config.js to avoid read here the env property
|
|
10
|
+
return process.env.DD_TELEMETRY_METRICS_ENABLED !== undefined ? metrics : true
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
class Telemetry {
|
|
9
14
|
configure (config, verbosity) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
const telemetryAndMetricsEnabled = config &&
|
|
16
|
+
config.telemetry &&
|
|
17
|
+
config.telemetry.enabled &&
|
|
18
|
+
isIastMetricsEnabled(config.telemetry.metrics)
|
|
19
|
+
|
|
20
|
+
this.verbosity = telemetryAndMetricsEnabled ? getVerbosity(verbosity) : Verbosity.OFF
|
|
21
|
+
this.enabled = this.verbosity !== Verbosity.OFF
|
|
13
22
|
|
|
14
23
|
if (this.enabled) {
|
|
15
24
|
telemetryMetrics.manager.set('iast', globalNamespace)
|
|
@@ -30,13 +39,13 @@ class Telemetry {
|
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
onRequestStart (context) {
|
|
33
|
-
if (this.isEnabled()
|
|
42
|
+
if (this.isEnabled()) {
|
|
34
43
|
initRequestNamespace(context)
|
|
35
44
|
}
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
onRequestEnd (context, rootSpan) {
|
|
39
|
-
if (this.isEnabled()
|
|
48
|
+
if (this.isEnabled()) {
|
|
40
49
|
finalizeRequestNamespace(context, rootSpan)
|
|
41
50
|
}
|
|
42
51
|
}
|
|
@@ -14,6 +14,8 @@ const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phra
|
|
|
14
14
|
// eslint-disable-next-line max-len
|
|
15
15
|
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
|
|
16
16
|
|
|
17
|
+
const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
18
|
+
|
|
17
19
|
class SensitiveHandler {
|
|
18
20
|
constructor () {
|
|
19
21
|
this._namePattern = new RegExp(DEFAULT_IAST_REDACTION_NAME_PATTERN, 'gmi')
|
|
@@ -54,6 +56,7 @@ class SensitiveHandler {
|
|
|
54
56
|
toRedactedJson (evidence, sensitive, sourcesIndexes, sources) {
|
|
55
57
|
const valueParts = []
|
|
56
58
|
const redactedSources = []
|
|
59
|
+
const redactedSourcesContext = []
|
|
57
60
|
|
|
58
61
|
const { value, ranges } = evidence
|
|
59
62
|
|
|
@@ -71,21 +74,41 @@ class SensitiveHandler {
|
|
|
71
74
|
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
72
75
|
|
|
73
76
|
while (nextSensitive != null && contains(nextTainted, nextSensitive)) {
|
|
74
|
-
|
|
77
|
+
const redactionStart = nextSensitive.start - nextTainted.start
|
|
78
|
+
const redactionEnd = nextSensitive.end - nextTainted.start
|
|
79
|
+
this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
|
|
75
80
|
nextSensitive = sensitive.shift()
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
if (nextSensitive != null && intersects(nextSensitive, nextTainted)) {
|
|
79
|
-
|
|
84
|
+
const redactionStart = nextSensitive.start - nextTainted.start
|
|
85
|
+
const redactionEnd = nextSensitive.end - nextTainted.start
|
|
86
|
+
this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
|
|
80
87
|
|
|
81
88
|
const entries = remove(nextSensitive, nextTainted)
|
|
82
89
|
nextSensitive = entries.length > 0 ? entries[0] : null
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
this.isSensibleSource(sources[sourceIndex])
|
|
92
|
+
if (this.isSensibleSource(sources[sourceIndex])) {
|
|
93
|
+
if (!sources[sourceIndex].redacted) {
|
|
94
|
+
redactedSources.push(sourceIndex)
|
|
95
|
+
sources[sourceIndex].pattern = ''.padEnd(sources[sourceIndex].value.length, REDACTED_SOURCE_BUFFER)
|
|
96
|
+
sources[sourceIndex].redacted = true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
86
99
|
|
|
87
100
|
if (redactedSources.indexOf(sourceIndex) > -1) {
|
|
88
|
-
|
|
101
|
+
const partValue = value.substring(i, i + (nextTainted.end - nextTainted.start))
|
|
102
|
+
this.writeRedactedValuePart(
|
|
103
|
+
valueParts,
|
|
104
|
+
partValue.length,
|
|
105
|
+
sourceIndex,
|
|
106
|
+
partValue,
|
|
107
|
+
sources[sourceIndex],
|
|
108
|
+
redactedSourcesContext[sourceIndex],
|
|
109
|
+
this.isSensibleSource(sources[sourceIndex])
|
|
110
|
+
)
|
|
111
|
+
redactedSourcesContext[sourceIndex] = []
|
|
89
112
|
} else {
|
|
90
113
|
const substringEnd = Math.min(nextTainted.end, value.length)
|
|
91
114
|
this.writeValuePart(valueParts, value.substring(nextTainted.start, substringEnd), sourceIndex)
|
|
@@ -100,7 +123,10 @@ class SensitiveHandler {
|
|
|
100
123
|
this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
|
|
101
124
|
if (nextTainted != null && intersects(nextSensitive, nextTainted)) {
|
|
102
125
|
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
103
|
-
|
|
126
|
+
|
|
127
|
+
const redactionStart = nextSensitive.start - nextTainted.start
|
|
128
|
+
const redactionEnd = nextSensitive.end - nextTainted.start
|
|
129
|
+
this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
|
|
104
130
|
|
|
105
131
|
for (const entry of remove(nextSensitive, nextTainted)) {
|
|
106
132
|
if (entry.start === i) {
|
|
@@ -111,9 +137,10 @@ class SensitiveHandler {
|
|
|
111
137
|
}
|
|
112
138
|
}
|
|
113
139
|
|
|
114
|
-
|
|
140
|
+
const _length = nextSensitive.end - nextSensitive.start
|
|
141
|
+
this.writeRedactedValuePart(valueParts, _length)
|
|
115
142
|
|
|
116
|
-
start = i +
|
|
143
|
+
start = i + _length
|
|
117
144
|
i = start - 1
|
|
118
145
|
nextSensitive = sensitive.shift()
|
|
119
146
|
}
|
|
@@ -126,6 +153,24 @@ class SensitiveHandler {
|
|
|
126
153
|
return { redactedValueParts: valueParts, redactedSources }
|
|
127
154
|
}
|
|
128
155
|
|
|
156
|
+
redactSource (sources, redactedSources, redactedSourcesContext, sourceIndex, start, end) {
|
|
157
|
+
if (sourceIndex != null) {
|
|
158
|
+
if (!sources[sourceIndex].redacted) {
|
|
159
|
+
redactedSources.push(sourceIndex)
|
|
160
|
+
sources[sourceIndex].pattern = ''.padEnd(sources[sourceIndex].value.length, REDACTED_SOURCE_BUFFER)
|
|
161
|
+
sources[sourceIndex].redacted = true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!redactedSourcesContext[sourceIndex]) {
|
|
165
|
+
redactedSourcesContext[sourceIndex] = []
|
|
166
|
+
}
|
|
167
|
+
redactedSourcesContext[sourceIndex].push({
|
|
168
|
+
start,
|
|
169
|
+
end
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
129
174
|
writeValuePart (valueParts, value, source) {
|
|
130
175
|
if (value.length > 0) {
|
|
131
176
|
if (source != null) {
|
|
@@ -136,9 +181,74 @@ class SensitiveHandler {
|
|
|
136
181
|
}
|
|
137
182
|
}
|
|
138
183
|
|
|
139
|
-
writeRedactedValuePart (
|
|
140
|
-
|
|
141
|
-
|
|
184
|
+
writeRedactedValuePart (
|
|
185
|
+
valueParts,
|
|
186
|
+
length,
|
|
187
|
+
sourceIndex,
|
|
188
|
+
partValue,
|
|
189
|
+
source,
|
|
190
|
+
sourceRedactionContext,
|
|
191
|
+
isSensibleSource
|
|
192
|
+
) {
|
|
193
|
+
if (sourceIndex != null) {
|
|
194
|
+
const placeholder = source.value.includes(partValue)
|
|
195
|
+
? source.pattern
|
|
196
|
+
: '*'.repeat(length)
|
|
197
|
+
|
|
198
|
+
if (isSensibleSource) {
|
|
199
|
+
valueParts.push({ redacted: true, source: sourceIndex, pattern: placeholder })
|
|
200
|
+
} else {
|
|
201
|
+
let _value = partValue
|
|
202
|
+
const dedupedSourceRedactionContexts = []
|
|
203
|
+
|
|
204
|
+
sourceRedactionContext.forEach(_sourceRedactionContext => {
|
|
205
|
+
const isPresentInDeduped = dedupedSourceRedactionContexts.some(_dedupedSourceRedactionContext =>
|
|
206
|
+
_dedupedSourceRedactionContext.start === _sourceRedactionContext.start &&
|
|
207
|
+
_dedupedSourceRedactionContext.end === _sourceRedactionContext.end
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if (!isPresentInDeduped) {
|
|
211
|
+
dedupedSourceRedactionContexts.push(_sourceRedactionContext)
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
let offset = 0
|
|
216
|
+
dedupedSourceRedactionContexts.forEach((_sourceRedactionContext) => {
|
|
217
|
+
if (_sourceRedactionContext.start > 0) {
|
|
218
|
+
valueParts.push({
|
|
219
|
+
source: sourceIndex,
|
|
220
|
+
value: _value.substring(0, _sourceRedactionContext.start - offset)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
_value = _value.substring(_sourceRedactionContext.start - offset)
|
|
224
|
+
offset = _sourceRedactionContext.start
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const sensitive =
|
|
228
|
+
_value.substring(_sourceRedactionContext.start - offset, _sourceRedactionContext.end - offset)
|
|
229
|
+
const indexOfPartValueInPattern = source.value.indexOf(sensitive)
|
|
230
|
+
|
|
231
|
+
const pattern = indexOfPartValueInPattern > -1
|
|
232
|
+
? placeholder.substring(indexOfPartValueInPattern, indexOfPartValueInPattern + sensitive.length)
|
|
233
|
+
: placeholder.substring(_sourceRedactionContext.start, _sourceRedactionContext.end)
|
|
234
|
+
|
|
235
|
+
valueParts.push({
|
|
236
|
+
redacted: true,
|
|
237
|
+
source: sourceIndex,
|
|
238
|
+
pattern
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
_value = _value.substring(pattern.length)
|
|
242
|
+
offset += pattern.length
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
if (_value.length) {
|
|
246
|
+
valueParts.push({
|
|
247
|
+
source: sourceIndex,
|
|
248
|
+
value: _value
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
142
252
|
} else {
|
|
143
253
|
valueParts.push({ redacted: true })
|
|
144
254
|
}
|
|
@@ -35,14 +35,14 @@ class DogStatsDClient {
|
|
|
35
35
|
this._udp6 = this._socket('udp6')
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
gauge (stat, value, tags) {
|
|
39
|
-
this._add(stat, value, TYPE_GAUGE, tags)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
38
|
increment (stat, value, tags) {
|
|
43
39
|
this._add(stat, value, TYPE_COUNTER, tags)
|
|
44
40
|
}
|
|
45
41
|
|
|
42
|
+
gauge (stat, value, tags) {
|
|
43
|
+
this._add(stat, value, TYPE_GAUGE, tags)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
46
|
distribution (stat, value, tags) {
|
|
47
47
|
this._add(stat, value, TYPE_DISTRIBUTION, tags)
|
|
48
48
|
}
|
|
@@ -153,7 +153,67 @@ class NoopDogStatsDClient {
|
|
|
153
153
|
flush () { }
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// This is a simplified user-facing proxy to the underlying DogStatsDClient instance
|
|
157
|
+
class CustomMetrics {
|
|
158
|
+
constructor (options) {
|
|
159
|
+
this.dogstatsd = new DogStatsDClient(options)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
increment (stat, value = 1, tags) {
|
|
163
|
+
return this.dogstatsd.increment(
|
|
164
|
+
stat,
|
|
165
|
+
value,
|
|
166
|
+
CustomMetrics.tagTranslator(tags)
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
decrement (stat, value = 1, tags) {
|
|
171
|
+
return this.dogstatsd.increment(
|
|
172
|
+
stat,
|
|
173
|
+
value * -1,
|
|
174
|
+
CustomMetrics.tagTranslator(tags)
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
gauge (stat, value, tags) {
|
|
179
|
+
return this.dogstatsd.gauge(
|
|
180
|
+
stat,
|
|
181
|
+
value,
|
|
182
|
+
CustomMetrics.tagTranslator(tags)
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
distribution (stat, value, tags) {
|
|
187
|
+
return this.dogstatsd.distribution(
|
|
188
|
+
stat,
|
|
189
|
+
value,
|
|
190
|
+
CustomMetrics.tagTranslator(tags)
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
flush () {
|
|
195
|
+
return this.dogstatsd.flush()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Exposing { tagName: 'tagValue' } to the end user
|
|
200
|
+
* These are translated into [ 'tagName:tagValue' ] for internal use
|
|
201
|
+
*/
|
|
202
|
+
static tagTranslator (objTags) {
|
|
203
|
+
const arrTags = []
|
|
204
|
+
|
|
205
|
+
if (!objTags) return arrTags
|
|
206
|
+
|
|
207
|
+
for (const [key, value] of Object.entries(objTags)) {
|
|
208
|
+
arrTags.push(`${key}:${value}`)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return arrTags
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
156
215
|
module.exports = {
|
|
157
216
|
DogStatsDClient,
|
|
158
|
-
NoopDogStatsDClient
|
|
217
|
+
NoopDogStatsDClient,
|
|
218
|
+
CustomMetrics
|
|
159
219
|
}
|