dd-trace 3.36.0 → 3.38.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 (76) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/ext/tags.d.ts +1 -0
  3. package/ext/tags.js +1 -0
  4. package/index.d.ts +1 -0
  5. package/package.json +9 -6
  6. package/packages/datadog-esbuild/index.js +30 -25
  7. package/packages/datadog-instrumentations/src/body-parser.js +4 -3
  8. package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +24 -4
  10. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
  11. package/packages/datadog-instrumentations/src/express.js +3 -2
  12. package/packages/datadog-instrumentations/src/graphql.js +5 -0
  13. package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -1
  14. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +20 -11
  16. package/packages/datadog-instrumentations/src/knex.js +62 -1
  17. package/packages/datadog-instrumentations/src/mocha.js +19 -4
  18. package/packages/datadog-instrumentations/src/mongodb.js +63 -0
  19. package/packages/datadog-instrumentations/src/mongoose.js +140 -1
  20. package/packages/datadog-instrumentations/src/next.js +62 -80
  21. package/packages/datadog-instrumentations/src/pg.js +14 -15
  22. package/packages/datadog-instrumentations/src/playwright.js +26 -5
  23. package/packages/datadog-plugin-cucumber/src/index.js +17 -5
  24. package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
  25. package/packages/datadog-plugin-jest/src/index.js +19 -4
  26. package/packages/datadog-plugin-jest/src/util.js +45 -2
  27. package/packages/datadog-plugin-memcached/src/index.js +10 -5
  28. package/packages/datadog-plugin-mocha/src/index.js +19 -6
  29. package/packages/datadog-plugin-mysql/src/index.js +2 -2
  30. package/packages/datadog-plugin-next/src/index.js +14 -5
  31. package/packages/datadog-plugin-pg/src/index.js +2 -2
  32. package/packages/dd-trace/src/appsec/channels.js +4 -1
  33. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  34. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +166 -0
  35. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
  36. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
  37. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
  38. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
  39. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
  42. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
  46. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +13 -1
  47. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  49. package/packages/dd-trace/src/appsec/index.js +45 -14
  50. package/packages/dd-trace/src/appsec/recommended.json +549 -24
  51. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  52. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  53. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
  54. package/packages/dd-trace/src/appsec/reporter.js +7 -5
  55. package/packages/dd-trace/src/appsec/telemetry.js +2 -2
  56. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +18 -5
  57. package/packages/dd-trace/src/appsec/waf/waf_manager.js +5 -4
  58. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
  59. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
  60. package/packages/dd-trace/src/config.js +8 -0
  61. package/packages/dd-trace/src/datastreams/processor.js +6 -2
  62. package/packages/dd-trace/src/format.js +9 -1
  63. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
  64. package/packages/dd-trace/src/opentracing/tracer.js +0 -2
  65. package/packages/dd-trace/src/plugin_manager.js +4 -3
  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/util/ci.js +17 -0
  70. package/packages/dd-trace/src/plugins/util/git.js +26 -4
  71. package/packages/dd-trace/src/plugins/util/test.js +16 -1
  72. package/packages/dd-trace/src/profiling/config.js +36 -5
  73. package/packages/dd-trace/src/profiling/profilers/wall.js +7 -1
  74. package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
  75. package/packages/dd-trace/src/telemetry/index.js +10 -1
  76. package/packages/dd-trace/src/telemetry/metrics.js +0 -5
@@ -196,6 +196,10 @@ class SinkIastPlugin extends IastPlugin {
196
196
  addSub (iastPluginSub, handler) {
197
197
  return super.addSub({ tagKey: TagKey.VULNERABILITY_TYPE, ...iastPluginSub }, handler)
198
198
  }
199
+
200
+ addNotSinkSub (iastPluginSub, handler) {
201
+ return super.addSub(iastPluginSub, handler)
202
+ }
199
203
  }
200
204
 
