dd-trace 5.28.0 → 5.29.1

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 (148) hide show
  1. package/LICENSE-3rdparty.csv +8 -2
  2. package/ci/init.js +16 -0
  3. package/index.d.ts +31 -13
  4. package/init.js +4 -68
  5. package/loader-hook.mjs +4 -0
  6. package/package.json +16 -11
  7. package/packages/datadog-core/src/storage.js +39 -2
  8. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  9. package/packages/datadog-instrumentations/src/cucumber.js +29 -3
  10. package/packages/datadog-instrumentations/src/express.js +38 -4
  11. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +3 -3
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -1
  13. package/packages/datadog-instrumentations/src/helpers/register.js +3 -4
  14. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +27 -8
  16. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  17. package/packages/datadog-instrumentations/src/mysql2.js +13 -8
  18. package/packages/datadog-instrumentations/src/next.js +7 -4
  19. package/packages/datadog-instrumentations/src/passport-http.js +2 -14
  20. package/packages/datadog-instrumentations/src/passport-local.js +2 -14
  21. package/packages/datadog-instrumentations/src/passport-utils.js +43 -19
  22. package/packages/datadog-instrumentations/src/pg.js +6 -6
  23. package/packages/datadog-instrumentations/src/playwright.js +17 -4
  24. package/packages/datadog-instrumentations/src/router.js +97 -1
  25. package/packages/datadog-instrumentations/src/sequelize.js +9 -4
  26. package/packages/datadog-instrumentations/src/url.js +4 -0
  27. package/packages/datadog-instrumentations/src/vitest.js +27 -2
  28. package/packages/datadog-plugin-avsc/src/schema_iterator.js +8 -3
  29. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +154 -0
  30. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
  31. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  32. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  33. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  34. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  35. package/packages/datadog-plugin-aws-sdk/src/util.js +92 -0
  36. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
  37. package/packages/datadog-plugin-cucumber/src/index.js +39 -4
  38. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -3
  39. package/packages/datadog-plugin-grpc/src/client.js +2 -2
  40. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  41. package/packages/datadog-plugin-jest/src/index.js +39 -4
  42. package/packages/datadog-plugin-mocha/src/index.js +36 -2
  43. package/packages/datadog-plugin-oracledb/src/index.js +1 -1
  44. package/packages/datadog-plugin-vitest/src/index.js +34 -2
  45. package/packages/datadog-shimmer/src/shimmer.js +8 -4
  46. package/packages/dd-trace/src/appsec/addresses.js +3 -0
  47. package/packages/dd-trace/src/appsec/blocked_templates.js +1 -1
  48. package/packages/dd-trace/src/appsec/channels.js +1 -0
  49. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +4 -0
  50. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
  51. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +1 -1
  52. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +1 -1
  53. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +10 -3
  54. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +4 -0
  55. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +4 -0
  56. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +6 -19
  57. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
  58. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +64 -3
  59. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  60. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  62. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +32 -37
  63. package/packages/dd-trace/src/appsec/index.js +16 -10
  64. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
  65. package/packages/dd-trace/src/appsec/remote_config/index.js +25 -1
  66. package/packages/dd-trace/src/appsec/reporter.js +3 -1
  67. package/packages/dd-trace/src/appsec/sdk/track_event.js +32 -19
  68. package/packages/dd-trace/src/appsec/telemetry.js +10 -0
  69. package/packages/dd-trace/src/appsec/user_tracking.js +168 -0
  70. package/packages/dd-trace/src/azure_metadata.js +4 -4
  71. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +5 -4
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +39 -3
  73. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  74. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -9
  78. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  79. package/packages/dd-trace/src/config.js +24 -32
  80. package/packages/dd-trace/src/constants.js +1 -0
  81. package/packages/dd-trace/src/crashtracking/crashtracker.js +3 -2
  82. package/packages/dd-trace/src/datastreams/processor.js +4 -6
  83. package/packages/dd-trace/src/datastreams/writer.js +6 -5
  84. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +80 -0
  85. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
  86. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +6 -0
  87. package/packages/dd-trace/src/debugger/devtools_client/index.js +63 -8
  88. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +10 -67
  89. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  90. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -1
  91. package/packages/dd-trace/src/debugger/devtools_client/status.js +4 -4
  92. package/packages/dd-trace/src/debugger/index.js +14 -10
  93. package/packages/dd-trace/src/dogstatsd.js +2 -2
  94. package/packages/dd-trace/src/encode/0.4.js +23 -78
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +0 -32
  96. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +1 -2
  97. package/packages/dd-trace/src/encode/span-stats.js +0 -30
  98. package/packages/dd-trace/src/exporters/agent/writer.js +3 -3
  99. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  100. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -1
  101. package/packages/dd-trace/src/flare/index.js +1 -1
  102. package/packages/dd-trace/src/guardrails/index.js +64 -0
  103. package/packages/dd-trace/src/guardrails/log.js +32 -0
  104. package/packages/dd-trace/src/guardrails/telemetry.js +78 -0
  105. package/packages/dd-trace/src/guardrails/util.js +10 -0
  106. package/packages/dd-trace/src/lambda/runtime/ritm.js +2 -2
  107. package/packages/dd-trace/src/llmobs/storage.js +2 -3
  108. package/packages/dd-trace/src/llmobs/writers/base.js +2 -2
  109. package/packages/dd-trace/src/log/channels.js +9 -2
  110. package/packages/dd-trace/src/log/index.js +11 -1
  111. package/packages/dd-trace/src/log/writer.js +14 -3
  112. package/packages/dd-trace/src/{encode → msgpack}/chunk.js +8 -5
  113. package/packages/dd-trace/src/msgpack/encoder.js +309 -0
  114. package/packages/dd-trace/src/msgpack/index.js +6 -0
  115. package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -2
  116. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -9
  117. package/packages/dd-trace/src/opentracing/span.js +1 -1
  118. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  119. package/packages/dd-trace/src/plugin_manager.js +4 -2
  120. package/packages/dd-trace/src/plugins/ci_plugin.js +47 -4
  121. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  122. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  123. package/packages/dd-trace/src/plugins/util/git.js +7 -7
  124. package/packages/dd-trace/src/plugins/util/test.js +36 -3
  125. package/packages/dd-trace/src/plugins/util/web.js +2 -2
  126. package/packages/dd-trace/src/priority_sampler.js +11 -1
  127. package/packages/dd-trace/src/profiling/config.js +3 -0
  128. package/packages/dd-trace/src/profiling/exporters/agent.js +9 -68
  129. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +76 -0
  130. package/packages/dd-trace/src/profiling/exporters/file.js +8 -4
  131. package/packages/dd-trace/src/profiling/profiler.js +62 -10
  132. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +22 -12
  133. package/packages/dd-trace/src/profiling/profilers/events.js +47 -8
  134. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -17
  135. package/packages/dd-trace/src/profiling/webspan-utils.js +23 -0
  136. package/packages/dd-trace/src/proxy.js +7 -2
  137. package/packages/dd-trace/src/runtime_metrics.js +107 -4
  138. package/packages/dd-trace/src/serverless.js +1 -1
  139. package/packages/dd-trace/src/span_processor.js +10 -10
  140. package/packages/dd-trace/src/tagger.js +1 -1
  141. package/packages/dd-trace/src/telemetry/index.js +1 -0
  142. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  143. package/packages/dd-trace/src/telemetry/logs/log-collector.js +10 -2
  144. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  145. package/packages/dd-trace/src/util.js +5 -16
  146. package/packages/datadog-instrumentations/src/qs.js +0 -24
  147. package/packages/dd-trace/src/appsec/passport.js +0 -110
  148. package/packages/dd-trace/src/telemetry/init-telemetry.js +0 -75
