dd-trace 4.11.1 → 4.16.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/LICENSE-3rdparty.csv +1 -0
- package/README.md +4 -9
- package/ext/tags.d.ts +1 -0
- package/ext/tags.js +1 -0
- package/index.d.ts +44 -0
- package/package.json +9 -6
- package/packages/datadog-esbuild/index.js +57 -32
- package/packages/datadog-instrumentations/src/body-parser.js +2 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
- package/packages/datadog-instrumentations/src/cucumber.js +30 -11
- package/packages/datadog-instrumentations/src/express.js +1 -1
- package/packages/datadog-instrumentations/src/graphql.js +10 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +22 -11
- package/packages/datadog-instrumentations/src/kafkajs.js +3 -4
- package/packages/datadog-instrumentations/src/mocha.js +33 -8
- package/packages/datadog-instrumentations/src/mysql.js +39 -1
- package/packages/datadog-instrumentations/src/next.js +47 -19
- package/packages/datadog-instrumentations/src/openai.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +60 -15
- package/packages/datadog-instrumentations/src/playwright.js +15 -3
- package/packages/datadog-plugin-cucumber/src/index.js +14 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +49 -13
- package/packages/datadog-plugin-graphql/src/index.js +3 -3
- package/packages/datadog-plugin-graphql/src/resolve.js +27 -2
- 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-mongodb-core/src/index.js +6 -2
- package/packages/datadog-plugin-mysql/src/index.js +2 -2
- package/packages/datadog-plugin-next/src/index.js +22 -5
- package/packages/datadog-plugin-pg/src/index.js +2 -2
- package/packages/dd-trace/src/appsec/addresses.js +1 -0
- package/packages/dd-trace/src/appsec/channels.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +7 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +29 -18
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
- 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 +131 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
- package/packages/dd-trace/src/appsec/index.js +42 -7
- package/packages/dd-trace/src/appsec/recommended.json +655 -31
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
- package/packages/dd-trace/src/appsec/reporter.js +26 -0
- package/packages/dd-trace/src/appsec/telemetry.js +132 -0
- package/packages/dd-trace/src/appsec/waf/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +13 -5
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +12 -14
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
- package/packages/dd-trace/src/datastreams/processor.js +6 -2
- package/packages/dd-trace/src/dogstatsd.js +108 -8
- 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/format.js +6 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
- package/packages/dd-trace/src/opentracing/span.js +13 -13
- package/packages/dd-trace/src/opentracing/tracer.js +3 -5
- package/packages/dd-trace/src/plugin_manager.js +1 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -3
- package/packages/dd-trace/src/plugins/tracing.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +20 -3
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +31 -7
- package/packages/dd-trace/src/proxy.js +13 -2
- package/packages/dd-trace/src/ritm.js +10 -2
- package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +1 -32
- package/packages/dd-trace/src/telemetry/dependencies.js +15 -0
- package/packages/dd-trace/src/telemetry/index.js +21 -2
- package/packages/dd-trace/src/util.js +1 -1
|
@@ -20,10 +20,12 @@ 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')
|
|
28
|
+
const NoopTracer = require('../../dd-trace/src/noop/tracer')
|
|
27
29
|
|
|
28
30
|
const TEST_FRAMEWORK_NAME = 'cypress'
|
|
29
31
|
|
|
@@ -118,9 +120,32 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
|
|
|
118
120
|
})
|
|
119
121
|
}
|
|
120
122
|
|
|
123
|
+
const noopTask = {
|
|
124
|
+
'dd:testSuiteStart': () => {
|
|
125
|
+
return null
|
|
126
|
+
},
|
|
127
|
+
'dd:beforeEach': () => {
|
|
128
|
+
return {}
|
|
129
|
+
},
|
|
130
|
+
'dd:afterEach': () => {
|
|
131
|
+
return null
|
|
132
|
+
},
|
|
133
|
+
'dd:addTags': () => {
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
121
138
|
module.exports = (on, config) => {
|
|
122
139
|
let isTestsSkipped = false
|
|
140
|
+
const skippedTests = []
|
|
123
141
|
const tracer = require('../../dd-trace')
|
|
142
|
+
|
|
143
|
+
// The tracer was not init correctly for whatever reason (such as invalid DD_SITE)
|
|
144
|
+
if (tracer._tracer instanceof NoopTracer) {
|
|
145
|
+
// We still need to register these tasks or the support file will fail
|
|
146
|
+
return on('task', noopTask)
|
|
147
|
+
}
|
|
148
|
+
|
|
124
149
|
const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
|
|
125
150
|
|
|
126
151
|
const {
|
|
@@ -248,19 +273,23 @@ module.exports = (on, config) => {
|
|
|
248
273
|
const cypressTests = tests || []
|
|
249
274
|
const finishedTests = finishedTestsByFile[spec.relative] || []
|
|
250
275
|
|
|
251
|
-
// Get tests that didn't go through `dd:afterEach`
|
|
276
|
+
// Get tests that didn't go through `dd:afterEach`
|
|
252
277
|
// and create a skipped test span for each of them
|
|
253
278
|
cypressTests.filter(({ title }) => {
|
|
254
279
|
const cypressTestName = title.join(' ')
|
|
255
|
-
const isSkippedByItr = testsToSkip.find(test =>
|
|
256
|
-
cypressTestName === test.name && spec.relative === test.suite
|
|
257
|
-
)
|
|
258
280
|
const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
|
|
259
281
|
|
|
260
|
-
return !
|
|
282
|
+
return !isTestFinished
|
|
261
283
|
}).forEach(({ title }) => {
|
|
262
|
-
const
|
|
284
|
+
const cypressTestName = title.join(' ')
|
|
285
|
+
const isSkippedByItr = testsToSkip.find(test =>
|
|
286
|
+
cypressTestName === test.name && spec.relative === test.suite
|
|
287
|
+
)
|
|
288
|
+
const skippedTestSpan = getTestSpan(cypressTestName, spec.relative)
|
|
263
289
|
skippedTestSpan.setTag(TEST_STATUS, 'skip')
|
|
290
|
+
if (isSkippedByItr) {
|
|
291
|
+
skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
|
|
292
|
+
}
|
|
264
293
|
skippedTestSpan.finish()
|
|
265
294
|
})
|
|
266
295
|
|
|
@@ -309,7 +338,9 @@ module.exports = (on, config) => {
|
|
|
309
338
|
{
|
|
310
339
|
isSuitesSkipped: isTestsSkipped,
|
|
311
340
|
isSuitesSkippingEnabled,
|
|
312
|
-
isCodeCoverageEnabled
|
|
341
|
+
isCodeCoverageEnabled,
|
|
342
|
+
skippingType: 'test',
|
|
343
|
+
skippingCount: skippedTests.length
|
|
313
344
|
}
|
|
314
345
|
)
|
|
315
346
|
|
|
@@ -320,12 +351,16 @@ module.exports = (on, config) => {
|
|
|
320
351
|
}
|
|
321
352
|
|
|
322
353
|
return new Promise(resolve => {
|
|
323
|
-
|
|
324
|
-
|
|
354
|
+
const exporter = tracer._tracer._exporter
|
|
355
|
+
if (!exporter) {
|
|
356
|
+
return resolve(null)
|
|
357
|
+
}
|
|
358
|
+
if (exporter.flush) {
|
|
359
|
+
exporter.flush(() => {
|
|
325
360
|
resolve(null)
|
|
326
361
|
})
|
|
327
|
-
} else {
|
|
328
|
-
|
|
362
|
+
} else if (exporter._writer) {
|
|
363
|
+
exporter._writer.flush(() => {
|
|
329
364
|
resolve(null)
|
|
330
365
|
})
|
|
331
366
|
}
|
|
@@ -353,6 +388,7 @@ module.exports = (on, config) => {
|
|
|
353
388
|
if (testsToSkip.find(test => {
|
|
354
389
|
return testName === test.name && testSuite === test.suite
|
|
355
390
|
})) {
|
|
391
|
+
skippedTests.push(test)
|
|
356
392
|
isTestsSkipped = true
|
|
357
393
|
return { shouldSkip: true }
|
|
358
394
|
}
|
|
@@ -366,7 +402,7 @@ module.exports = (on, config) => {
|
|
|
366
402
|
'dd:afterEach': ({ test, coverage }) => {
|
|
367
403
|
const { state, error, isRUMActive, testSourceLine, testSuite, testName } = test
|
|
368
404
|
if (activeSpan) {
|
|
369
|
-
if (coverage && tracer._tracer._exporter
|
|
405
|
+
if (coverage && isCodeCoverageEnabled && tracer._tracer._exporter && tracer._tracer._exporter.exportCoverage) {
|
|
370
406
|
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
|
|
371
407
|
const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, rootDir))
|
|
372
408
|
const { _traceId, _spanId } = testSuiteSpan.context()
|
|
@@ -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
|
}
|
|
@@ -8,13 +8,14 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
8
8
|
static get id () { return 'graphql' }
|
|
9
9
|
static get operation () { return 'resolve' }
|
|
10
10
|
|
|
11
|
-
start ({ info, context }) {
|
|
11
|
+
start ({ info, context, args }) {
|
|
12
12
|
const path = getPath(info, this.config)
|
|
13
13
|
|
|
14
14
|
if (!shouldInstrument(this.config, path)) return
|
|
15
|
-
|
|
16
15
|
const computedPathString = path.join('.')
|
|
17
16
|
|
|
17
|
+
addResolver(context, info, args)
|
|
18
|
+
|
|
18
19
|
if (this.config.collapse) {
|
|
19
20
|
if (!context[collapsedPathSym]) {
|
|
20
21
|
context[collapsedPathSym] = {}
|
|
@@ -108,4 +109,28 @@ function withCollapse (responsePathAsArray) {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
function addResolver (context, info, args) {
|
|
113
|
+
if (info.rootValue && !info.rootValue[info.fieldName]) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!context.resolvers) {
|
|
118
|
+
context.resolvers = {}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const resolvers = context.resolvers
|
|
122
|
+
|
|
123
|
+
if (!resolvers[info.fieldName]) {
|
|
124
|
+
if (args && Object.keys(args).length) {
|
|
125
|
+
resolvers[info.fieldName] = [args]
|
|
126
|
+
} else {
|
|
127
|
+
resolvers[info.fieldName] = []
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
if (args && Object.keys(args).length) {
|
|
131
|
+
resolvers[info.fieldName].push(args)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
111
136
|
module.exports = GraphQLResolvePlugin
|
|
@@ -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()
|
|
@@ -36,10 +36,14 @@ class MongodbCorePlugin extends DatabasePlugin {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function sanitizeBigInt (data) {
|
|
40
|
+
return JSON.stringify(data, (_key, value) => typeof value === 'bigint' ? value.toString() : value)
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
function getQuery (cmd) {
|
|
40
44
|
if (!cmd || typeof cmd !== 'object' || Array.isArray(cmd)) return
|
|
41
|
-
if (cmd.query) return
|
|
42
|
-
if (cmd.filter) return
|
|
45
|
+
if (cmd.query) return sanitizeBigInt(limitDepth(cmd.query))
|
|
46
|
+
if (cmd.filter) return sanitizeBigInt(limitDepth(cmd.filter))
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
function getResource (plugin, ns, query, operationName) {
|
|
@@ -9,7 +9,7 @@ class MySQLPlugin extends DatabasePlugin {
|
|
|
9
9
|
|
|
10
10
|
start (payload) {
|
|
11
11
|
const service = this.serviceName({ pluginConfig: this.config, dbConfig: payload.conf, system: this.system })
|
|
12
|
-
this.startSpan(this.operationName(), {
|
|
12
|
+
const span = this.startSpan(this.operationName(), {
|
|
13
13
|
service,
|
|
14
14
|
resource: payload.sql,
|
|
15
15
|
type: 'sql',
|
|
@@ -22,7 +22,7 @@ class MySQLPlugin extends DatabasePlugin {
|
|
|
22
22
|
[CLIENT_PORT_KEY]: payload.conf.port
|
|
23
23
|
}
|
|
24
24
|
})
|
|
25
|
-
payload.sql = this.injectDbmQuery(payload.sql, service)
|
|
25
|
+
payload.sql = this.injectDbmQuery(span, payload.sql, service)
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -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 }) {
|
|
@@ -59,7 +64,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
59
64
|
span.finish()
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
pageLoad ({ page }) {
|
|
67
|
+
pageLoad ({ page, isAppPath }) {
|
|
63
68
|
const store = storage.getStore()
|
|
64
69
|
|
|
65
70
|
if (!store) return
|
|
@@ -69,15 +74,27 @@ class NextPlugin extends ServerPlugin {
|
|
|
69
74
|
|
|
70
75
|
// Only use error page names if there's not already a name
|
|
71
76
|
const current = span.context()._tags['next.page']
|
|
72
|
-
if (current &&
|
|
77
|
+
if (current && ['/404', '/500', '/_error', '/_not-found'].includes(page)) {
|
|
73
78
|
return
|
|
74
79
|
}
|
|
75
80
|
|
|
81
|
+
// remove ending /route or /page for appDir projects
|
|
82
|
+
if (isAppPath) page = page.substring(0, page.lastIndexOf('/'))
|
|
83
|
+
|
|
84
|
+
// This is for static files whose 'page' includes the whole file path
|
|
85
|
+
// For normal page matches, like /api/hello/[name] and a req.url like /api/hello/world,
|
|
86
|
+
// nothing should happen
|
|
87
|
+
// For page matches like /User/something/public/text.txt and req.url like /text.txt,
|
|
88
|
+
// it should disregard the extra absolute path Next.js sometimes sets
|
|
89
|
+
if (page.includes(req.url)) page = req.url
|
|
90
|
+
|
|
76
91
|
span.addTags({
|
|
77
92
|
[COMPONENT]: this.constructor.id,
|
|
78
93
|
'resource.name': `${req.method} ${page}`.trim(),
|
|
79
94
|
'next.page': page
|
|
80
95
|
})
|
|
96
|
+
|
|
97
|
+
web.setRoute(req, page)
|
|
81
98
|
}
|
|
82
99
|
|
|
83
100
|
configure (config) {
|
|
@@ -12,7 +12,7 @@ class PGPlugin extends DatabasePlugin {
|
|
|
12
12
|
const service = this.serviceName({ pluginConfig: this.config, params })
|
|
13
13
|
const originalStatement = this.maybeTruncate(query.text)
|
|
14
14
|
|
|
15
|
-
this.startSpan(this.operationName(), {
|
|
15
|
+
const span = this.startSpan(this.operationName(), {
|
|
16
16
|
service,
|
|
17
17
|
resource: originalStatement,
|
|
18
18
|
type: 'sql',
|
|
@@ -27,7 +27,7 @@ class PGPlugin extends DatabasePlugin {
|
|
|
27
27
|
}
|
|
28
28
|
})
|
|
29
29
|
|
|
30
|
-
query.__ddInjectableQuery = this.injectDbmQuery(query.text, service, !!query.name)
|
|
30
|
+
query.__ddInjectableQuery = this.injectDbmQuery(span, query.text, service, !!query.name)
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -12,6 +12,7 @@ module.exports = {
|
|
|
12
12
|
HTTP_INCOMING_RESPONSE_CODE: 'server.response.status',
|
|
13
13
|
HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
|
|
14
14
|
// TODO: 'server.response.trailers',
|
|
15
|
+
HTTP_INCOMING_GRAPHQL_RESOLVERS: 'graphql.server.all_resolvers',
|
|
15
16
|
|
|
16
17
|
HTTP_CLIENT_IP: 'http.client_ip',
|
|
17
18
|
|
|
@@ -5,6 +5,8 @@ const dc = require('../../../diagnostics_channel')
|
|
|
5
5
|
// TODO: use TBD naming convention
|
|
6
6
|
module.exports = {
|
|
7
7
|
bodyParser: dc.channel('datadog:body-parser:read:finish'),
|
|
8
|
+
cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
|
|
9
|
+
graphqlFinishExecute: dc.channel('apm:graphql:execute:finish'),
|
|
8
10
|
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
|
|
9
11
|
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
|
|
10
12
|
passportVerify: dc.channel('datadog:passport:verify:finish'),
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
3
3
|
const { LDAP_INJECTION } = require('../vulnerabilities')
|
|
4
|
+
const { getNodeModulesPaths } = require('../path-line')
|
|
5
|
+
|
|
6
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('ldapjs-promise')
|
|
4
7
|
|
|
5
8
|
class LdapInjectionAnalyzer extends InjectionAnalyzer {
|
|
6
9
|
constructor () {
|
|
@@ -10,6 +13,10 @@ class LdapInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
10
13
|
onConfigure () {
|
|
11
14
|
this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
|
|
12
15
|
}
|
|
16
|
+
|
|
17
|
+
_getExcludedPaths () {
|
|
18
|
+
return EXCLUDED_PATHS
|
|
19
|
+
}
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
module.exports = new LdapInjectionAnalyzer()
|
|
@@ -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('mysql', 'mysql2', 'sequelize', 'pg-pool')
|
|
12
12
|
|
|
13
13
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
14
14
|
constructor () {
|
|
@@ -20,21 +20,33 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
20
20
|
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
|
|
21
21
|
this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, 'POSTGRES'))
|
|
22
22
|
|
|
23
|
-
this.addSub(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.addSub('datadog:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
this.addSub(
|
|
24
|
+
'datadog:sequelize:query:start',
|
|
25
|
+
({ sql, dialect }) => this.getStoreAndAnalyze(sql, dialect.toUpperCase())
|
|
26
|
+
)
|
|
27
|
+
this.addSub('datadog:sequelize:query:finish', () => this.returnToParentStore())
|
|
28
|
+
|
|
29
|
+
this.addSub('datadog:pg:pool:query:start', ({ query }) => this.getStoreAndAnalyze(query.text, 'POSTGRES'))
|
|
30
|
+
this.addSub('datadog:pg:pool:query:finish', () => this.returnToParentStore())
|
|
31
|
+
|
|
32
|
+
this.addSub('datadog:mysql:pool:query:start', ({ sql }) => this.getStoreAndAnalyze(sql, 'MYSQL'))
|
|
33
|
+
this.addSub('datadog:mysql:pool:query:finish', () => this.returnToParentStore())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getStoreAndAnalyze (query, dialect) {
|
|
37
|
+
const parentStore = storage.getStore()
|
|
38
|
+
if (parentStore) {
|
|
39
|
+
this.analyze(query, dialect, parentStore)
|
|
40
|
+
|
|
41
|
+
storage.enterWith({ ...parentStore, sqlAnalyzed: true, sqlParentStore: parentStore })
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
returnToParentStore () {
|
|
46
|
+
const store = storage.getStore()
|
|
47
|
+
if (store && store.sqlParentStore) {
|
|
48
|
+
storage.enterWith(store.sqlParentStore)
|
|
49
|
+
}
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
_getEvidence (value, iastContext, dialect) {
|
|
@@ -42,8 +54,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
42
54
|
return { value, ranges, dialect }
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
analyze (value, dialect) {
|
|
46
|
-
const store = storage.getStore()
|
|
57
|
+
analyze (value, dialect, store = storage.getStore()) {
|
|
47
58
|
if (!(store && store.sqlAnalyzed)) {
|
|
48
59
|
const iastContext = getIastContext(store)
|
|
49
60
|
if (this._isInvalidContext(store, iastContext)) return
|
|
@@ -6,6 +6,7 @@ const { addVulnerability } = require('../vulnerability-reporter')
|
|
|
6
6
|
const { getIastContext } = require('../iast-context')
|
|
7
7
|
const overheadController = require('../overhead-controller')
|
|
8
8
|
const { SinkIastPlugin } = require('../iast-plugin')
|
|
9
|
+
const { getOriginalPathAndLineFromSourceMap } = require('../taint-tracking/rewriter')
|
|
9
10
|
|
|
10
11
|
class Analyzer extends SinkIastPlugin {
|
|
11
12
|
constructor (type) {
|
|
@@ -25,8 +26,9 @@ class Analyzer extends SinkIastPlugin {
|
|
|
25
26
|
const evidence = this._getEvidence(value, context)
|
|
26
27
|
const location = this._getLocation(value)
|
|
27
28
|
if (!this._isExcluded(location)) {
|
|
29
|
+
const locationSourceMap = this._replaceLocationFromSourceMap(location)
|
|
28
30
|
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
29
|
-
const vulnerability = this._createVulnerability(this._type, evidence, spanId,
|
|
31
|
+
const vulnerability = this._createVulnerability(this._type, evidence, spanId, locationSourceMap)
|
|
30
32
|
addVulnerability(context, vulnerability)
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -47,6 +49,22 @@ class Analyzer extends SinkIastPlugin {
|
|
|
47
49
|
return getFirstNonDDPathAndLine(this._getExcludedPaths())
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
_replaceLocationFromSourceMap (location) {
|
|
53
|
+
if (location) {
|
|
54
|
+
const { path, line, column } = getOriginalPathAndLineFromSourceMap(location)
|
|
55
|
+
if (path) {
|
|
56
|
+
location.path = path
|
|
57
|
+
}
|
|
58
|
+
if (line) {
|
|
59
|
+
location.line = line
|
|
60
|
+
}
|
|
61
|
+
if (column) {
|
|
62
|
+
location.column = column
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return location
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
_getExcludedPaths () {}
|
|
51
69
|
|
|
52
70
|
_isInvalidContext (store, iastContext) {
|
|
@@ -47,6 +47,7 @@ function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPat
|
|
|
47
47
|
return {
|
|
48
48
|
path: path.relative(process.cwd(), filepath),
|
|
49
49
|
line: callsite.getLineNumber(),
|
|
50
|
+
column: callsite.getColumnNumber(),
|
|
50
51
|
isInternal: !path.isAbsolute(filepath)
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -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)) {
|
|
@@ -10,12 +10,51 @@ const { getRewriteFunction } = require('./rewriter-telemetry')
|
|
|
10
10
|
|
|
11
11
|
let rewriter
|
|
12
12
|
let getPrepareStackTrace
|
|
13
|
+
|
|
14
|
+
let getRewriterOriginalPathAndLineFromSourceMap = function (path, line, column) {
|
|
15
|
+
return { path, line, column }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isFlagPresent (flag) {
|
|
19
|
+
return process.env.NODE_OPTIONS?.includes(flag) ||
|
|
20
|
+
process.execArgv?.some(arg => arg.includes(flag))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getGetOriginalPathAndLineFromSourceMapFunction (chainSourceMap, getOriginalPathAndLineFromSourceMap) {
|
|
24
|
+
if (chainSourceMap) {
|
|
25
|
+
return function (path, line, column) {
|
|
26
|
+
// if --enable-source-maps is present stacktraces of the rewritten files contain the original path, file and
|
|
27
|
+
// column because the sourcemap chaining is done during the rewriting process so we can skip it
|
|
28
|
+
if (isPrivateModule(path) && isNotLibraryFile(path)) {
|
|
29
|
+
return { path, line, column }
|
|
30
|
+
} else {
|
|
31
|
+
return getOriginalPathAndLineFromSourceMap(path, line, column)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
return getOriginalPathAndLineFromSourceMap
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
function getRewriter (telemetryVerbosity) {
|
|
14
40
|
if (!rewriter) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
try {
|
|
42
|
+
const iastRewriter = require('@datadog/native-iast-rewriter')
|
|
43
|
+
const Rewriter = iastRewriter.Rewriter
|
|
44
|
+
getPrepareStackTrace = iastRewriter.getPrepareStackTrace
|
|
45
|
+
|
|
46
|
+
const chainSourceMap = isFlagPresent('--enable-source-maps')
|
|
47
|
+
const getOriginalPathAndLineFromSourceMap = iastRewriter.getOriginalPathAndLineFromSourceMap
|
|
48
|
+
if (getOriginalPathAndLineFromSourceMap) {
|
|
49
|
+
getRewriterOriginalPathAndLineFromSourceMap =
|
|
50
|
+
getGetOriginalPathAndLineFromSourceMapFunction(chainSourceMap, getOriginalPathAndLineFromSourceMap)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
rewriter = new Rewriter({ csiMethods, telemetryVerbosity: getName(telemetryVerbosity), chainSourceMap })
|
|
54
|
+
} catch (e) {
|
|
55
|
+
iastLog.error('Unable to initialize TaintTracking Rewriter')
|
|
56
|
+
.errorAndPublish(e)
|
|
57
|
+
}
|
|
19
58
|
}
|
|
20
59
|
return rewriter
|
|
21
60
|
}
|
|
@@ -74,6 +113,10 @@ function disableRewriter () {
|
|
|
74
113
|
Error.prepareStackTrace = originalPrepareStackTrace
|
|
75
114
|
}
|
|
76
115
|
|
|
116
|
+
function getOriginalPathAndLineFromSourceMap ({ path, line, column }) {
|
|
117
|
+
return getRewriterOriginalPathAndLineFromSourceMap(path, line, column)
|
|
118
|
+
}
|
|
119
|
+
|
|
77
120
|
module.exports = {
|
|
78
|
-
enableRewriter, disableRewriter
|
|
121
|
+
enableRewriter, disableRewriter, getOriginalPathAndLineFromSourceMap
|
|
79
122
|
}
|
|
@@ -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
|
}
|