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
@@ -14,6 +14,8 @@ const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phra
14
14
  // eslint-disable-next-line max-len
15
15
  const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
16
16
 
17
+ const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
18
+
17
19
  class SensitiveHandler {
18
20
  constructor () {
19
21
  this._namePattern = new RegExp(DEFAULT_IAST_REDACTION_NAME_PATTERN, 'gmi')
@@ -54,6 +56,7 @@ class SensitiveHandler {
54
56
  toRedactedJson (evidence, sensitive, sourcesIndexes, sources) {
55
57
  const valueParts = []
56
58
  const redactedSources = []
59
+ const redactedSourcesContext = []
57
60
 
58
61
  const { value, ranges } = evidence
59
62
 
@@ -71,21 +74,52 @@ class SensitiveHandler {
71
74
  sourceIndex = sourcesIndexes[nextTaintedIndex]
72
75
 
73
76
  while (nextSensitive != null && contains(nextTainted, nextSensitive)) {
74
- sourceIndex != null && redactedSources.push(sourceIndex)
77
+ const redactionStart = nextSensitive.start - nextTainted.start
78
+ const redactionEnd = nextSensitive.end - nextTainted.start
79
+ if (redactionStart === redactionEnd) {
80
+ this.writeRedactedValuePart(valueParts, 0)
81
+ } else {
82
+ this.redactSource(
83
+ sources,
84
+ redactedSources,
85
+ redactedSourcesContext,
86
+ sourceIndex,
87
+ redactionStart,
88
+ redactionEnd
89
+ )
90
+ }
75
91
  nextSensitive = sensitive.shift()
76
92
  }
77
93
 
78
94
  if (nextSensitive != null && intersects(nextSensitive, nextTainted)) {
79
- sourceIndex != null && redactedSources.push(sourceIndex)
95
+ const redactionStart = nextSensitive.start - nextTainted.start
96
+ const redactionEnd = nextSensitive.end - nextTainted.start
97
+ this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
80
98
 
81
99
  const entries = remove(nextSensitive, nextTainted)
82
100
  nextSensitive = entries.length > 0 ? entries[0] : null
83
101
  }
84
102
 
85
- this.isSensibleSource(sources[sourceIndex]) && redactedSources.push(sourceIndex)
103
+ if (this.isSensibleSource(sources[sourceIndex])) {
104
+ if (!sources[sourceIndex].redacted) {
105
+ redactedSources.push(sourceIndex)
106
+ sources[sourceIndex].pattern = ''.padEnd(sources[sourceIndex].value.length, REDACTED_SOURCE_BUFFER)
107
+ sources[sourceIndex].redacted = true
108
+ }
109
+ }
86
110
 
87
111
  if (redactedSources.indexOf(sourceIndex) > -1) {
88
- this.writeRedactedValuePart(valueParts, sourceIndex)
112
+ const partValue = value.substring(i, i + (nextTainted.end - nextTainted.start))
113
+ this.writeRedactedValuePart(
114
+ valueParts,
115
+ partValue.length,
116
+ sourceIndex,
117
+ partValue,
118
+ sources[sourceIndex],
119
+ redactedSourcesContext[sourceIndex],
120
+ this.isSensibleSource(sources[sourceIndex])
121
+ )
122
+ redactedSourcesContext[sourceIndex] = []
89
123
  } else {
90
124
  const substringEnd = Math.min(nextTainted.end, value.length)
91
125
  this.writeValuePart(valueParts, value.substring(nextTainted.start, substringEnd), sourceIndex)
@@ -100,7 +134,10 @@ class SensitiveHandler {
100
134
  this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
101
135
  if (nextTainted != null && intersects(nextSensitive, nextTainted)) {
102
136
  sourceIndex = sourcesIndexes[nextTaintedIndex]
103
- sourceIndex != null && redactedSources.push(sourceIndex)
137
+
138
+ const redactionStart = nextSensitive.start - nextTainted.start
139
+ const redactionEnd = nextSensitive.end - nextTainted.start
140
+ this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
104
141
 
105
142
  for (const entry of remove(nextSensitive, nextTainted)) {
106
143
  if (entry.start === i) {
@@ -111,9 +148,10 @@ class SensitiveHandler {
111
148
  }
112
149
  }
113
150
 
114
- this.writeRedactedValuePart(valueParts)
151
+ const _length = nextSensitive.end - nextSensitive.start
152
+ this.writeRedactedValuePart(valueParts, _length)
115
153
 
116
- start = i + (nextSensitive.end - nextSensitive.start)
154
+ start = i + _length
117
155
  i = start - 1
118
156
  nextSensitive = sensitive.shift()
119
157
  }
@@ -126,6 +164,24 @@ class SensitiveHandler {
126
164
  return { redactedValueParts: valueParts, redactedSources }
127
165
  }
128
166
 
167
+ redactSource (sources, redactedSources, redactedSourcesContext, sourceIndex, start, end) {
168
+ if (sourceIndex != null) {
169
+ if (!sources[sourceIndex].redacted) {
170
+ redactedSources.push(sourceIndex)
171
+ sources[sourceIndex].pattern = ''.padEnd(sources[sourceIndex].value.length, REDACTED_SOURCE_BUFFER)
172
+ sources[sourceIndex].redacted = true
173
+ }
174
+
175
+ if (!redactedSourcesContext[sourceIndex]) {
176
+ redactedSourcesContext[sourceIndex] = []
177
+ }
178
+ redactedSourcesContext[sourceIndex].push({
179
+ start,
180
+ end
181
+ })
182
+ }
183
+ }
184
+
129
185
  writeValuePart (valueParts, value, source) {
130
186
  if (value.length > 0) {
131
187
  if (source != null) {
@@ -136,9 +192,74 @@ class SensitiveHandler {
136
192
  }
137
193
  }
138
194
 
139
- writeRedactedValuePart (valueParts, source) {
140
- if (source != null) {
141
- valueParts.push({ redacted: true, source })
195
+ writeRedactedValuePart (
196
+ valueParts,
197
+ length,
198
+ sourceIndex,
199
+ partValue,
200
+ source,
201
+ sourceRedactionContext,
202
+ isSensibleSource
203
+ ) {
204
+ if (sourceIndex != null) {
205
+ const placeholder = source.value.includes(partValue)
206
+ ? source.pattern
207
+ : '*'.repeat(length)
208
+
209
+ if (isSensibleSource) {
210
+ valueParts.push({ redacted: true, source: sourceIndex, pattern: placeholder })
211
+ } else {
212
+ let _value = partValue
213
+ const dedupedSourceRedactionContexts = []
214
+
215
+ sourceRedactionContext.forEach(_sourceRedactionContext => {
216
+ const isPresentInDeduped = dedupedSourceRedactionContexts.some(_dedupedSourceRedactionContext =>
217
+ _dedupedSourceRedactionContext.start === _sourceRedactionContext.start &&
218
+ _dedupedSourceRedactionContext.end === _sourceRedactionContext.end
219
+ )
220
+
221
+ if (!isPresentInDeduped) {
222
+ dedupedSourceRedactionContexts.push(_sourceRedactionContext)
223
+ }
224
+ })
225
+
226
+ let offset = 0
227
+ dedupedSourceRedactionContexts.forEach((_sourceRedactionContext) => {
228
+ if (_sourceRedactionContext.start > 0) {
229
+ valueParts.push({
230
+ source: sourceIndex,
231
+ value: _value.substring(0, _sourceRedactionContext.start - offset)
232
+ })
233
+
234
+ _value = _value.substring(_sourceRedactionContext.start - offset)
235
+ offset = _sourceRedactionContext.start
236
+ }
237
+
238
+ const sensitive =
239
+ _value.substring(_sourceRedactionContext.start - offset, _sourceRedactionContext.end - offset)
240
+ const indexOfPartValueInPattern = source.value.indexOf(sensitive)
241
+
242
+ const pattern = indexOfPartValueInPattern > -1
243
+ ? placeholder.substring(indexOfPartValueInPattern, indexOfPartValueInPattern + sensitive.length)
244
+ : placeholder.substring(_sourceRedactionContext.start, _sourceRedactionContext.end)
245
+
246
+ valueParts.push({
247
+ redacted: true,
248
+ source: sourceIndex,
249
+ pattern
250
+ })
251
+
252
+ _value = _value.substring(pattern.length)
253
+ offset += pattern.length
254
+ })
255
+
256
+ if (_value.length) {
257
+ valueParts.push({
258
+ source: sourceIndex,
259
+ value: _value
260
+ })
261
+ }
262
+ }
142
263
  } else {
143
264
  valueParts.push({ redacted: true })
144
265
  }
@@ -28,7 +28,6 @@ class VulnerabilityFormatter {
28
28
  const { redactedValueParts, redactedSources } = scrubbingResult
29
29
  redactedSources.forEach(i => {
30
30
  delete sources[i].value
31
- sources[i].redacted = true
32
31
  })
33
32
  return { valueParts: redactedValueParts }
34
33
  }
@@ -4,15 +4,18 @@ const log = require('../log')
4
4
  const RuleManager = require('./rule_manager')
5
5
  const remoteConfig = require('./remote_config')
6
6
  const {
7
+ bodyParser,
8
+ cookieParser,
9
+ graphqlFinishExecute,
7
10
  incomingHttpRequestStart,
8
11
  incomingHttpRequestEnd,
9
- bodyParser,
10
12
  passportVerify,
11
13
  queryParser
12
14
  } = require('./channels')
13
15
  const waf = require('./waf')
14
16
  const addresses = require('./addresses')
15
17
  const Reporter = require('./reporter')
18
+ const appsecTelemetry = require('./telemetry')
16
19
  const web = require('../plugins/util/web')
17
20
  const { extractIp } = require('../plugins/util/ip_extractor')
18
21
  const { HTTP_CLIENT_IP } = require('../../../../ext/tags')
@@ -35,10 +38,14 @@ function enable (_config) {
35
38
 
36
39
  Reporter.setRateLimit(_config.appsec.rateLimit)
37
40
 
41
+ appsecTelemetry.enable(_config.telemetry)
42
+
38
43
  incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
39
44
  incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
40
45
  bodyParser.subscribe(onRequestBodyParsed)
41
46
  queryParser.subscribe(onRequestQueryParsed)
47
+ cookieParser.subscribe(onRequestCookieParser)
48
+ graphqlFinishExecute.subscribe(onGraphqlFinishExecute)
42
49
 
43
50
  if (_config.appsec.eventTracking.enabled) {
44
51
  passportVerify.subscribe(onPassportVerify)
@@ -105,12 +112,9 @@ function incomingHttpEndTranslator ({ req, res }) {
105
112
  payload[addresses.HTTP_INCOMING_PARAMS] = req.params
106
113
  }
107
114
 
115
+ // we need to keep this to support other cookie parsers
108
116
  if (req.cookies && typeof req.cookies === 'object') {
109
- payload[addresses.HTTP_INCOMING_COOKIES] = {}
110
-
111
- for (const k of Object.keys(req.cookies)) {
112
- payload[addresses.HTTP_INCOMING_COOKIES][k] = [req.cookies[k]]
113
- }
117
+ payload[addresses.HTTP_INCOMING_COOKIES] = req.cookies
114
118
  }
115
119
 
116
120
  waf.run(payload, req)
@@ -146,6 +150,19 @@ function onRequestQueryParsed ({ req, res, abortController }) {
146
150
  handleResults(results, req, res, rootSpan, abortController)
147
151
  }
148
152
 
153
+ function onRequestCookieParser ({ req, res, abortController, cookies }) {
154
+ const rootSpan = web.root(req)
155
+ if (!rootSpan) return
156
+
157
+ if (!cookies || typeof cookies !== 'object') return
158
+
159
+ const results = waf.run({
160
+ [addresses.HTTP_INCOMING_COOKIES]: cookies
161
+ }, req)
162
+
163
+ handleResults(results, req, res, rootSpan, abortController)
164
+ }
165
+
149
166
  function onPassportVerify ({ credentials, user }) {
150
167
  const store = storage.getStore()
151
168
  const rootSpan = store && store.req && web.root(store.req)
@@ -158,6 +175,20 @@ function onPassportVerify ({ credentials, user }) {
158
175
  passportTrackEvent(credentials, user, rootSpan, config.appsec.eventTracking.mode)
159
176
  }
160
177
 
178
+ function onGraphqlFinishExecute ({ context }) {
179
+ const store = storage.getStore()
180
+ const req = store?.req
181
+
182
+ if (!req) return
183
+
184
+ const resolvers = context?.resolvers
185
+
186
+ if (!resolvers || typeof resolvers !== 'object') return
187
+
188
+ // Don't collect blocking result because it only works in monitor mode.
189
+ waf.run({ [addresses.HTTP_INCOMING_GRAPHQL_RESOLVERS]: resolvers }, req)
190
+ }
191
+
161
192
  function handleResults (actions, req, res, rootSpan, abortController) {
162
193
  if (!actions || !req || !res || !rootSpan || !abortController) return
163
194
 
@@ -172,13 +203,17 @@ function disable () {
172
203
 
173
204
  RuleManager.clearAllRules()
174
205
 
206
+ appsecTelemetry.disable()
207
+
175
208
  remoteConfig.disableWafUpdate()
176
209
 
177
210
  // Channel#unsubscribe() is undefined for non active channels
211
+ if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
212
+ if (graphqlFinishExecute.hasSubscribers) graphqlFinishExecute.unsubscribe(onGraphqlFinishExecute)
178
213
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
179
214
  if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
180
- if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
181
215
  if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed)
216
+ if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
182
217
  if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify)
183
218
  }
184
219