@@ -17,7 +17,12 @@ const {
17
17
  TEST_SOURCE_START,
18
18
  TEST_IS_NEW,
19
19
  TEST_EARLY_FLAKE_ENABLED,
20
- TEST_EARLY_FLAKE_ABORT_REASON
20
+ TEST_EARLY_FLAKE_ABORT_REASON,
21
+ TEST_NAME,
22
+ DI_ERROR_DEBUG_INFO_CAPTURED,
23
+ DI_DEBUG_ERROR_SNAPSHOT_ID,
24
+ DI_DEBUG_ERROR_FILE,
25
+ DI_DEBUG_ERROR_LINE
21
26
  } = require('../../dd-trace/src/plugins/util/test')
22
27
  const { COMPONENT } = require('../../dd-trace/src/constants')
23
28
  const {
@@ -31,6 +36,8 @@ const {
31
36
  // This is because there's some loss of resolution.
32
37
  const MILLISECONDS_TO_SUBTRACT_FROM_FAILED_TEST_DURATION = 5
33
38
 
39
+ const debuggerParameterPerTest = new Map()
40
+
34
41
  class VitestPlugin extends CiPlugin {
35
42
  static get id () {
36
43
  return 'vitest'
@@ -81,6 +88,26 @@ class VitestPlugin extends CiPlugin {
81
88
  extraTags
82
89
  )
83
90
 
91
+ const debuggerParameters = debuggerParameterPerTest.get(testName)
92
+
93
+ if (debuggerParameters) {
94
+ const spanContext = span.context()
95
+
96
+ // TODO: handle race conditions with this.retriedTestIds
97
+ this.retriedTestIds = {
98
+ spanId: spanContext.toSpanId(),
99
+ traceId: spanContext.toTraceId()
100
+ }
101
+ const { snapshotId, file, line } = debuggerParameters
102
+
103
+ // TODO: should these be added on test:end if and only if the probe is hit?
104
+ // Sync issues: `hitProbePromise` might be resolved after the test ends
105
+ span.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
106
+ span.setTag(DI_DEBUG_ERROR_SNAPSHOT_ID, snapshotId)
107
+ span.setTag(DI_DEBUG_ERROR_FILE, file)
108
+ span.setTag(DI_DEBUG_ERROR_LINE, line)
109
+ }
110
+
84
111
  this.enter(span, store)
85
112
  })
86
113
 
@@ -110,11 +137,16 @@ class VitestPlugin extends CiPlugin {
110
137
  }
111
138
  })
