dd-trace 4.16.0 → 4.18.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 (71) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +9 -1
  3. package/package.json +5 -4
  4. package/packages/datadog-instrumentations/src/body-parser.js +2 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +29 -4
  6. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
  7. package/packages/datadog-instrumentations/src/express.js +2 -1
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  9. package/packages/datadog-instrumentations/src/jest.js +58 -20
  10. package/packages/datadog-instrumentations/src/knex.js +69 -1
  11. package/packages/datadog-instrumentations/src/mocha.js +34 -4
  12. package/packages/datadog-instrumentations/src/mongodb.js +63 -0
  13. package/packages/datadog-instrumentations/src/mongoose.js +140 -1
  14. package/packages/datadog-instrumentations/src/next.js +98 -23
  15. package/packages/datadog-instrumentations/src/playwright.js +22 -8
  16. package/packages/datadog-plugin-cucumber/src/index.js +17 -5
  17. package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
  18. package/packages/datadog-plugin-http/src/client.js +2 -0
  19. package/packages/datadog-plugin-jest/src/index.js +29 -6
  20. package/packages/datadog-plugin-jest/src/util.js +45 -2
  21. package/packages/datadog-plugin-memcached/src/index.js +10 -5
  22. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  23. package/packages/datadog-plugin-next/src/index.js +4 -3
  24. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  25. package/packages/dd-trace/src/appsec/channels.js +3 -1
  26. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
  27. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
  29. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
  30. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
  31. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +173 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
  33. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
  34. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
  35. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
  36. package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
  37. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
  38. package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
  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/rewriter.js +13 -2
  42. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  44. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
  46. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +22 -4
  47. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +15 -2
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
  51. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
  52. package/packages/dd-trace/src/appsec/index.js +31 -13
  53. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
  54. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +14 -1
  55. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
  56. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
  57. package/packages/dd-trace/src/config.js +37 -13
  58. package/packages/dd-trace/src/format.js +3 -0
  59. package/packages/dd-trace/src/git_properties.js +16 -15
  60. package/packages/dd-trace/src/plugin_manager.js +3 -1
  61. package/packages/dd-trace/src/plugins/util/ci.js +17 -0
  62. package/packages/dd-trace/src/plugins/util/git.js +26 -4
  63. package/packages/dd-trace/src/plugins/util/test.js +45 -2
  64. package/packages/dd-trace/src/profiling/config.js +20 -3
  65. package/packages/dd-trace/src/profiling/profilers/wall.js +51 -40
  66. package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
  67. package/packages/dd-trace/src/telemetry/index.js +4 -0
  68. package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
  69. package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
  70. package/packages/dd-trace/src/telemetry/metrics.js +0 -5
  71. package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
