dd-trace 3.37.0 → 3.38.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 (50) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/package.json +4 -3
  3. package/packages/datadog-instrumentations/src/body-parser.js +2 -1
  4. package/packages/datadog-instrumentations/src/cucumber.js +24 -4
  5. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
  6. package/packages/datadog-instrumentations/src/express.js +2 -1
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  8. package/packages/datadog-instrumentations/src/jest.js +20 -11
  9. package/packages/datadog-instrumentations/src/knex.js +62 -1
  10. package/packages/datadog-instrumentations/src/mocha.js +19 -4
  11. package/packages/datadog-instrumentations/src/mongodb.js +63 -0
  12. package/packages/datadog-instrumentations/src/mongoose.js +140 -1
  13. package/packages/datadog-instrumentations/src/next.js +40 -0
  14. package/packages/datadog-instrumentations/src/playwright.js +11 -2
  15. package/packages/datadog-plugin-cucumber/src/index.js +17 -5
  16. package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
  17. package/packages/datadog-plugin-jest/src/index.js +19 -4
  18. package/packages/datadog-plugin-jest/src/util.js +45 -2
  19. package/packages/datadog-plugin-memcached/src/index.js +10 -5
  20. package/packages/datadog-plugin-mocha/src/index.js +19 -6
  21. package/packages/dd-trace/src/appsec/channels.js +3 -1
  22. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  23. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +166 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
  25. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
  26. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
  27. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
  28. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
  29. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
  31. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  32. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
  33. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +13 -1
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
  37. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  38. package/packages/dd-trace/src/appsec/index.js +31 -13
  39. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
  40. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +14 -1
  41. package/packages/dd-trace/src/config.js +8 -0
  42. package/packages/dd-trace/src/format.js +3 -0
  43. package/packages/dd-trace/src/plugin_manager.js +3 -1
  44. package/packages/dd-trace/src/plugins/util/ci.js +17 -0
  45. package/packages/dd-trace/src/plugins/util/git.js +26 -4
  46. package/packages/dd-trace/src/plugins/util/test.js +16 -1
  47. package/packages/dd-trace/src/profiling/config.js +36 -5
  48. package/packages/dd-trace/src/profiling/profilers/wall.js +7 -1
  49. package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
  50. package/packages/dd-trace/src/telemetry/metrics.js +0 -5
@@ -18,15 +18,14 @@ let onRemoveTransaction = (transactionId, iastContext) => {}
18
18
 