112
139
 
113
- this.addSub('ci:vitest:test:error', ({ duration, error }) => {
140
+ this.addSub('ci:vitest:test:error', ({ duration, error, willBeRetried, probe, isDiEnabled }) => {
114
141
  const store = storage.getStore()
115
142
  const span = store?.span
116
143
 
117
144
  if (span) {
145
+ if (willBeRetried && this.di && isDiEnabled) {
146
+ const testName = span.context()._tags[TEST_NAME]
147
+ const debuggerParameters = this.addDiProbe(error, probe)
148
+ debuggerParameterPerTest.set(testName, debuggerParameters)
149
+ }
118
150
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
119
151
  hasCodeowners: !!span.context()._tags[TEST_CODE_OWNERS]
120
152
  })
@@ -6,8 +6,12 @@ const log = require('../../dd-trace/src/log')
6
6
  const unwrappers = new WeakMap()
7
7
 
8
8
  function copyProperties (original, wrapped) {
9
- Object.setPrototypeOf(wrapped, original)
10
-
9
+ // TODO getPrototypeOf is not fast. Should we instead do this in specific
10
+ // instrumentations where needed?
11
+ const proto = Object.getPrototypeOf(original)
12
+ if (proto !== Function.prototype) {
13
+ Object.setPrototypeOf(wrapped, proto)
14
+ }
11
15
  const props = Object.getOwnPropertyDescriptors(original)
12
16
  const keys = Reflect.ownKeys(props)
13
17
 
@@ -136,7 +140,7 @@ function wrapMethod (target, name, wrapper, noAssert) {
136
140
  if (callState.completed) {
137
141
  // error was thrown after original function returned/resolved, so
138
142
  // it was us. log it.
139
- log.error(e)
143
+ log.error('Shimmer error was thrown after original function returned/resolved', e)
140
144
  // original ran and returned something. return it.
141
145
  return callState.retVal
142
146
  }
@@ -144,7 +148,7 @@ function wrapMethod (target, name, wrapper, noAssert) {
144
148
  if (!callState.called) {
145
149
  // error was thrown before original function was called, so
146
150
  // it was us. log it.
147
- log.error(e)
151
+ log.error('Shimmer error was thrown before original function was called', e)
148
152
  // original never ran. call it unwrapped.
149
153
  return original.apply(this, args)
150
154
  }
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ // TODO: reorder all this, it's a mess
3
4
  module.exports = {
4
5
  HTTP_INCOMING_BODY: 'server.request.body',
5
6
  HTTP_INCOMING_QUERY: 'server.request.query',
@@ -20,6 +21,8 @@ module.exports = {
20
21
  HTTP_CLIENT_IP: 'http.client_ip',
21
22
 
22
23
  USER_ID: 'usr.id',
24
+ USER_LOGIN: 'usr.login',
25
+
23
26
  WAF_CONTEXT_PROCESSOR: 'waf.context.processor',
24
27
 
25
28
  HTTP_OUTGOING_URL: 'server.io.net.url',
@@ -1,4 +1,4 @@
1
- /* eslint-disable max-len */
1
+ /* eslint-disable @stylistic/js/max-len */
2
2
  'use strict'
3
3
 
4
4
  const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>`
@@ -19,6 +19,7 @@ module.exports = {
19
19
  nextBodyParsed: dc.channel('apm:next:body-parsed'),
20
20
  nextQueryParsed: dc.channel('apm:next:query-parsed'),
21
21
  expressProcessParams: dc.channel('datadog:express:process_params:start'),
22
+ routerParam: dc.channel('datadog:router:param:start'),
22
23
  responseBody: dc.channel('datadog:express:response:json:start'),
23
24
  responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'),
24
25
  httpClientRequestStart: dc.channel('apm:http:client:request:start'),
@@ -11,6 +11,10 @@ class CodeInjectionAnalyzer extends InjectionAnalyzer {
11
11
  onConfigure () {
12
12
  this.addSub('datadog:eval:call', ({ script }) => this.analyze(script))
13
13
  }
14
+
15
+ _areRangesVulnerable () {
16
+ return true
17
+ }
14
18
  }
15
19
 
16
20
  module.exports = new CodeInjectionAnalyzer()
@@ -1,4 +1,4 @@
1
- /* eslint-disable max-len */
1
+ /* eslint-disable @stylistic/js/max-len */
2
2
  'use strict'
3
3
 
4
4
  const { NameAndValue } = require('./hardcoded-rule-type')
@@ -1,4 +1,4 @@
1
- /* eslint-disable max-len */
1
+ /* eslint-disable @stylistic/js/max-len */
2
2
  'use strict'
3
3
 
4
4
  const { ValueOnly, NameAndValue } = require('./hardcoded-rule-type')
@@ -1,4 +1,4 @@
1
- /* eslint-disable max-len */
1
+ /* eslint-disable @stylistic/js/max-len */
2
2
  'use strict'
3
3
 
4
4
  const { ValueOnly, NameAndValue } = require('./hardcoded-rule-type')
@@ -1,12 +1,15 @@
1
1
  'use strict'
2
2
  const Analyzer = require('./vulnerability-analyzer')
3
- const { isTainted, getRanges } = require('../taint-tracking/operations')
3
+ const { getRanges } = require('../taint-tracking/operations')
4
+ const { SQL_ROW_VALUE } = require('../taint-tracking/source-types')
4
5
 
5
6
  class InjectionAnalyzer extends Analyzer {
6
7
  _isVulnerable (value, iastContext) {
7
- if (value) {
8
- return isTainted(iastContext, value)
8
+ const ranges = value && getRanges(iastContext, value)
9
+ if (ranges?.length > 0) {
10
+ return this._areRangesVulnerable(ranges)
9
11
  }
12
+
10
13
  return false
11
14
  }
12
15
 
@@ -14,6 +17,10 @@ class InjectionAnalyzer extends Analyzer {
14
17
  const ranges = getRanges(iastContext, value)
15
18
  return { value, ranges }
16
19
  }
20
+
21
+ _areRangesVulnerable (ranges) {
22
+ return ranges?.some(range => range.iinfo.type !== SQL_ROW_VALUE)
23
+ }
17
24
  }
18
25
 
19
26
  module.exports = InjectionAnalyzer
@@ -82,6 +82,10 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
82
82
  return knexDialect.toUpperCase()
83
83
  }
84
84
  }
85
+
86
+ _areRangesVulnerable () {
87
+ return true
88
+ }
85
89
  }
86
90
 
87
91
  module.exports = new SqlInjectionAnalyzer()
@@ -13,6 +13,10 @@ class TemplateInjectionAnalyzer extends InjectionAnalyzer {
13
13
  this.addSub('datadog:handlebars:register-partial:start', ({ partial }) => this.analyze(partial))
14
14
  this.addSub('datadog:pug:compile:start', ({ source }) => this.analyze(source))
15
15
  }
16
+
17
+ _areRangesVulnerable () {
18
+ return true
19
+ }
16
20
  }
17
21
 
18
22
  module.exports = new TemplateInjectionAnalyzer()
@@ -60,24 +60,10 @@ class IastPlugin extends Plugin {
60
60
  this.pluginSubs = []
61
61
  }
62
62
 
63
- _wrapHandler (handler) {
64
- return (message, name) => {
65
- try {
66
- handler(message, name)
67
- } catch (e) {
68
- log.error('[ASM] Error executing IAST plugin handler', e)
69
- }
70
- }
71
- }
72
-
73
63
  _getTelemetryHandler (iastSub) {
74
64
  return () => {
75
- try {
76
- const iastContext = getIastContext(storage.getStore())
77
- iastSub.increaseExecuted(iastContext)
78
- } catch (e) {
79
- log.error('[ASM] Error increasing handler executed metrics', e)
80
- }
65
+ const iastContext = getIastContext(storage.getStore())
66
+ iastSub.increaseExecuted(iastContext)
81
67
  }
82
68
  }
83
69
 
@@ -99,11 +85,11 @@ class IastPlugin extends Plugin {
99
85
 
100
86
  addSub (iastSub, handler) {
101
87
  if (typeof iastSub === 'string') {
102
- super.addSub(iastSub, this._wrapHandler(handler))
88
+ super.addSub(iastSub, handler)
103
89
  } else {
104
90
  iastSub = this._getAndRegisterSubscription(iastSub)
105
91
  if (iastSub) {
106
- super.addSub(iastSub.channelName, this._wrapHandler(handler))
92
+ super.addSub(iastSub.channelName, handler)
107
93
 
108
94
  if (iastTelemetry.isEnabled()) {
109
95
  super.addSub(iastSub.channelName, this._getTelemetryHandler(iastSub))
@@ -112,7 +98,8 @@ class IastPlugin extends Plugin {
112
98
  }
113
99
  }
114
100
 
115
- enable () {
101
+ enable (iastConfig) {
102
+ this.iastConfig = iastConfig
116
103
  this.configure(true)
117
104
  }
118
105
 
@@ -18,10 +18,10 @@ module.exports = {
18
18
  enableTaintTracking (config, telemetryVerbosity) {
19
19
  enableRewriter(telemetryVerbosity)
20
20
  enableTaintOperations(telemetryVerbosity)
21
- taintTrackingPlugin.enable()
21
+ taintTrackingPlugin.enable(config)
22
22
 
23
- kafkaContextPlugin.enable()
24
- kafkaConsumerPlugin.enable()
23
+ kafkaContextPlugin.enable(config)
24
+ kafkaConsumerPlugin.enable(config)
25
25
 
26
26
  setMaxTransactions(config.maxConcurrentRequests)
27
27
  },
@@ -12,7 +12,8 @@ const {
12
12
  HTTP_REQUEST_HEADER_NAME,
13
13
  HTTP_REQUEST_PARAMETER,
14
14
  HTTP_REQUEST_PATH_PARAM,
15
- HTTP_REQUEST_URI
15
+ HTTP_REQUEST_URI,
16
+ SQL_ROW_VALUE
16
17
  } = require('./source-types')
17
18
  const { EXECUTED_SOURCE } = require('../telemetry/iast-metric')
18
19
 
@@ -26,6 +27,16 @@ class TaintTrackingPlugin extends SourceIastPlugin {
26
27
  this._taintedURLs = new WeakMap()
27
28
  }
28
29
 
30
+ configure (config) {
31
+ super.configure(config)
32
+
33
+ let rowsToTaint = this.iastConfig?.dbRowsToTaint
34
+ if (typeof rowsToTaint !== 'number') {
35
+ rowsToTaint = 1
36
+ }
37
+ this._rowsToTaint = rowsToTaint
38
+ }
39
+
29
40
  onConfigure () {
30
41
  const onRequestBody = ({ req }) => {
31
42
  const iastContext = getIastContext(storage.getStore())
@@ -46,8 +57,13 @@ class TaintTrackingPlugin extends SourceIastPlugin {
46
57
  )
47
58
 
48
59
  this.addSub(
49
- { channelName: 'datadog:qs:parse:finish', tag: HTTP_REQUEST_PARAMETER },
50
- ({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs)
60
+ { channelName: 'datadog:query:read:finish', tag: HTTP_REQUEST_PARAMETER },
61
+ ({ query }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, query)
62
+ )
63
+
64
+ this.addSub(
65
+ { channelName: 'datadog:express:query:finish', tag: HTTP_REQUEST_PARAMETER },
66
+ ({ query }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, query)
51
67
  )
52
68
 
53
69
  this.addSub(
@@ -68,6 +84,16 @@ class TaintTrackingPlugin extends SourceIastPlugin {
68
84
  ({ cookies }) => this._cookiesTaintTrackingHandler(cookies)
69
85
  )
70
86
 
87
+ this.addSub(
88
+ { channelName: 'datadog:sequelize:query:finish', tag: SQL_ROW_VALUE },
89
+ ({ result }) => this._taintDatabaseResult(result, 'sequelize')
90
+ )
91
+
92
+ this.addSub(
93
+ { channelName: 'apm:pg:query:finish', tag: SQL_ROW_VALUE },
94
+ ({ result }) => this._taintDatabaseResult(result, 'pg')
95
+ )
96
+
71
97
  this.addSub(
72
98
  { channelName: 'datadog:express:process_params:start', tag: HTTP_REQUEST_PATH_PARAM },
73
99
  ({ req }) => {
@@ -77,6 +103,15 @@ class TaintTrackingPlugin extends SourceIastPlugin {
77
103
  }
78
104
  )
79
105
 
106
+ this.addSub(
107
+ { channelName: 'datadog:router:param:start', tag: HTTP_REQUEST_PATH_PARAM },
108
+ ({ req }) => {
109
+ if (req && req.params !== null && typeof req.params === 'object') {
110
+ this._taintTrackingHandler(HTTP_REQUEST_PATH_PARAM, req, 'params')
111
+ }
112
+ }
113
+ )
114
+
80
115
  this.addSub(
81
116
  { channelName: 'apm:graphql:resolve:start', tag: HTTP_REQUEST_BODY },
82
117
  (data) => {
@@ -170,6 +205,32 @@ class TaintTrackingPlugin extends SourceIastPlugin {
170
205
  this.taintHeaders(req.headers, iastContext)
171
206
  this.taintUrl(req, iastContext)
172
207
  }
208
+
209
+ _taintDatabaseResult (result, dbOrigin, iastContext = getIastContext(storage.getStore()), name) {
210
+ if (!iastContext) return result
211
+
212
+ if (this._rowsToTaint === 0) return result
213
+
214
+ if (Array.isArray(result)) {
215
+ for (let i = 0; i < result.length && i < this._rowsToTaint; i++) {
216
+ const nextName = name ? `${name}.${i}` : '' + i
217
+ result[i] = this._taintDatabaseResult(result[i], dbOrigin, iastContext, nextName)
218
+ }
219
+ } else if (result && typeof result === 'object') {
220
+ if (dbOrigin === 'sequelize' && result.dataValues) {
221
+ result.dataValues = this._taintDatabaseResult(result.dataValues, dbOrigin, iastContext, name)
222
+ } else {
223
+ for (const key in result) {
224
+ const nextName = name ? `${name}.${key}` : key
225
+ result[key] = this._taintDatabaseResult(result[key], dbOrigin, iastContext, nextName)
226
+ }
227
+ }
228
+ } else if (typeof result === 'string') {
229
+ result = newTaintedString(iastContext, result, name, SQL_ROW_VALUE)
230
+ }
231
+
232
+ return result
233
+ }
173
234
  }
174
235
 
175
236
  module.exports = new TaintTrackingPlugin()
@@ -11,5 +11,6 @@ module.exports = {
11
11
  HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter',
12
12
  HTTP_REQUEST_URI: 'http.request.uri',
13
13
  KAFKA_MESSAGE_KEY: 'kafka.message.key',
14
- KAFKA_MESSAGE_VALUE: 'kafka.message.value'
14
+ KAFKA_MESSAGE_VALUE: 'kafka.message.value',
15
+ SQL_ROW_VALUE: 'sql.row.value'
15
16
  }
@@ -1,6 +1,6 @@
1
- // eslint-disable-next-line max-len
1
+ // eslint-disable-next-line @stylistic/js/max-len
2
2
  const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|(?:sur|last)name|user(?:name)?|address|e?mail)'
3
- // eslint-disable-next-line max-len
3
+ // eslint-disable-next-line @stylistic/js/max-len
4
4
  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,})|[\\w\\.-]+@[a-zA-Z\\d\\.-]+\\.[a-zA-Z]{2,})'
5
5
 
6
6
  module.exports = {
@@ -7,7 +7,7 @@ const STRINGIFY_RANGE_KEY = 'DD_' + crypto.randomBytes(20).toString('hex')
7
7
  const STRINGIFY_SENSITIVE_KEY = STRINGIFY_RANGE_KEY + 'SENSITIVE'
8
8
  const STRINGIFY_SENSITIVE_NOT_STRING_KEY = STRINGIFY_SENSITIVE_KEY + 'NOTSTRING'
9
9
 
10
- // eslint-disable-next-line max-len
10
+ // eslint-disable-next-line @stylistic/js/max-len
11
11
  const KEYS_REGEX_WITH_SENSITIVE_RANGES = new RegExp(`(?:"(${STRINGIFY_RANGE_KEY}_\\d+_))|(?:"(${STRINGIFY_SENSITIVE_KEY}_\\d+_(\\d+)_))|("${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_\\d+_([\\s0-9.a-zA-Z]*)")`, 'gm')
12
12
  const KEYS_REGEX_WITHOUT_SENSITIVE_RANGES = new RegExp(`"(${STRINGIFY_RANGE_KEY}_\\d+_)`, 'gm')
13
13
 
@@ -17,13 +17,36 @@ let resetVulnerabilityCacheTimer
17
17
  let deduplicationEnabled = true
18
18
 
19
19
  function addVulnerability (iastContext, vulnerability) {
20
- if (vulnerability && vulnerability.evidence && vulnerability.type &&
21
- vulnerability.location) {
22
- if (iastContext && iastContext.rootSpan) {
20
+ if (vulnerability?.evidence && vulnerability?.type && vulnerability?.location) {
21
+ if (deduplicationEnabled && isDuplicatedVulnerability(vulnerability)) return
22
+
23
+ VULNERABILITY_HASHES.set(`${vulnerability.type}${vulnerability.hash}`, true)
24
+
25
+ let span = iastContext?.rootSpan
26
+
27
+ if (!span && tracer) {
28
+ span = tracer.startSpan('vulnerability', {
29
+ type: 'vulnerability'
30
+ })
31
+
32
+ vulnerability.location.spanId = span.context().toSpanId()
33
+
34
+ span.addTags({
35
+ [IAST_ENABLED_TAG_KEY]: 1
36
+ })
37
+ }
38
+
39
+ if (!span) return
40
+
41
+ keepTrace(span, SAMPLING_MECHANISM_APPSEC)
42
+ standalone.sample(span)
43
+
44
+ if (iastContext?.rootSpan) {
23
45
  iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
24
46
  iastContext[VULNERABILITIES_KEY].push(vulnerability)
25
47
  } else {
26
- sendVulnerabilities([vulnerability])
48
+ sendVulnerabilities([vulnerability], span)
49
+ span.finish()
27
50
  }
28
51
  }
29
52
  }
@@ -34,36 +57,17 @@ function isValidVulnerability (vulnerability) {
34
57
  vulnerability.location && vulnerability.location.spanId
35
58
  }
36
59
 
37
- function sendVulnerabilities (vulnerabilities, rootSpan) {
60
+ function sendVulnerabilities (vulnerabilities, span) {
38
61
  if (vulnerabilities && vulnerabilities.length) {
39
- let span = rootSpan
40
- if (!span && tracer) {
41
- span = tracer.startSpan('vulnerability', {
42
- type: 'vulnerability'
43
- })
44
- vulnerabilities.forEach((vulnerability) => {
45
- vulnerability.location.spanId = span.context().toSpanId()
46
- })
47
- span.addTags({
48
- [IAST_ENABLED_TAG_KEY]: 1
49
- })
50
- }
51
-
52
62
  if (span && span.addTags) {
53
- const validAndDedupVulnerabilities = deduplicateVulnerabilities(vulnerabilities).filter(isValidVulnerability)
54
- const jsonToSend = vulnerabilitiesFormatter.toJson(validAndDedupVulnerabilities)
63
+ const validatedVulnerabilities = vulnerabilities.filter(isValidVulnerability)
64
+ const jsonToSend = vulnerabilitiesFormatter.toJson(validatedVulnerabilities)
55
65
 
56
66
  if (jsonToSend.vulnerabilities.length > 0) {
57
67
  const tags = {}
58
68
  // TODO: Store this outside of the span and set the tag in the exporter.
59
69
  tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
60
70
  span.addTags(tags)
61
-
62
- keepTrace(span, SAMPLING_MECHANISM_APPSEC)
63
-
64
- standalone.sample(span)
65
-
66
- if (!rootSpan) span.finish()
67
71
  }
68
72
  }
69
73
  }
@@ -86,17 +90,8 @@ function stopClearCacheTimer () {
86
90
  }
87
91
  }
88
92
 
89
- function deduplicateVulnerabilities (vulnerabilities) {
90
- if (!deduplicationEnabled) return vulnerabilities
91
- const deduplicated = vulnerabilities.filter((vulnerability) => {
92
- const key = `${vulnerability.type}${vulnerability.hash}`
93
- if (!VULNERABILITY_HASHES.get(key)) {
94
- VULNERABILITY_HASHES.set(key, true)
95
- return true
96
- }
97
- return false
98
- })
99
- return deduplicated
93
+ function isDuplicatedVulnerability (vulnerability) {
94
+ return VULNERABILITY_HASHES.get(`${vulnerability.type}${vulnerability.hash}`)
100
95
  }
101
96
 
102
97
  function start (config, _tracer) {
@@ -16,7 +16,8 @@ const {
16
16
  expressProcessParams,
17
17
  responseBody,
18
18
  responseWriteHead,
19
- responseSetHeader
19
+ responseSetHeader,
20
+ routerParam
20
21
  } = require('./channels')
21
22
  const waf = require('./waf')
22
23
  const addresses = require('./addresses')
@@ -27,7 +28,7 @@ const web = require('../plugins/util/web')
27
28
  const { extractIp } = require('../plugins/util/ip_extractor')
28
29
  const { HTTP_CLIENT_IP } = require('../../../../ext/tags')
29
30
  const { isBlocked, block, setTemplates, getBlockingAction } = require('./blocking')
30
- const { passportTrackEvent } = require('./passport')
31
+ const UserTracking = require('./user_tracking')
31
32
  const { storage } = require('../../../datadog-core')
32
33
  const graphql = require('./graphql')
33
34
  const rasp = require('./rasp')
@@ -58,23 +59,23 @@ function enable (_config) {
58
59
 
59
60
  apiSecuritySampler.configure(_config.appsec)
60
61
 
62
+ UserTracking.setCollectionMode(_config.appsec.eventTracking.mode, false)
63
+
61
64
  bodyParser.subscribe(onRequestBodyParsed)
62
65
  multerParser.subscribe(onRequestBodyParsed)
63
66
  cookieParser.subscribe(onRequestCookieParser)
64
67
  incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
65
68
  incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
69
+ passportVerify.subscribe(onPassportVerify) // possible optimization: only subscribe if collection mode is enabled
66
70
  queryParser.subscribe(onRequestQueryParsed)
67
71
  nextBodyParsed.subscribe(onRequestBodyParsed)
68
72
  nextQueryParsed.subscribe(onRequestQueryParsed)
69
73
  expressProcessParams.subscribe(onRequestProcessParams)
74
+ routerParam.subscribe(onRequestProcessParams)
70
75
  responseBody.subscribe(onResponseBody)
71
76
  responseWriteHead.subscribe(onResponseWriteHead)
72
77
  responseSetHeader.subscribe(onResponseSetHeader)
73
78
 
74
- if (_config.appsec.eventTracking.enabled) {
75
- passportVerify.subscribe(onPassportVerify)
76
- }
77
-
78
79
  isEnabled = true
79
80
  config = _config
80
81
  } catch (err) {
@@ -163,8 +164,10 @@ function incomingHttpEndTranslator ({ req, res }) {
163
164
  persistent[addresses.HTTP_INCOMING_COOKIES] = req.cookies
164
165
  }
165
166
 
166
- if (req.query !== null && typeof req.query === 'object') {
167
- persistent[addresses.HTTP_INCOMING_QUERY] = req.query
167
+ // we need to keep this to support nextjs
168
+ const query = req.query
169
+ if (query !== null && typeof query === 'object') {
170
+ persistent[addresses.HTTP_INCOMING_QUERY] = query
168
171
  }
169
172
 
170
173
  if (apiSecuritySampler.sampleRequest(req, res, true)) {
@@ -180,7 +183,7 @@ function incomingHttpEndTranslator ({ req, res }) {
180
183
  Reporter.finishRequest(req, res)
181
184
  }
182
185
 
183
- function onPassportVerify ({ credentials, user }) {
186
+ function onPassportVerify ({ framework, login, user, success, abortController }) {
184
187
  const store = storage.getStore()
185
188
  const rootSpan = store?.req && web.root(store.req)
186
189
 
@@ -189,7 +192,9 @@ function onPassportVerify ({ credentials, user }) {
189
192
  return
190
193
  }
191
194
 
192
- passportTrackEvent(credentials, user, rootSpan, config.appsec.eventTracking.mode)
195
+ const results = UserTracking.trackLogin(framework, login, user, success, rootSpan)
196
+
197
+ handleResults(results, store.req, store.req.res, rootSpan, abortController)
193
198
  }
194
199
 
195
200
  function onRequestQueryParsed ({ req, res, query, abortController }) {
@@ -309,6 +314,7 @@ function disable () {
309
314
  if (nextBodyParsed.hasSubscribers) nextBodyParsed.unsubscribe(onRequestBodyParsed)
310
315
  if (nextQueryParsed.hasSubscribers) nextQueryParsed.unsubscribe(onRequestQueryParsed)
311
316
  if (expressProcessParams.hasSubscribers) expressProcessParams.unsubscribe(onRequestProcessParams)
317
+ if (routerParam.hasSubscribers) routerParam.unsubscribe(onRequestProcessParams)
312
318
  if (responseBody.hasSubscribers) responseBody.unsubscribe(onResponseBody)
313
319
  if (responseWriteHead.hasSubscribers) responseWriteHead.unsubscribe(onResponseWriteHead)
314
320
  if (responseSetHeader.hasSubscribers) responseSetHeader.unsubscribe(onResponseSetHeader)
@@ -22,6 +22,7 @@ module.exports = {
22
22
  ASM_RASP_SSRF: 1n << 23n,
23
23
  ASM_RASP_SHI: 1n << 24n,
24
24
  APM_TRACING_SAMPLE_RULES: 1n << 29n,
25
+ ASM_AUTO_USER_INSTRUM_MODE: 1n << 31n,
25
26
  ASM_ENDPOINT_FINGERPRINT: 1n << 32n,
26
27
  ASM_NETWORK_FINGERPRINT: 1n << 34n,
27
28
  ASM_HEADER_FINGERPRINT: 1n << 35n
@@ -4,6 +4,8 @@ const Activation = require('../activation')
4
4
 
5
5
  const RemoteConfigManager = require('./manager')
6
6
  const RemoteConfigCapabilities = require('./capabilities')
7
+ const { setCollectionMode } = require('../user_tracking')
8
+ const log = require('../../log')
7
9
 
8
10
  let rc
9
11
 
@@ -23,9 +25,31 @@ function enable (config, appsec) {
23
25
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_ACTIVATION, true)
24
26
  }
25
27
 
26
- rc.setProductHandler('ASM_FEATURES', (action, rcConfig) => {
28
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_AUTO_USER_INSTRUM_MODE, true)
29
+
30
+ let autoUserInstrumModeId
31
+
32
+ rc.setProductHandler('ASM_FEATURES', (action, rcConfig, configId) => {
27
33
  if (!rcConfig) return
28
34
 
35
+ // this is put before other handlers because it can reject the config
36
+ if (typeof rcConfig.auto_user_instrum?.mode === 'string') {
37
+ if (action === 'apply' || action === 'modify') {
38
+ // check if there is already a config applied with this field
39
+ if (autoUserInstrumModeId && configId !== autoUserInstrumModeId) {
40
+ log.error('[RC] Multiple auto_user_instrum received in ASM_FEATURES. Discarding config')
41
+ // eslint-disable-next-line no-throw-literal
42
+ throw 'Multiple auto_user_instrum.mode received in ASM_FEATURES'
43
+ }
44
+
45
+ setCollectionMode(rcConfig.auto_user_instrum.mode)
46
+ autoUserInstrumModeId = configId
47
+ } else if (configId === autoUserInstrumModeId) {
48
+ setCollectionMode(config.appsec.eventTracking.mode)
49
+ autoUserInstrumModeId = null
50
+ }
51
+ }
52
+
29
53
  if (activation === Activation.ONECLICK) {
30
54
  enableOrDisableAppsec(action, rcConfig, config, appsec)
31
55
  }