@@ -0,0 +1,173 @@
1
+ 'use strict'
2
+
3
+ const InjectionAnalyzer = require('./injection-analyzer')
4
+ const { NOSQL_MONGODB_INJECTION } = require('../vulnerabilities')
5
+ const { getRanges, addSecureMark } = require('../taint-tracking/operations')
6
+ const { getNodeModulesPaths } = require('../path-line')
7
+ const { getNextSecureMark } = require('../taint-tracking/secure-marks-generator')
8
+ const { storage } = require('../../../../../datadog-core')
9
+ const { getIastContext } = require('../iast-context')
10
+ const { HTTP_REQUEST_PARAMETER, HTTP_REQUEST_BODY } = require('../taint-tracking/source-types')
11
+
12
+ const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose')
13
+ const MONGODB_NOSQL_SECURE_MARK = getNextSecureMark()
14
+
15
+ function iterateObjectStrings (target, fn, levelKeys = [], depth = 50, visited = new Set()) {
16
+ if (target && typeof target === 'object') {
17
+ Object.keys(target).forEach((key) => {
18
+ const nextLevelKeys = [...levelKeys, key]
19
+ const val = target[key]
20
+
21
+ if (typeof val === 'string') {
22
+ fn(val, nextLevelKeys, target, key)
23
+ } else if (depth > 0 && !visited.has(val)) {
24
+ iterateObjectStrings(val, fn, nextLevelKeys, depth - 1, visited)
25
+ visited.add(val)
26
+ }
27
+ })
28
+ }
29
+ }
30
+
31
+ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
32
+ constructor () {
33
+ super(NOSQL_MONGODB_INJECTION)
34
+ this.sanitizedObjects = new WeakSet()
35
+ }
36
+
37
+ onConfigure () {
38
+ this.configureSanitizers()
39
+
40
+ this.addSub('datadog:mongodb:collection:filter:start', ({ filters }) => {
41
+ const store = storage.getStore()
42
+ if (store && !store.nosqlAnalyzed && filters?.length) {
43
+ filters.forEach(filter => {
44
+ this.analyze({ filter }, store)
45
+ })
46
+ }
47
+ })
48
+
49
+ this.addSub('datadog:mongoose:model:filter:start', ({ filters }) => {
50
+ const store = storage.getStore()
51
+ if (!store) return
52
+
53
+ if (filters?.length) {
54
+ filters.forEach(filter => {
55
+ this.analyze({ filter }, store)
56
+ })
57
+ }
58
+
59
+ storage.enterWith({ ...store, nosqlAnalyzed: true, mongooseParentStore: store })
60
+ })
61
+
62
+ this.addSub('datadog:mongoose:model:filter:finish', () => {
63
+ const store = storage.getStore()
64
+ if (store?.mongooseParentStore) {
65
+ storage.enterWith(store.mongooseParentStore)
66
+ }
67
+ })
68
+ }
69
+
70
+ configureSanitizers () {
71
+ this.addNotSinkSub('datadog:express-mongo-sanitize:filter:finish', ({ sanitizedProperties, req }) => {
72
+ const store = storage.getStore()
73
+ const iastContext = getIastContext(store)
74
+
75
+ if (iastContext) { // do nothing if we are not in an iast request
76
+ sanitizedProperties.forEach(key => {
77
+ iterateObjectStrings(req[key], function (value, levelKeys) {
78
+ if (typeof value === 'string') {
79
+ let parentObj = req[key]
80
+ const levelsLength = levelKeys.length
81
+
82
+ for (let i = 0; i < levelsLength; i++) {
83
+ const currentLevelKey = levelKeys[i]
84
+
85
+ if (i === levelsLength - 1) {
86
+ parentObj[currentLevelKey] = addSecureMark(iastContext, value, MONGODB_NOSQL_SECURE_MARK)
87
+ } else {
88
+ parentObj = parentObj[currentLevelKey]
89
+ }
90
+ }
91
+ }
92
+ })
93
+ })
94
+ }
95
+ })
96
+
97
+ this.addNotSinkSub('datadog:express-mongo-sanitize:sanitize:finish', ({ sanitizedObject }) => {
98
+ const store = storage.getStore()
99
+ const iastContext = getIastContext(store)
100
+
101
+ if (iastContext) { // do nothing if we are not in an iast request
102
+ iterateObjectStrings(sanitizedObject, function (value, levelKeys, parent, lastKey) {
103
+ try {
104
+ parent[lastKey] = addSecureMark(iastContext, value, MONGODB_NOSQL_SECURE_MARK)
105
+ } catch {
106
+ // if it is a readonly property, do nothing
107
+ }
108
+ })
109
+ }
110
+ })
111
+
112
+ this.addNotSinkSub('datadog:mongoose:sanitize-filter:finish', ({ sanitizedObject }) => {
113
+ this.sanitizedObjects.add(sanitizedObject)
114
+ })
115
+ }
116
+
117
+ _isVulnerableRange (range) {
118
+ const rangeType = range?.iinfo?.type
119
+ const isVulnerableType = rangeType === HTTP_REQUEST_PARAMETER || rangeType === HTTP_REQUEST_BODY
120
+ return isVulnerableType && (range.secureMarks & MONGODB_NOSQL_SECURE_MARK) !== MONGODB_NOSQL_SECURE_MARK
121
+ }
122
+
123
+ _isVulnerable (value, iastContext) {
124
+ if (value?.filter && iastContext) {
125
+ let isVulnerable = false
126
+
127
+ if (this.sanitizedObjects.has(value.filter)) {
128
+ return false
129
+ }
130
+
131
+ const rangesByKey = {}
132
+ const allRanges = []
133
+
134
+ iterateObjectStrings(value.filter, (val, nextLevelKeys) => {
135
+ const ranges = getRanges(iastContext, val)
136
+ if (ranges?.length) {
137
+ const filteredRanges = []
138
+
139
+ for (const range of ranges) {
140
+ if (this._isVulnerableRange(range)) {
141
+ isVulnerable = true
142
+ filteredRanges.push(range)
143
+ }
144
+ }
145
+
146
+ if (filteredRanges.length > 0) {
147
+ rangesByKey[nextLevelKeys.join('.')] = filteredRanges
148
+ allRanges.push(...filteredRanges)
149
+ }
150
+ }
151
+ }, [], 4)
152
+
153
+ if (isVulnerable) {
154
+ value.rangesToApply = rangesByKey
155
+ value.ranges = allRanges
156
+ }
157
+
158
+ return isVulnerable
159
+ }
160
+ return false
161
+ }
162
+
163
+ _getEvidence (value, iastContext) {
164
+ return { value: value.filter, rangesToApply: value.rangesToApply, ranges: value.ranges }
165
+ }
166
+
167
+ _getExcludedPaths () {
168
+ return EXCLUDED_PATHS_FROM_STACK
169
+ }
170
+ }
171
+
172
+ module.exports = new NosqlInjectionMongodbAnalyzer()
173
+ module.exports.MONGODB_NOSQL_SECURE_MARK = MONGODB_NOSQL_SECURE_MARK
@@ -8,7 +8,7 @@ const { getIastContext } = require('../iast-context')
8
8
  const { addVulnerability } = require('../vulnerability-reporter')
