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.
Files changed (78) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +4 -9
  3. package/ext/tags.d.ts +1 -0
  4. package/ext/tags.js +1 -0
  5. package/index.d.ts +44 -0
  6. package/package.json +9 -6
  7. package/packages/datadog-esbuild/index.js +57 -32
  8. package/packages/datadog-instrumentations/src/body-parser.js +2 -2
  9. package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +30 -11
  11. package/packages/datadog-instrumentations/src/express.js +1 -1
  12. package/packages/datadog-instrumentations/src/graphql.js +10 -4
  13. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  14. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +22 -11
  16. package/packages/datadog-instrumentations/src/kafkajs.js +3 -4
  17. package/packages/datadog-instrumentations/src/mocha.js +33 -8
  18. package/packages/datadog-instrumentations/src/mysql.js +39 -1
  19. package/packages/datadog-instrumentations/src/next.js +47 -19
  20. package/packages/datadog-instrumentations/src/openai.js +1 -1
  21. package/packages/datadog-instrumentations/src/pg.js +60 -15
  22. package/packages/datadog-instrumentations/src/playwright.js +15 -3
  23. package/packages/datadog-plugin-cucumber/src/index.js +14 -2
  24. package/packages/datadog-plugin-cypress/src/plugin.js +49 -13
  25. package/packages/datadog-plugin-graphql/src/index.js +3 -3
  26. package/packages/datadog-plugin-graphql/src/resolve.js +27 -2
  27. package/packages/datadog-plugin-jest/src/index.js +10 -2
  28. package/packages/datadog-plugin-jest/src/util.js +10 -4
  29. package/packages/datadog-plugin-mocha/src/index.js +14 -2
  30. package/packages/datadog-plugin-mongodb-core/src/index.js +6 -2
  31. package/packages/datadog-plugin-mysql/src/index.js +2 -2
  32. package/packages/datadog-plugin-next/src/index.js +22 -5
  33. package/packages/datadog-plugin-pg/src/index.js +2 -2
  34. package/packages/dd-trace/src/appsec/addresses.js +1 -0
  35. package/packages/dd-trace/src/appsec/channels.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +7 -0
  37. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +29 -18
  38. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
  39. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
  42. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +131 -10
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
  45. package/packages/dd-trace/src/appsec/index.js +42 -7
  46. package/packages/dd-trace/src/appsec/recommended.json +655 -31
  47. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  48. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  49. package/packages/dd-trace/src/appsec/reporter.js +26 -0
  50. package/packages/dd-trace/src/appsec/telemetry.js +132 -0
  51. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  52. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +13 -5
  53. package/packages/dd-trace/src/appsec/waf/waf_manager.js +12 -14
  54. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
  55. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
  56. package/packages/dd-trace/src/datastreams/processor.js +6 -2
  57. package/packages/dd-trace/src/dogstatsd.js +108 -8
  58. package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
  59. package/packages/dd-trace/src/exporters/common/request.js +13 -4
  60. package/packages/dd-trace/src/format.js +6 -1
  61. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
  62. package/packages/dd-trace/src/opentracing/span.js +13 -13
  63. package/packages/dd-trace/src/opentracing/tracer.js +3 -5
  64. package/packages/dd-trace/src/plugin_manager.js +1 -2
  65. package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
  66. package/packages/dd-trace/src/plugins/database.js +14 -4
  67. package/packages/dd-trace/src/plugins/index.js +1 -0
  68. package/packages/dd-trace/src/plugins/outbound.js +4 -3
  69. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  70. package/packages/dd-trace/src/plugins/util/test.js +20 -3
  71. package/packages/dd-trace/src/profiling/config.js +3 -1
  72. package/packages/dd-trace/src/profiling/profilers/wall.js +31 -7
  73. package/packages/dd-trace/src/proxy.js +13 -2
  74. package/packages/dd-trace/src/ritm.js +10 -2
  75. package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +1 -32
  76. package/packages/dd-trace/src/telemetry/dependencies.js +15 -0
  77. package/packages/dd-trace/src/telemetry/index.js +21 -2
  78. 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` and haven't been skipped by ITR
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 !isSkippedByItr && !isTestFinished
282
+ return !isTestFinished
261
283
  }).forEach(({ title }) => {
262
- const skippedTestSpan = getTestSpan(title.join(' '), spec.relative)
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
- if (tracer._tracer._exporter.flush) {
324
- tracer._tracer._exporter.flush(() => {
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
- tracer._tracer._exporter._writer.flush(() => {
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.exportCoverage && isCodeCoverageEnabled) {
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 = (config.hooks && config.hooks.execute) || noop
60
- const parse = (config.hooks && config.hooks.parse) || noop
61
- const validate = (config.hooks && config.hooks.validate) || noop
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
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
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.filter(({ path: testPath }) => {
52
- const relativePath = getTestSuitePath(testPath, rootDir)
53
- return !skippableSuites.includes(relativePath)
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', ({ status, isSuitesSkipped, testCodeCoverageLinesTotal }) => {
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
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
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 JSON.stringify(limitDepth(cmd.query))
42
- if (cmd.filter) return JSON.stringify(limitDepth(cmd.filter))
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
- start ({ req, res }) {
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 && (page === '/404' || page === '/500' || page === '/_error')) {
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('datadog:sequelize:query:start', ({ sql, dialect }) => {
24
- const parentStore = storage.getStore()
25
- if (parentStore) {
26
- this.analyze(sql, dialect.toUpperCase())
27
-
28
- storage.enterWith({ ...parentStore, sqlAnalyzed: true, sequelizeParentStore: parentStore })
29
- }
30
- })
31
-
32
- this.addSub('datadog:sequelize:query:finish', () => {
33
- const store = storage.getStore()
34
- if (store && store.sequelizeParentStore) {
35
- storage.enterWith(store.sequelizeParentStore)
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, location)
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[property] = tainted
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
- const iastRewriter = require('@datadog/native-iast-rewriter')
16
- const Rewriter = iastRewriter.Rewriter
17
- getPrepareStackTrace = iastRewriter.getPrepareStackTrace
18
- rewriter = new Rewriter({ csiMethods, telemetryVerbosity: getName(telemetryVerbosity) })
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
- // in order to telemetry be enabled, tracer telemetry and metrics collection have to be enabled
11
- this.enabled = config && config.telemetry && config.telemetry.enabled && config.telemetry.metrics
12
- this.verbosity = this.enabled ? getVerbosity(verbosity) : Verbosity.OFF
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() && this.verbosity !== Verbosity.OFF) {
42
+ if (this.isEnabled()) {
34
43
  initRequestNamespace(context)
35
44
  }
36
45
  }
37
46
 
38
47
  onRequestEnd (context, rootSpan) {
39
- if (this.isEnabled() && this.verbosity !== Verbosity.OFF) {
48
+ if (this.isEnabled()) {
40
49
  finalizeRequestNamespace(context, rootSpan)
41
50
  }
42
51
  }