201
205
  module.exports = {
@@ -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',
@@ -5,11 +5,14 @@ const RuleManager = require('./rule_manager')
5
5
  const remoteConfig = require('./remote_config')
6
6
  const {
7
7
  bodyParser,
8
+ cookieParser,
8
9
  graphqlFinishExecute,
9
10
  incomingHttpRequestStart,
10
11
  incomingHttpRequestEnd,
11
12
  passportVerify,
12
- queryParser
13
+ queryParser,
14
+ nextBodyParsed,
15
+ nextQueryParsed
13
16
  } = require('./channels')
14
17
  const waf = require('./waf')
15
18
  const addresses = require('./addresses')
@@ -29,6 +32,8 @@ function enable (_config) {
29
32
  if (isEnabled) return
30
33
 
31
34
  try {
35
+ appsecTelemetry.enable(_config.telemetry)
36
+
32
37
  setTemplates(_config)
33
38
 
34
39
  RuleManager.applyRules(_config.appsec.rules, _config.appsec)
@@ -37,12 +42,13 @@ function enable (_config) {
37
42
 
38
43
  Reporter.setRateLimit(_config.appsec.rateLimit)
39
44
 
40
- appsecTelemetry.enable(_config.telemetry)
41
-
42
45
  incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
43
46
  incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
44
47
  bodyParser.subscribe(onRequestBodyParsed)
48
+ nextBodyParsed.subscribe(onRequestBodyParsed)
49
+ nextQueryParsed.subscribe(onRequestQueryParsed)
45
50
  queryParser.subscribe(onRequestQueryParsed)
51
+ cookieParser.subscribe(onRequestCookieParser)
46
52
  graphqlFinishExecute.subscribe(onGraphqlFinishExecute)
47
53
 
48
54
  if (_config.appsec.eventTracking.enabled) {
@@ -110,12 +116,13 @@ function incomingHttpEndTranslator ({ req, res }) {
110
116
  payload[addresses.HTTP_INCOMING_PARAMS] = req.params
111
117
  }
112
118
 
119
+ // we need to keep this to support other cookie parsers
113
120
  if (req.cookies && typeof req.cookies === 'object') {
114
- payload[addresses.HTTP_INCOMING_COOKIES] = {}
121
+ payload[addresses.HTTP_INCOMING_COOKIES] = req.cookies
122
+ }
115
123
 
116
- for (const k of Object.keys(req.cookies)) {
117
- payload[addresses.HTTP_INCOMING_COOKIES][k] = [req.cookies[k]]
118
- }
124
+ if (req.query && typeof req.query === 'object') {
125
+ payload[addresses.HTTP_INCOMING_QUERY] = req.query
119
126
  }
120
127
 
121
128
  waf.run(payload, req)
@@ -125,27 +132,50 @@ function incomingHttpEndTranslator ({ req, res }) {
125
132
  Reporter.finishRequest(req, res)
126
133
  }
127
134
 
128
- 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
+
129
143
  const rootSpan = web.root(req)
130
144
  if (!rootSpan) return
131
145
 
132
- if (req.body === undefined || req.body === null) return
133
-
134
146
  const results = waf.run({
135
- [addresses.HTTP_INCOMING_BODY]: req.body
147
+ [addresses.HTTP_INCOMING_BODY]: body
136
148
  }, req)
137
149
 
138
150
  handleResults(results, req, res, rootSpan, abortController)
139
151
  }
140
152
 
141
- 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
+
142
161
  const rootSpan = web.root(req)
143
162
  if (!rootSpan) return
144
163
 
145
- if (!req.query || typeof req.query !== 'object') return
164
+ const results = waf.run({
165
+ [addresses.HTTP_INCOMING_QUERY]: query
166
+ }, req)
167
+
168
+ handleResults(results, req, res, rootSpan, abortController)
169
+ }
170
+
171
+ function onRequestCookieParser ({ req, res, abortController, cookies }) {
172
+ if (!cookies || typeof cookies !== 'object') return
173
+
174
+ const rootSpan = web.root(req)
175
+ if (!rootSpan) return
146
176
 
147
177
  const results = waf.run({
148
- [addresses.HTTP_INCOMING_QUERY]: req.query
178
+ [addresses.HTTP_INCOMING_COOKIES]: cookies
149
179
  }, req)
150
180
 
151
181
  handleResults(results, req, res, rootSpan, abortController)
@@ -201,6 +231,7 @@ function disable () {
201
231
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
202
232
  if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
203
233
  if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed)
234
+ if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
204
235
  if (passportVerify.hasSubscribers) passportVerify.unsubscribe(onPassportVerify)
205
236
  }
206
237