9
9
  const { getNodeModulesPaths } = require('../path-line')
10
10
 
11
- const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool')
11
+ const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool', 'knex')
12
12
 
13
13
  class SqlInjectionAnalyzer extends InjectionAnalyzer {
14
14
  constructor () {
@@ -31,6 +31,12 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
31
31
 
32
32
  this.addSub('datadog:mysql:pool:query:start', ({ sql }) => this.getStoreAndAnalyze(sql, 'MYSQL'))
33
33
  this.addSub('datadog:mysql:pool:query:finish', () => this.returnToParentStore())
34
+
35
+ this.addSub('datadog:knex:raw:start', ({ sql, dialect: knexDialect }) => {
36
+ const dialect = this.normalizeKnexDialect(knexDialect)
37
+ this.getStoreAndAnalyze(sql, dialect)
38
+ })
39
+ this.addSub('datadog:knex:raw:finish', () => this.returnToParentStore())
34
40
  }
35
41
 
36
42
  getStoreAndAnalyze (query, dialect) {
@@ -83,6 +89,20 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
83
89
  _getExcludedPaths () {
84
90
  return EXCLUDED_PATHS
85
91
  }
92
+
93
+ normalizeKnexDialect (knexDialect) {
94
+ if (knexDialect === 'postgresql') {
95
+ return 'POSTGRES'
96
+ }
97
+
98
+ if (knexDialect === 'sqlite3') {
99
+ return 'SQLITE'
100
+ }
101
+
102
+ if (typeof knexDialect === 'string') {
103
+ return knexDialect.toUpperCase()
104
+ }
105
+ }
86
106
  }
87
107
 
88
108
  module.exports = new SqlInjectionAnalyzer()
@@ -6,8 +6,8 @@ const { getNodeModulesPaths } = require('../path-line')
6
6
  const { getRanges } = require('../taint-tracking/operations')
7
7
  const {
8
8
  HTTP_REQUEST_HEADER_VALUE,
9
- HTTP_REQUEST_PATH,
10
- HTTP_REQUEST_PATH_PARAM
9
+ HTTP_REQUEST_PATH_PARAM,
10
+ HTTP_REQUEST_URI
11
11
  } = require('../taint-tracking/source-types')
12
12
 
13
13
  const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
@@ -56,7 +56,7 @@ class UnvalidatedRedirectAnalyzer extends InjectionAnalyzer {
56
56
  }
57
57
 
58
58
  _isUrl (range) {
59
- return range.iinfo.type === HTTP_REQUEST_PATH
59
+ return range.iinfo.type === HTTP_REQUEST_URI
60
60
  }
61
61
 
62
62
  _getExcludedPaths () {
@@ -71,8 +71,7 @@ class Analyzer extends SinkIastPlugin {
71
71
  return store && !iastContext
72
72
  }
73
73
 
74
- analyze (value) {
75
- const store = storage.getStore()
74
+ analyze (value, store = storage.getStore()) {
76
75
  const iastContext = getIastContext(store)
77
76
  if (this._isInvalidContext(store, iastContext)) return
78
77
 
@@ -11,8 +11,8 @@ class XcontenttypeHeaderMissingAnalyzer extends MissingHeaderAnalyzer {
11
11
  }
12
12
 
13
13
  _isVulnerableFromRequestAndResponse (req, res) {
14
- const headerToCheck = res.getHeader(XCONTENTTYPEOPTIONS_HEADER_NAME)
15
- return !headerToCheck || headerToCheck.trim().toLowerCase() !== 'nosniff'
14
+ const headerValues = this._getHeaderValues(res, XCONTENTTYPEOPTIONS_HEADER_NAME)
15
+ return headerValues.length === 0 || headerValues.some(headerValue => headerValue.trim().toLowerCase() !== 'nosniff')
16
16
  }
17
17
  }
18
18
 
@@ -1,9 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ const dc = require('../../../../diagnostics_channel')
3
4
  const log = require('../../log')
4
- const telemetryLogs = require('./telemetry/log')
5
5
  const { calculateDDBasePath } = require('../../util')
6
6
 
7
+ const telemetryLog = dc.channel('datadog:telemetry:log')
8
+
7
9
  const ddBasePath = calculateDDBasePath(__dirname)
8
10
  const EOL = '\n'
9
11
  const STACK_FRAME_LINE_REGEX = /^\s*at\s/gm
@@ -80,9 +82,8 @@ const iastLog = {
80
82
  },
81
83
 
82
84
  publish (data, level) {
83
- if (telemetryLogs.isLevelEnabled(level)) {
84
- const telemetryLog = getTelemetryLog(data, level)
85
- telemetryLogs.publish(telemetryLog)
85
+ if (telemetryLog.hasSubscribers) {
86
+ telemetryLog.publish(getTelemetryLog(data, level))
86
87
  }
87
88
  return this
88
89
  },
@@ -92,6 +93,10 @@ const iastLog = {
92
93
  return this.publish(data, 'DEBUG')
93
94
  },
94
95
 
96
+ /**
97
+ * forward 'INFO' log level to 'DEBUG' telemetry log level
98
+ * see also {@link ../../telemetry/logs#isLevelEnabled } method
99
+ */
95
100
  infoAndPublish (data) {
96
101
  this.info(data)
97
102
  return this.publish(data, 'DEBUG')
@@ -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 = {
@@ -6,6 +6,7 @@ const { calculateDDBasePath } = require('../../util')
6
6
  const pathLine = {
7
7
  getFirstNonDDPathAndLine,
8
8
  getNodeModulesPaths,
9
+ getRelativePath,
9
10
  getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
10
11
  calculateDDBasePath, // Exported only for test purposes
11
12
  ddBasePath: calculateDDBasePath(__dirname) // Only for test purposes
@@ -45,7 +46,7 @@ function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPat
45
46
  const filepath = callsite.getFileName()
46
47
  if (!isExcluded(callsite, externallyExcludedPaths) && filepath.indexOf(pathLine.ddBasePath) === -1) {
47
48
  return {
48
- path: path.relative(process.cwd(), filepath),
49
+ path: getRelativePath(filepath),
49
50
  line: callsite.getLineNumber(),
50
51
  column: callsite.getColumnNumber(),
51
52
  isInternal: !path.isAbsolute(filepath)
@@ -56,6 +57,10 @@ function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPat
56
57
  return null
57
58
  }
58
59
 
60
+ function getRelativePath (filepath) {
61
+ return path.relative(process.cwd(), filepath)
62
+ }
63
+
59
64
  function isExcluded (callsite, externallyExcludedPaths) {
60
65
  if (callsite.isNative()) return true
61
66
  const filename = callsite.getFileName()
@@ -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
  }
@@ -7,7 +7,9 @@ const { isPrivateModule, isNotLibraryFile } = require('./filter')
7
7
  const { csiMethods } = require('./csi-methods')
8
8
  const { getName } = require('../telemetry/verbosity')
9
9
  const { getRewriteFunction } = require('./rewriter-telemetry')
10
+ const dc = require('../../../../../diagnostics_channel')
10
11
 
12
+ const hardcodedSecretCh = dc.channel('datadog:secrets:result')
11
13
  let rewriter
12
14
  let getPrepareStackTrace
13
15
 
@@ -50,7 +52,11 @@ function getRewriter (telemetryVerbosity) {
50
52
  getGetOriginalPathAndLineFromSourceMapFunction(chainSourceMap, getOriginalPathAndLineFromSourceMap)
51
53
  }
52
54
 
53
- rewriter = new Rewriter({ csiMethods, telemetryVerbosity: getName(telemetryVerbosity), chainSourceMap })
55
+ rewriter = new Rewriter({
56
+ csiMethods,
57
+ telemetryVerbosity: getName(telemetryVerbosity),
58
+ chainSourceMap
59
+ })
54
60
  } catch (e) {
55
61
  iastLog.error('Unable to initialize TaintTracking Rewriter')
56
62
  .errorAndPublish(e)
@@ -80,7 +86,12 @@ function getCompileMethodFn (compileMethod) {
80
86
  try {
81
87
  if (isPrivateModule(filename) && isNotLibraryFile(filename)) {
82
88
  const rewritten = rewriteFn(content, filename)
83
- if (rewritten && rewritten.content) {
89
+
90
+ if (rewritten?.literalsResult && hardcodedSecretCh.hasSubscribers) {
91
+ hardcodedSecretCh.publish(rewritten.literalsResult)
92
+ }
93
+
94
+ if (rewritten?.content) {
84
95
  return compileMethod.apply(this, [rewritten.content, filename])
85
96
  }
86
97
  }
@@ -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
  }
@@ -1,21 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const telemetryMetrics = require('../../../telemetry/metrics')
4
- const telemetryLogs = require('./log')
5
4
  const { Verbosity, getVerbosity } = require('./verbosity')
6
5
  const { initRequestNamespace, finalizeRequestNamespace, globalNamespace } = require('./namespaces')
7
6
 
8
- function isIastMetricsEnabled (metrics) {
9
- // TODO: let DD_TELEMETRY_METRICS_ENABLED as undefined in config.js to avoid read here the env property
10
- return process.env.DD_TELEMETRY_METRICS_ENABLED !== undefined ? metrics : true
11
- }
12
-
13
7
  class Telemetry {
14
8
  configure (config, verbosity) {
15
- const telemetryAndMetricsEnabled = config &&
16
- config.telemetry &&
17
- config.telemetry.enabled &&
18
- isIastMetricsEnabled(config.telemetry.metrics)
9
+ const telemetryAndMetricsEnabled = config?.telemetry?.enabled && config.telemetry.metrics
19
10
 
20
11
  this.verbosity = telemetryAndMetricsEnabled ? getVerbosity(verbosity) : Verbosity.OFF
21
12
  this.enabled = this.verbosity !== Verbosity.OFF
@@ -23,15 +14,11 @@ class Telemetry {
23
14
  if (this.enabled) {
24
15
  telemetryMetrics.manager.set('iast', globalNamespace)
25
16
  }
26
-
27
- telemetryLogs.start()
28
17
  }
29
18
 
30
19
  stop () {
31
20
  this.enabled = false
32
21
  telemetryMetrics.manager.delete('iast')
33
-
34
- telemetryLogs.stop()
35
22
  }
36
23
 
37
24
  isEnabled () {
@@ -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
@@ -1,18 +1,17 @@
1
1
  'use strict'
2
2
 
3
+ const iastLog = require('../../iast-log')
3
4
  const vulnerabilities = require('../../vulnerabilities')
4
5
 
5
6
  const { contains, intersects, remove } = require('./range-utils')
6
7
 
7
8
  const CommandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
9
+ const JsonSensitiveAnalyzer = require('./sensitive-analyzers/json-sensitive-analyzer')
8
10
  const LdapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
9
11
  const SqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
10
12
  const UrlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
11
13
 
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,}))'
14
+ const { DEFAULT_IAST_REDACTION_NAME_PATTERN, DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./sensitive-regex')
16
15
 
17
16
  const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
18
17
 
@@ -23,6 +22,7 @@ class SensitiveHandler {
23
22
 
24
23
  this._sensitiveAnalyzers = new Map()
25
24
  this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
25
+ this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, new JsonSensitiveAnalyzer())
26
26
  this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
27
27
  this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
28
28
  const urlSensitiveAnalyzer = new UrlSensitiveAnalyzer()
@@ -264,6 +264,24 @@ class SensitiveHandler {
264
264
  valueParts.push({ redacted: true })
265
265
  }
266
266
  }
267
+
268
+ setRedactionPatterns (redactionNamePattern, redactionValuePattern) {
269
+ if (redactionNamePattern) {
270
+ try {
271
+ this._namePattern = new RegExp(redactionNamePattern, 'gmi')
272
+ } catch (e) {
273
+ iastLog.warn('Redaction name pattern is not valid')
274
+ }
275
+ }
276
+
277
+ if (redactionValuePattern) {
278
+ try {
279
+ this._valuePattern = new RegExp(redactionValuePattern, 'gmi')
280
+ } catch (e) {
281
+ iastLog.warn('Redaction value pattern is not valid')
282
+ }
283
+ }
284
+ }
267
285
  }
268
286
 
269
287
  module.exports = new SensitiveHandler()
@@ -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
+ }