19
19
  function onRemoveTransactionInformationTelemetry (transactionId, iastContext) {
20
20
  const metrics = TaintedUtils.getMetrics(transactionId, iastTelemetry.verbosity)
21
- if (metrics && metrics.requestCount) {
21
+ if (metrics?.requestCount) {
22
22
  REQUEST_TAINTED.add(metrics.requestCount, null, iastContext)
23
23
  }
24
24
  }
25
25
 
26
26
  function removeTransaction (iastContext) {
27
- if (iastContext && iastContext[IAST_TRANSACTION_ID]) {
28
- const transactionId = iastContext[IAST_TRANSACTION_ID]
29
-
27
+ const transactionId = iastContext?.[IAST_TRANSACTION_ID]
28
+ if (transactionId) {
30
29
  onRemoveTransaction(transactionId, iastContext)
31
30
 
32
31
  TaintedUtils.removeTransaction(transactionId)
@@ -36,8 +35,8 @@ function removeTransaction (iastContext) {
36
35
 
37
36
  function newTaintedString (iastContext, string, name, type) {
38
37
  let result = string
39
- if (iastContext && iastContext[IAST_TRANSACTION_ID]) {
40
- const transactionId = iastContext[IAST_TRANSACTION_ID]
38
+ const transactionId = iastContext?.[IAST_TRANSACTION_ID]
39
+ if (transactionId) {
41
40
  result = TaintedUtils.newTaintedString(transactionId, string, name, type)
42
41
  } else {
43
42
  result = string
@@ -47,15 +46,17 @@ function newTaintedString (iastContext, string, name, type) {
47
46
 
48
47
  function taintObject (iastContext, object, type, keyTainting, keyType) {
49
48
  let result = object
50
- if (iastContext && iastContext[IAST_TRANSACTION_ID]) {
51
- const transactionId = iastContext[IAST_TRANSACTION_ID]
49
+ const transactionId = iastContext?.[IAST_TRANSACTION_ID]
50
+ if (transactionId) {
52
51
  const queue = [{ parent: null, property: null, value: object }]
53
52
  const visited = new WeakSet()
53
+
54
54
  while (queue.length > 0) {
55
55
  const { parent, property, value, key } = queue.pop()
56
56
  if (value === null) {
57
57
  continue
58
58
  }
59
+
59
60
  try {
60
61
  if (typeof value === 'string') {
61
62
  const tainted = TaintedUtils.newTaintedString(transactionId, value, property, type)
@@ -71,11 +72,13 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
71
72
  }
72
73
  } else if (typeof value === 'object' && !visited.has(value)) {
73
74
  visited.add(value)
75
+
74
76
  const keys = Object.keys(value)
75
77
  for (let i = 0; i < keys.length; i++) {
76
78
  const key = keys[i]
77
79
  queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key], key })
78
80
  }
81
+
79
82
  if (parent && keyTainting && key) {
80
83
  const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
81
84
  parent[taintedProperty] = value
@@ -91,8 +94,8 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
91
94
 
92
95
  function isTainted (iastContext, string) {
93
96
  let result = false
94
- if (iastContext && iastContext[IAST_TRANSACTION_ID]) {
95
- const transactionId = iastContext[IAST_TRANSACTION_ID]
97
+ const transactionId = iastContext?.[IAST_TRANSACTION_ID]
98
+ if (transactionId) {
96
99
  result = TaintedUtils.isTainted(transactionId, string)
97
100
  } else {
98
101
  result = false
@@ -102,8 +105,8 @@ function isTainted (iastContext, string) {
102
105
 
103
106
  function getRanges (iastContext, string) {
104
107
  let result = []
105
- if (iastContext && iastContext[IAST_TRANSACTION_ID]) {
106
- const transactionId = iastContext[IAST_TRANSACTION_ID]
108
+ const transactionId = iastContext?.[IAST_TRANSACTION_ID]
109
+ if (transactionId) {
107
110
  result = TaintedUtils.getRanges(transactionId, string)
108
111
  } else {
109
112
  result = []
@@ -111,6 +114,15 @@ function getRanges (iastContext, string) {
111
114
  return result
112
115
  }
113
116
 
117
+ function addSecureMark (iastContext, string, mark) {
118
+ const transactionId = iastContext?.[IAST_TRANSACTION_ID]
119
+ if (transactionId) {
120
+ return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark)
121
+ }
122
+
123
+ return string
124
+ }
125
+
114
126
  function enableTaintOperations (telemetryVerbosity) {
115
127
  if (isInfoAllowed(telemetryVerbosity)) {
116
128
  onRemoveTransaction = onRemoveTransactionInformationTelemetry
@@ -132,6 +144,7 @@ function setMaxTransactions (transactions) {
132
144
  }
133
145
 
134
146
  module.exports = {
147
+ addSecureMark,
135
148
  createTransaction,
136
149
  removeTransaction,
137
150
  newTaintedString,
@@ -11,8 +11,8 @@ const {
11
11
  HTTP_REQUEST_HEADER_VALUE,
12
12
  HTTP_REQUEST_HEADER_NAME,
13
13
  HTTP_REQUEST_PARAMETER,
14
- HTTP_REQUEST_PATH,
15
- HTTP_REQUEST_PATH_PARAM
14
+ HTTP_REQUEST_PATH_PARAM,
15
+ HTTP_REQUEST_URI
16
16
  } = require('./source-types')
17
17
 
18
18
  class TaintTrackingPlugin extends SourceIastPlugin {
@@ -93,9 +93,9 @@ class TaintTrackingPlugin extends SourceIastPlugin {
93
93
  taintUrl (req, iastContext) {
94
94
  this.execSource({
95
95
  handler: function () {
96
- req.url = newTaintedString(iastContext, req.url, 'req.url', HTTP_REQUEST_PATH)
96
+ req.url = newTaintedString(iastContext, req.url, HTTP_REQUEST_URI, HTTP_REQUEST_URI)
97
97
  },
98
- tag: [HTTP_REQUEST_PATH],
98
+ tag: [HTTP_REQUEST_URI],
99
99
  iastContext
100
100
  })
101
101
  }
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ let next = 0
4
+
5
+ function getNextSecureMark () {
6
+ return 1 << next++
7
+ }
8
+
9
+ function reset () {
10
+ next = 0
11
+ }
12
+
13
+ module.exports = { getNextSecureMark, reset }
@@ -8,5 +8,6 @@ module.exports = {
8
8
  HTTP_REQUEST_HEADER_VALUE: 'http.request.header',
9
9
  HTTP_REQUEST_PARAMETER: 'http.request.parameter',
10
10
  HTTP_REQUEST_PATH: 'http.request.path',
11
- HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter'
11
+ HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter',
12
+ HTTP_REQUEST_URI: 'http.request.uri'
12
13
  }
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const { stringifyWithRanges } = require('../../utils')
4
+
5
+ class JsonSensitiveAnalyzer {
6
+ extractSensitiveRanges (evidence) {
7
+ // expect object evidence
8
+ const { value, ranges, sensitiveRanges } = stringifyWithRanges(evidence.value, evidence.rangesToApply, true)
9
+ evidence.value = value
10
+ evidence.ranges = ranges
11
+
12
+ return sensitiveRanges
13
+ }
14
+ }
15
+
16
+ module.exports = JsonSensitiveAnalyzer
@@ -5,14 +5,12 @@ const vulnerabilities = require('../../vulnerabilities')
5
5
  const { contains, intersects, remove } = require('./range-utils')
6
6
 
7
7
  const CommandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
8
+ const JsonSensitiveAnalyzer = require('./sensitive-analyzers/json-sensitive-analyzer')
8
9
  const LdapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
9
10
  const SqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
10
11
  const UrlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
11
12
 
12
- // eslint-disable-next-line max-len
13
- 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)?)'
14
- // eslint-disable-next-line max-len
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,}))'
13
+ const { DEFAULT_IAST_REDACTION_NAME_PATTERN, DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./sensitive-regex')
16
14
 
17
15
  const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
18
16
 
@@ -23,6 +21,7 @@ class SensitiveHandler {
23
21
 
24
22
  this._sensitiveAnalyzers = new Map()
25
23
  this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
24
+ this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, new JsonSensitiveAnalyzer())
26
25
  this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
27
26
  this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
28
27
  const urlSensitiveAnalyzer = new UrlSensitiveAnalyzer()
@@ -0,0 +1,9 @@
1
+ // eslint-disable-next-line max-len
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)?)'
3
+ // eslint-disable-next-line max-len
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,}))'
5
+
6
+ module.exports = {
7
+ DEFAULT_IAST_REDACTION_NAME_PATTERN,
8
+ DEFAULT_IAST_REDACTION_VALUE_PATTERN
9
+ }
@@ -1,4 +1,7 @@
1
+ 'use strict'
2
+
1
3
  const sensitiveHandler = require('./evidence-redaction/sensitive-handler')
4
+ const { stringifyWithRanges } = require('./utils')
2
5
 
3
6
  class VulnerabilityFormatter {
4
7
  constructor () {
@@ -38,6 +41,13 @@ class VulnerabilityFormatter {
38
41
  getUnredactedValueParts (evidence, sourcesIndexes) {
39
42
  const valueParts = []
40
43
  let fromIndex = 0
44
+
45
+ if (typeof evidence.value === 'object' && evidence.rangesToApply) {
46
+ const { value, ranges } = stringifyWithRanges(evidence.value, evidence.rangesToApply)
47
+ evidence.value = value
48
+ evidence.ranges = ranges
49
+ }
50
+
41
51
  evidence.ranges.forEach((range, rangeIndex) => {
42
52
  if (fromIndex < range.start) {
43
53
  valueParts.push({ value: evidence.value.substring(fromIndex, range.start) })
@@ -45,14 +55,16 @@ class VulnerabilityFormatter {
45
55
  valueParts.push({ value: evidence.value.substring(range.start, range.end), source: sourcesIndexes[rangeIndex] })
46
56
  fromIndex = range.end
47
57
  })
58
+
48
59
  if (fromIndex < evidence.value.length) {
49
60
  valueParts.push({ value: evidence.value.substring(fromIndex) })
50
61
  }
62
+
51
63
  return { valueParts }
52
64
  }
53
65
 
54
66
  formatEvidence (type, evidence, sourcesIndexes, sources) {
55
- if (!evidence.ranges) {
67
+ if (!evidence.ranges && !evidence.rangesToApply) {
56
68
  if (typeof evidence.value === 'undefined') {
57
69
  return undefined
58
70
  } else {
@@ -0,0 +1,169 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('crypto')
4
+ const { DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./evidence-redaction/sensitive-regex')
5
+
6
+ const STRINGIFY_RANGE_KEY = 'DD_' + crypto.randomBytes(20).toString('hex')
7
+ const STRINGIFY_SENSITIVE_KEY = STRINGIFY_RANGE_KEY + 'SENSITIVE'
8
+ const STRINGIFY_SENSITIVE_NOT_STRING_KEY = STRINGIFY_SENSITIVE_KEY + 'NOTSTRING'
9
+
10
+ // eslint-disable-next-line max-len
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
+ const KEYS_REGEX_WITHOUT_SENSITIVE_RANGES = new RegExp(`"(${STRINGIFY_RANGE_KEY}_\\d+_)`, 'gm')
13
+
14
+ const sensitiveValueRegex = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
15
+
16
+ function iterateObject (target, fn, levelKeys = [], depth = 50) {
17
+ Object.keys(target).forEach((key) => {
18
+ const nextLevelKeys = [...levelKeys, key]
19
+ const val = target[key]
20
+
21
+ fn(val, nextLevelKeys, target, key)
22
+
23
+ if (val !== null && typeof val === 'object') {
24
+ iterateObject(val, fn, nextLevelKeys, depth - 1)
25
+ }
26
+ })
27
+ }
28
+
29
+ function stringifyWithRanges (obj, objRanges, loadSensitiveRanges = false) {
30
+ let value
31
+ const ranges = []
32
+ const sensitiveRanges = []
33
+ objRanges = objRanges || {}
34
+
35
+ if (objRanges || loadSensitiveRanges) {
36
+ const cloneObj = Array.isArray(obj) ? [] : {}
37
+ let counter = 0
38
+ const allRanges = {}
39
+ const sensitiveKeysMapping = {}
40
+
41
+ iterateObject(obj, (val, levelKeys, parent, key) => {
42
+ let currentLevelClone = cloneObj
43
+ for (let i = 0; i < levelKeys.length - 1; i++) {
44
+ let levelKey = levelKeys[i]
45
+
46
+ if (!currentLevelClone[levelKey]) {
47
+ const sensitiveKey = sensitiveKeysMapping[levelKey]
48
+ if (currentLevelClone[sensitiveKey]) {
49
+ levelKey = sensitiveKey
50
+ }
51
+ }
52
+
53
+ currentLevelClone = currentLevelClone[levelKey]
54
+ }
55
+
56
+ if (loadSensitiveRanges) {
57
+ const sensitiveKey = sensitiveKeysMapping[key]
58
+ if (sensitiveKey) {
59
+ key = sensitiveKey
60
+ } else {
61
+ sensitiveValueRegex.lastIndex = 0
62
+
63
+ if (sensitiveValueRegex.test(key)) {
64
+ const current = counter++
65
+ const id = `${STRINGIFY_SENSITIVE_KEY}_${current}_${key.length}_`
66
+ key = `${id}${key}`
67
+ }
68
+ }
69
+ }
70
+
71
+ if (typeof val === 'string') {
72
+ const ranges = objRanges[levelKeys.join('.')]
73
+ if (ranges) {
74
+ const current = counter++
75
+ const id = `${STRINGIFY_RANGE_KEY}_${current}_`
76
+
77
+ allRanges[id] = ranges
78
+ currentLevelClone[key] = `${id}${val}`
79
+ } else {
80
+ currentLevelClone[key] = val
81
+ }
82
+ if (loadSensitiveRanges) {
83
+ const current = counter++
84
+ const id = `${STRINGIFY_SENSITIVE_KEY}_${current}_${val.length}_`
85
+
86
+ currentLevelClone[key] = `${id}${currentLevelClone[key]}`
87
+ }
88
+ } else if (typeof val !== 'object' || val === null) {
89
+ if (loadSensitiveRanges) {
90
+ const current = counter++
91
+ const id = `${STRINGIFY_SENSITIVE_NOT_STRING_KEY}_${current}_`
92
+
93
+ // this is special, in the final string we should modify "key_value_[null|false|true]..."
94
+ // by null|false|..... ignoring the beginning and ending quotes
95
+ currentLevelClone[key] = id + val
96
+ } else {
97
+ currentLevelClone[key] = val
98
+ }
99
+ } else if (Array.isArray(val)) {
100
+ currentLevelClone[key] = []
101
+ } else {
102
+ currentLevelClone[key] = {}
103
+ }
104
+ })
105
+
106
+ value = JSON.stringify(cloneObj, null, 2)
107
+
108
+ if (counter > 0) {
109
+ let keysRegex
110
+ if (loadSensitiveRanges) {
111
+ keysRegex = KEYS_REGEX_WITH_SENSITIVE_RANGES
112
+ } else {
113
+ keysRegex = KEYS_REGEX_WITHOUT_SENSITIVE_RANGES
114
+ }
115
+ keysRegex.lastIndex = 0
116
+
117
+ let regexRes = keysRegex.exec(value)
118
+ while (regexRes) {
119
+ const offset = regexRes.index + 1 // +1 to increase the " char
120
+
121
+ if (regexRes[1]) {
122
+ // is a range
123
+ const rangesId = regexRes[1]
124
+ value = value.replace(rangesId, '')
125
+
126
+ const updatedRanges = allRanges[rangesId].map(range => {
127
+ return {
128
+ ...range,
129
+ start: range.start + offset,
130
+ end: range.end + offset
131
+ }
132
+ })
133
+
134
+ ranges.push(...updatedRanges)
135
+ } else if (regexRes[2]) {
136
+ // is a sensitive string literal
137
+ const sensitiveId = regexRes[2]
138
+
139
+ sensitiveRanges.push({
140
+ start: offset,
141
+ end: offset + parseInt(regexRes[3])
142
+ })
143
+
144
+ value = value.replace(sensitiveId, '')
145
+ } else if (regexRes[4]) {
146
+ // is a sensitive value (number, null, false, ...)
147
+ const sensitiveId = regexRes[4]
148
+ const originalValue = regexRes[5]
149
+
150
+ sensitiveRanges.push({
151
+ start: regexRes.index,
152
+ end: regexRes.index + originalValue.length
153
+ })
154
+
155
+ value = value.replace(sensitiveId, originalValue)
156
+ }
157
+
158
+ keysRegex.lastIndex = 0
159
+ regexRes = keysRegex.exec(value)
160
+ }
161
+ }
162
+ } else {
163
+ value = JSON.stringify(obj, null, 2)
164
+ }
165
+
166
+ return { value, ranges, sensitiveRanges }
167
+ }
168
+
169
+ module.exports = { stringifyWithRanges }
@@ -5,6 +5,7 @@ module.exports = {
5
5
  LDAP_INJECTION: 'LDAP_INJECTION',
6
6
  NO_HTTPONLY_COOKIE: 'NO_HTTPONLY_COOKIE',
7
7
  NO_SAMESITE_COOKIE: 'NO_SAMESITE_COOKIE',
8
+ NOSQL_MONGODB_INJECTION: 'NOSQL_MONGODB_INJECTION',
8
9
  PATH_TRAVERSAL: 'PATH_TRAVERSAL',
9
10
  SQL_INJECTION: 'SQL_INJECTION',
10
11
  SSRF: 'SSRF',
@@ -10,7 +10,9 @@ const {
10
10
  incomingHttpRequestStart,
11
11
  incomingHttpRequestEnd,
12
12
  passportVerify,
13
- queryParser
13
+ queryParser,
14
+ nextBodyParsed,
15
+ nextQueryParsed
14
16
  } = require('./channels')
15
17
  const waf = require('./waf')
16
18
  const addresses = require('./addresses')
@@ -30,6 +32,8 @@ function enable (_config) {
30
32
  if (isEnabled) return
31
33
 
32
34
  try {
35
+ appsecTelemetry.enable(_config.telemetry)
36
+
33
37
  setTemplates(_config)
34
38
 
35
39
  RuleManager.applyRules(_config.appsec.rules, _config.appsec)
@@ -38,11 +42,11 @@ function enable (_config) {
38
42
 
39
43
  Reporter.setRateLimit(_config.appsec.rateLimit)
40
44
 
41
- appsecTelemetry.enable(_config.telemetry)
42
-
43
45
  incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
44
46
  incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
45
47
  bodyParser.subscribe(onRequestBodyParsed)
48
+ nextBodyParsed.subscribe(onRequestBodyParsed)
49
+ nextQueryParsed.subscribe(onRequestQueryParsed)
46
50
  queryParser.subscribe(onRequestQueryParsed)
47
51
  cookieParser.subscribe(onRequestCookieParser)
48
52
  graphqlFinishExecute.subscribe(onGraphqlFinishExecute)
@@ -117,6 +121,10 @@ function incomingHttpEndTranslator ({ req, res }) {
117
121
  payload[addresses.HTTP_INCOMING_COOKIES] = req.cookies
118
122
  }
119
123
 
124
+ if (req.query && typeof req.query === 'object') {
125
+ payload[addresses.HTTP_INCOMING_QUERY] = req.query
126
+ }
127
+
120
128
  waf.run(payload, req)
121
129
 
122
130
  waf.disposeContext(req)
@@ -124,38 +132,48 @@ function incomingHttpEndTranslator ({ req, res }) {
124
132
  Reporter.finishRequest(req, res)
125
133
  }
126
134
 
127
- function onRequestBodyParsed ({ req, res, abortController }) {
135
+ function onRequestBodyParsed ({ req, res, body, abortController }) {
136
+ if (body === undefined || body === null) return
137
+
138
+ if (!req) {
139
+ const store = storage.getStore()
140
+ req = store?.req
141
+ }
142
+
128
143
  const rootSpan = web.root(req)
129
144
  if (!rootSpan) return
130
145
 
131
- if (req.body === undefined || req.body === null) return
132
-
133
146
  const results = waf.run({
134
- [addresses.HTTP_INCOMING_BODY]: req.body
147
+ [addresses.HTTP_INCOMING_BODY]: body
135
148
  }, req)
136
149
 
137
150
  handleResults(results, req, res, rootSpan, abortController)
138
151
  }
139
152
 
140
- function onRequestQueryParsed ({ req, res, abortController }) {
153
+ function onRequestQueryParsed ({ req, res, query, abortController }) {
154
+ if (!query || typeof query !== 'object') return
155
+
156
+ if (!req) {
157
+ const store = storage.getStore()
158
+ req = store?.req
159
+ }
160
+
141
161
  const rootSpan = web.root(req)
142
162
  if (!rootSpan) return
143
163
 
144
- if (!req.query || typeof req.query !== 'object') return
145
-
146
164
  const results = waf.run({
147
- [addresses.HTTP_INCOMING_QUERY]: req.query
165
+ [addresses.HTTP_INCOMING_QUERY]: query
148
166
  }, req)
149
167
 
150
168
  handleResults(results, req, res, rootSpan, abortController)
151
169
  }
152
170
 
153
171
  function onRequestCookieParser ({ req, res, abortController, cookies }) {
172
+ if (!cookies || typeof cookies !== 'object') return
173
+
154
174
  const rootSpan = web.root(req)
155
175
  if (!rootSpan) return
156
176
 
157
- if (!cookies || typeof cookies !== 'object') return
158
-
159
177
  const results = waf.run({
160
178
  [addresses.HTTP_INCOMING_COOKIES]: cookies
161
179
  }, req)
@@ -3,11 +3,12 @@
3
3
  const { URL, format } = require('url')
4
4
  const uuid = require('crypto-randomuuid')
5
5
  const { EventEmitter } = require('events')
6
- const Scheduler = require('./scheduler')
7
6
  const tracerVersion = require('../../../../../package.json').version
8
7
  const request = require('../../exporters/common/request')
9
8
  const log = require('../../log')
9
+ const { getExtraServices } = require('../../service-naming/extra-services')
10
10
  const { UNACKNOWLEDGED, ACKNOWLEDGED, ERROR } = require('./apply_states')
11
+ const Scheduler = require('./scheduler')
11
12
 
12
13
  const clientId = uuid()
13
14
 
@@ -57,7 +58,8 @@ class RemoteConfigManager extends EventEmitter {
57
58
  tracer_version: tracerVersion,
58
59
  service: config.service,
59
60
  env: config.env,
60
- app_version: config.version
61
+ app_version: config.version,
62
+ extra_services: []
61
63
  },
62
64
  capabilities: DEFAULT_CAPABILITY // updated by `updateCapabilities()`
63
65
  },
@@ -113,8 +115,14 @@ class RemoteConfigManager extends EventEmitter {
113
115
  this.state.client.products = this.eventNames().filter(e => typeof e === 'string')
114
116
  }
115
117
 
118
+ getPayload () {
119
+ this.state.client.client_tracer.extra_services = getExtraServices()
120
+
121
+ return JSON.stringify(this.state)
122
+ }
123
+
116
124
  poll (cb) {
117
- request(JSON.stringify(this.state), this.requestOptions, (err, data, statusCode) => {
125
+ request(this.getPayload(), this.requestOptions, (err, data, statusCode) => {
118
126
  // 404 means RC is disabled, ignore it
119
127
  if (statusCode === 404) return cb()
120
128
 
@@ -2,6 +2,12 @@
2
2
 
3
3
  const log = require('../../log')
4
4
  const Reporter = require('../reporter')
5
+ const addresses = require('../addresses')
6
+
7
+ // TODO: remove once ephemeral addresses are implemented
8
+ const preventDuplicateAddresses = new Set([
9
+ addresses.HTTP_INCOMING_QUERY
10
+ ])
5
11
 
6
12
  class WAFContextWrapper {
7
13
  constructor (ddwafContext, requiredAddresses, wafTimeout, wafVersion, rulesVersion) {
@@ -10,16 +16,21 @@ class WAFContextWrapper {
10
16
  this.wafTimeout = wafTimeout
11
17
  this.wafVersion = wafVersion
12
18
  this.rulesVersion = rulesVersion
19
+ this.addressesToSkip = new Set()
13
20
  }
14
21
 
15
22
  run (params) {
16
23
  const inputs = {}
17
24
  let someInputAdded = false
25
+ const newAddressesToSkip = new Set(this.addressesToSkip)
18
26
 
19
27
  // TODO: possible optimizaion: only send params that haven't already been sent with same value to this wafContext
20
28
  for (const key of Object.keys(params)) {
21
- if (this.requiredAddresses.has(key)) {
29
+ if (this.requiredAddresses.has(key) && !this.addressesToSkip.has(key)) {
22
30
  inputs[key] = params[key]
31
+ if (preventDuplicateAddresses.has(key)) {
32
+ newAddressesToSkip.add(key)
33
+ }
23
34
  someInputAdded = true
24
35
  }
25
36
  }
@@ -33,6 +44,8 @@ class WAFContextWrapper {
33
44
 
34
45
  const end = process.hrtime.bigint()
35
46
 
47
+ this.addressesToSkip = newAddressesToSkip
48
+
36
49
  const ruleTriggered = !!result.events?.length
37
50
  const blockTriggered = result.actions?.includes('block')
38
51
 
@@ -179,6 +179,11 @@ class Config {
179
179
  false
180
180
  )
181
181
 
182
+ const DD_TRACE_MEMCACHED_COMMAND_ENABLED = coalesce(
183
+ process.env.DD_TRACE_MEMCACHED_COMMAND_ENABLED,
184
+ false
185
+ )
186
+
182
187
  const DD_SERVICE = options.service ||
183
188
  process.env.DD_SERVICE ||
184
189
  process.env.DD_SERVICE_NAME ||
@@ -629,6 +634,9 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
629
634
 
630
635
  this.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT
631
636
 
637
+ // Requires an accompanying DD_APM_OBFUSCATION_MEMCACHED_KEEP_COMMAND=true in the agent
638
+ this.memcachedCommandEnabled = isTrue(DD_TRACE_MEMCACHED_COMMAND_ENABLED)
639
+
632
640
  if (this.gitMetadataEnabled) {
633
641
  this.repositoryUrl = coalesce(
634
642
  process.env.DD_GIT_REPOSITORY_URL,
@@ -4,6 +4,7 @@ const constants = require('./constants')
4
4
  const tags = require('../../../ext/tags')
5
5
  const id = require('./id')
6
6
  const { isError } = require('./util')
7
+ const { registerExtraService } = require('./service-naming/extra-services')
7
8
 
8
9
  const SAMPLING_PRIORITY_KEY = constants.SAMPLING_PRIORITY_KEY
9
10
  const SAMPLING_RULE_DECISION = constants.SAMPLING_RULE_DECISION
@@ -76,6 +77,8 @@ function extractTags (trace, span) {
76
77
  const tracerService = span.tracer()._service.toLowerCase()
77
78
  if (tags['service.name']?.toLowerCase() !== tracerService) {
78
79
  span.setTag(BASE_SERVICE, tracerService)
80
+
81
+ registerExtraService(tags['service.name'])
79
82
  }
80
83
 
81
84
  for (const tag in tags) {
@@ -136,7 +136,8 @@ module.exports = class PluginManager {
136
136
  headerTags,
137
137
  dbmPropagationMode,
138
138
  dsmEnabled,
139
- clientIpEnabled
139
+ clientIpEnabled,
140
+ memcachedCommandEnabled
140
141
  } = this._tracerConfig
141
142
 
142
143
  const sharedConfig = {}
@@ -151,6 +152,7 @@ module.exports = class PluginManager {
151
152
 
152
153
  sharedConfig.dbmPropagationMode = dbmPropagationMode
153
154
  sharedConfig.dsmEnabled = dsmEnabled
155
+ sharedConfig.memcachedCommandEnabled = memcachedCommandEnabled
154
156
 
155
157
  if (serviceMapping && serviceMapping[name]) {
156
158
  sharedConfig.service = serviceMapping[name]