dd-trace 5.35.0 → 5.37.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 (125) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/index.d.ts +8 -7
  3. package/loader-hook.mjs +0 -4
  4. package/package.json +15 -14
  5. package/packages/datadog-core/index.js +1 -1
  6. package/packages/datadog-core/src/storage.js +76 -31
  7. package/packages/datadog-instrumentations/src/cucumber.js +54 -1
  8. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  9. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  10. package/packages/datadog-instrumentations/src/jest.js +105 -11
  11. package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
  13. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
  14. package/packages/datadog-instrumentations/src/mysql2.js +3 -3
  15. package/packages/datadog-instrumentations/src/openai.js +8 -0
  16. package/packages/datadog-instrumentations/src/playwright.js +70 -22
  17. package/packages/datadog-instrumentations/src/vitest.js +60 -6
  18. package/packages/datadog-plugin-aerospike/src/index.js +1 -1
  19. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +1 -1
  20. package/packages/datadog-plugin-apollo/src/gateway/index.js +1 -1
  21. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -1
  22. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -3
  23. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -4
  24. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -2
  25. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  26. package/packages/datadog-plugin-couchbase/src/index.js +2 -2
  27. package/packages/datadog-plugin-cucumber/src/index.js +31 -14
  28. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +72 -7
  29. package/packages/datadog-plugin-cypress/src/support.js +36 -29
  30. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  31. package/packages/datadog-plugin-graphql/src/utils.js +8 -1
  32. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  33. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  34. package/packages/datadog-plugin-hapi/src/index.js +1 -1
  35. package/packages/datadog-plugin-http/src/client.js +1 -1
  36. package/packages/datadog-plugin-http/src/server.js +1 -1
  37. package/packages/datadog-plugin-http2/src/client.js +3 -3
  38. package/packages/datadog-plugin-http2/src/server.js +1 -1
  39. package/packages/datadog-plugin-jest/src/index.js +17 -12
  40. package/packages/datadog-plugin-langchain/src/tracing.js +1 -1
  41. package/packages/datadog-plugin-mariadb/src/index.js +3 -3
  42. package/packages/datadog-plugin-mocha/src/index.js +35 -16
  43. package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
  44. package/packages/datadog-plugin-next/src/index.js +4 -4
  45. package/packages/datadog-plugin-openai/src/tracing.js +2 -3
  46. package/packages/datadog-plugin-playwright/src/index.js +35 -9
  47. package/packages/datadog-plugin-rhea/src/consumer.js +1 -1
  48. package/packages/datadog-plugin-router/src/index.js +2 -2
  49. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  50. package/packages/datadog-plugin-vitest/src/index.js +36 -12
  51. package/packages/dd-trace/src/appsec/graphql.js +6 -6
  52. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +3 -7
  53. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
  54. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +17 -30
  55. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +2 -2
  56. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -11
  57. package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
  58. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
  59. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +24 -4
  60. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +2 -2
  61. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +2 -2
  62. package/packages/dd-trace/src/appsec/iast/index.js +4 -2
  63. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
  64. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
  65. package/packages/dd-trace/src/appsec/iast/taint-tracking/constants.js +6 -0
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
  67. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +8 -8
  68. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +1 -1
  69. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +65 -0
  70. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +14 -5
  71. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +80 -2
  72. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
  73. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
  74. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -1
  75. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
  76. package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
  78. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
  79. package/packages/dd-trace/src/appsec/index.js +4 -4
  80. package/packages/dd-trace/src/appsec/rasp/command_injection.js +5 -5
  81. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +5 -5
  82. package/packages/dd-trace/src/appsec/rasp/lfi.js +3 -3
  83. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +4 -4
  84. package/packages/dd-trace/src/appsec/rasp/ssrf.js +3 -3
  85. package/packages/dd-trace/src/appsec/reporter.js +3 -3
  86. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  87. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +2 -0
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +31 -56
  90. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
  91. package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
  92. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
  93. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +3 -3
  94. package/packages/dd-trace/src/config.js +18 -3
  95. package/packages/dd-trace/src/data_streams_context.js +2 -2
  96. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  97. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
  98. package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
  99. package/packages/dd-trace/src/exporters/common/agents.js +1 -1
  100. package/packages/dd-trace/src/exporters/common/request.js +3 -3
  101. package/packages/dd-trace/src/iitm.js +2 -2
  102. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +1 -1
  103. package/packages/dd-trace/src/llmobs/tagger.js +12 -2
  104. package/packages/dd-trace/src/log/writer.js +3 -3
  105. package/packages/dd-trace/src/noop/span.js +1 -1
  106. package/packages/dd-trace/src/opentracing/propagation/text_map.js +5 -4
  107. package/packages/dd-trace/src/opentracing/span.js +3 -3
  108. package/packages/dd-trace/src/plugin_manager.js +3 -1
  109. package/packages/dd-trace/src/plugins/apollo.js +1 -1
  110. package/packages/dd-trace/src/plugins/ci_plugin.js +51 -4
  111. package/packages/dd-trace/src/plugins/database.js +14 -4
  112. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  113. package/packages/dd-trace/src/plugins/plugin.js +8 -8
  114. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  115. package/packages/dd-trace/src/plugins/util/git.js +3 -3
  116. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
  117. package/packages/dd-trace/src/plugins/util/test.js +10 -4
  118. package/packages/dd-trace/src/profiling/exporters/agent.js +3 -3
  119. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  120. package/packages/dd-trace/src/proxy.js +5 -1
  121. package/packages/dd-trace/src/ritm.js +2 -1
  122. package/packages/dd-trace/src/scope.js +5 -5
  123. package/packages/dd-trace/src/spanleak.js +0 -1
  124. package/packages/dd-trace/src/tracer.js +0 -14
  125. package/packages/memwatch/package.json +0 -9
@@ -10,11 +10,14 @@ const {
10
10
  getVulnerabilityCallSiteFrames,
11
11
  replaceCallSiteFromSourceMap
12
12
  } = require('../vulnerability-reporter')
13
+ const { getMarkFromVulnerabilityType } = require('../taint-tracking/secure-marks')
14
+ const { SUPPRESSED_VULNERABILITIES } = require('../telemetry/iast-metric')
13
15
 
14
16
  class Analyzer extends SinkIastPlugin {
15
17
  constructor (type) {
16
18
  super()
17
19
  this._type = type
20
+ this._secureMark = getMarkFromVulnerabilityType(type)
18
21
  }
19
22
 
20
23
  _isVulnerable (value, context) {
@@ -75,11 +78,17 @@ class Analyzer extends SinkIastPlugin {
75
78
  if (locationFromSourceMap?.path) {
76
79
  originalLocation.path = locationFromSourceMap.path
77
80
  }
81
+
78
82
  if (locationFromSourceMap?.line) {
79
83
  originalLocation.line = locationFromSourceMap.line
80
84
  }
81
- if (locationFromSourceMap?.column) {
82
- originalLocation.column = locationFromSourceMap.column
85
+
86
+ if (location?.class_name) {
87
+ originalLocation.class = location.class_name
88
+ }
89
+
90
+ if (location?.function) {
91
+ originalLocation.method = location.function
83
92
  }
84
93
 
85
94
  return originalLocation
@@ -91,7 +100,7 @@ class Analyzer extends SinkIastPlugin {
91
100
  return store && !iastContext
92
101
  }
93
102
 
94
- analyze (value, store = storage.getStore(), meta) {
103
+ analyze (value, store = storage('legacy').getStore(), meta) {
95
104
  const iastContext = getIastContext(store)
96
105
  if (this._isInvalidContext(store, iastContext)) return
97
106
 
@@ -99,7 +108,7 @@ class Analyzer extends SinkIastPlugin {
99
108
  }
100
109
 
101
110
  analyzeAll (...values) {
102
- const store = storage.getStore()
111
+ const store = storage('legacy').getStore()
103
112
  const iastContext = getIastContext(store)
104
113
  if (this._isInvalidContext(store, iastContext)) return
105
114
 
@@ -149,6 +158,17 @@ class Analyzer extends SinkIastPlugin {
149
158
  return hash
150
159
  }
151
160
 
161
+ _getSuppressedMetricTag () {
162
+ if (!this._suppressedMetricTag) {
163
+ this._suppressedMetricTag = SUPPRESSED_VULNERABILITIES.formatTags(this._type)[0]
164
+ }
165
+ return this._suppressedMetricTag
166
+ }
167
+
168
+ _incrementSuppressedMetric (iastContext) {
169
+ SUPPRESSED_VULNERABILITIES.inc(iastContext, this._getSuppressedMetricTag())
170
+ }
171
+
152
172
  addSub (iastSubOrChannelName, handler) {
153
173
  const iastSub = typeof iastSubOrChannelName === 'string'
154
174
  ? { channelName: iastSubOrChannelName }
@@ -48,7 +48,7 @@ class IastContextPlugin extends IastPlugin {
48
48
  let isRequestAcquired = false
49
49
  let iastContext
50
50
 
51
- const store = storage.getStore()
51
+ const store = storage('legacy').getStore()
52
52
  if (store) {
53
53
  const topContext = this.getTopContext()
54
54
  const rootSpan = this.getRootSpan(store)
@@ -70,7 +70,7 @@ class IastContextPlugin extends IastPlugin {
70
70
  }
71
71
 
72
72
  finishContext () {
73
- const store = storage.getStore()
73
+ const store = storage('legacy').getStore()
74
74
  if (store) {
75
75
  const topContext = this.getTopContext()
76
76
  const iastContext = iastContextFunctions.getIastContext(store, topContext)
@@ -62,12 +62,12 @@ class IastPlugin extends Plugin {
62
62
 
63
63
  _getTelemetryHandler (iastSub) {
64
64
  return () => {
65
- const iastContext = getIastContext(storage.getStore())
65
+ const iastContext = getIastContext(storage('legacy').getStore())
66
66
  iastSub.increaseExecuted(iastContext)
67
67
  }
68
68
  }
69
69
 
70
- _execHandlerAndIncMetric ({ handler, metric, tags, iastContext = getIastContext(storage.getStore()) }) {
70
+ _execHandlerAndIncMetric ({ handler, metric, tags, iastContext = getIastContext(storage('legacy').getStore()) }) {
71
71
  try {
72
72
  const result = handler()
73
73
  if (iastTelemetry.isEnabled()) {
@@ -15,6 +15,7 @@ const {
15
15
  const { IAST_ENABLED_TAG_KEY } = require('./tags')
16
16
  const iastTelemetry = require('./telemetry')
17
17
  const { enable: enableFsPlugin, disable: disableFsPlugin, IAST_MODULE } = require('../rasp/fs-plugin')
18
+ const securityControls = require('./security-controls')
18
19
 
19
20
  // TODO Change to `apm:http:server:request:[start|close]` when the subscription
20
21
  // order of the callbacks can be enforce
@@ -35,6 +36,7 @@ function enable (config, _tracer) {
35
36
  requestClose.subscribe(onIncomingHttpRequestEnd)
36
37
  overheadController.configure(config.iast)
37
38
  overheadController.startGlobalContext()
39
+ securityControls.configure(config.iast)
38
40
  vulnerabilityReporter.start(config, _tracer)
39
41
 
40
42
  isEnabled = true
@@ -57,7 +59,7 @@ function disable () {
57
59
 
58
60
  function onIncomingHttpRequestStart (data) {
59
61
  if (data?.req) {
60
- const store = storage.getStore()
62
+ const store = storage('legacy').getStore()
61
63
  if (store) {
62
64
  const topContext = web.getContext(data.req)
63
65
  if (topContext) {
@@ -82,7 +84,7 @@ function onIncomingHttpRequestStart (data) {
82
84
 
83
85
  function onIncomingHttpRequestEnd (data) {
84
86
  if (data?.req) {
85
- const store = storage.getStore()
87
+ const store = storage('legacy').getStore()
86
88
  const topContext = web.getContext(data.req)
87
89
  const iastContext = iastContextFunctions.getIastContext(store, topContext)
88
90
  if (iastContext?.rootSpan) {
@@ -0,0 +1,187 @@
1
+ 'use strict'
2
+
3
+ const path = require('path')
4
+ const dc = require('dc-polyfill')
5
+ const { storage } = require('../../../../../datadog-core')
6
+ const shimmer = require('../../../../../datadog-shimmer')
7
+ const log = require('../../../log')
8
+ const { parse, SANITIZER_TYPE } = require('./parser')
9
+ const TaintTrackingOperations = require('../taint-tracking/operations')
10
+ const { getIastContext } = require('../iast-context')
11
+ const { iterateObjectStrings } = require('../utils')
12
+
13
+ // esm
14
+ const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
15
+
16
+ // cjs
17
+ const moduleLoadEndChannel = dc.channel('dd-trace:moduleLoadEnd')
18
+
19
+ let controls
20
+ let controlsKeys
21
+ let hooks
22
+
23
+ function configure (iastConfig) {
24
+ if (!iastConfig?.securityControlsConfiguration) return
25
+
26
+ try {
27
+ controls = parse(iastConfig.securityControlsConfiguration)
28
+ if (controls?.size > 0) {
29
+ hooks = new WeakSet()
30
+ controlsKeys = [...controls.keys()]
31
+
32
+ moduleLoadStartChannel.subscribe(onModuleLoaded)
33
+ moduleLoadEndChannel.subscribe(onModuleLoaded)
34
+ }
35
+ } catch (e) {
36
+ log.error('[ASM] Error configuring IAST Security Controls', e)
37
+ }
38
+ }
39
+
40
+ function onModuleLoaded (payload) {
41
+ if (!payload?.module || hooks?.has(payload.module)) return
42
+
43
+ const { filename, module } = payload
44
+
45
+ const controlsByFile = getControls(filename)
46
+ if (controlsByFile) {
47
+ const hook = hookModule(filename, module, controlsByFile)
48
+ payload.module = hook
49
+ hooks.add(hook)
50
+ }
51
+ }
52
+
53
+ function getControls (filename) {
54
+ if (filename.startsWith('file://')) {
55
+ filename = filename.substring(7)
56
+ }
57
+
58
+ let key = path.isAbsolute(filename) ? path.relative(process.cwd(), filename) : filename
59
+ key = key.replaceAll(path.sep, path.posix.sep)
60
+
61
+ if (key.includes('node_modules')) {
62
+ key = controlsKeys.find(file => key.endsWith(file))
63
+ }
64
+
65
+ return controls.get(key)
66
+ }
67
+
68
+ function hookModule (filename, module, controlsByFile) {
69
+ try {
70
+ controlsByFile.forEach(({ type, method, parameters, secureMarks }) => {
71
+ const { target, parent, methodName } = resolve(method, module)
72
+ if (!target) {
73
+ log.error('[ASM] Unable to resolve IAST security control %s:%s', filename, method)
74
+ return
75
+ }
76
+
77
+ let wrapper
78
+ if (type === SANITIZER_TYPE) {
79
+ wrapper = wrapSanitizer(target, secureMarks)
80
+ } else {
81
+ wrapper = wrapInputValidator(target, parameters, secureMarks)
82
+ }
83
+
84
+ if (methodName) {
85
+ parent[methodName] = wrapper
86
+ } else {
87
+ module = wrapper
88
+ }
89
+ })
90
+ } catch (e) {
91
+ log.error('[ASM] Error initializing IAST security control for %', filename, e)
92
+ }
93
+
94
+ return module
95
+ }
96
+
97
+ function resolve (path, obj, separator = '.') {
98
+ if (!path) {
99
+ // esm module with default export
100
+ if (obj?.default) {
101
+ return { target: obj.default, parent: obj, methodName: 'default' }
102
+ } else {
103
+ return { target: obj, parent: obj }
104
+ }
105
+ }
106
+
107
+ const properties = path.split(separator)
108
+
109
+ let parent
110
+ let methodName
111
+ const target = properties.reduce((prev, curr) => {
112
+ parent = prev
113
+ methodName = curr
114
+ return prev?.[curr]
115
+ }, obj)
116
+
117
+ return { target, parent, methodName }
118
+ }
119
+
120
+ function wrapSanitizer (target, secureMarks) {
121
+ return shimmer.wrapFunction(target, orig => function () {
122
+ const result = orig.apply(this, arguments)
123
+
124
+ try {
125
+ return addSecureMarks(result, secureMarks)
126
+ } catch (e) {
127
+ log.error('[ASM] Error adding Secure mark for sanitizer', e)
128
+ }
129
+
130
+ return result
131
+ })
132
+ }
133
+
134
+ function wrapInputValidator (target, parameters, secureMarks) {
135
+ const allParameters = !parameters?.length
136
+
137
+ return shimmer.wrapFunction(target, orig => function () {
138
+ try {
139
+ [...arguments].forEach((arg, index) => {
140
+ if (allParameters || parameters.includes(index)) {
141
+ addSecureMarks(arg, secureMarks, false)
142
+ }
143
+ })
144
+ } catch (e) {
145
+ log.error('[ASM] Error adding Secure mark for input validator', e)
146
+ }
147
+
148
+ return orig.apply(this, arguments)
149
+ })
150
+ }
151
+
152
+ function addSecureMarks (value, secureMarks, createNewTainted = true) {
153
+ if (!value) return
154
+
155
+ const store = storage('legacy').getStore()
156
+ const iastContext = getIastContext(store)
157
+
158
+ if (typeof value === 'string') {
159
+ return TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted)
160
+ } else {
161
+ iterateObjectStrings(value, (value, levelKeys, parent, lastKey) => {
162
+ try {
163
+ const securedTainted = TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted)
164
+ if (createNewTainted) {
165
+ parent[lastKey] = securedTainted
166
+ }
167
+ } catch (e) {
168
+ // if it is a readonly property, do nothing
169
+ }
170
+ })
171
+ return value
172
+ }
173
+ }
174
+
175
+ function disable () {
176
+ if (moduleLoadStartChannel.hasSubscribers) moduleLoadStartChannel.unsubscribe(onModuleLoaded)
177
+ if (moduleLoadEndChannel.hasSubscribers) moduleLoadEndChannel.unsubscribe(onModuleLoaded)
178
+
179
+ controls = undefined
180
+ controlsKeys = undefined
181
+ hooks = undefined
182
+ }
183
+
184
+ module.exports = {
185
+ configure,
186
+ disable
187
+ }
@@ -0,0 +1,96 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../../log')
4
+ const { getMarkFromVulnerabilityType, CUSTOM_SECURE_MARK } = require('../taint-tracking/secure-marks')
5
+
6
+ const SECURITY_CONTROL_DELIMITER = ';'
7
+ const SECURITY_CONTROL_FIELD_DELIMITER = ':'
8
+ const SECURITY_CONTROL_ELEMENT_DELIMITER = ','
9
+
10
+ const INPUT_VALIDATOR_TYPE = 'INPUT_VALIDATOR'
11
+ const SANITIZER_TYPE = 'SANITIZER'
12
+
13
+ const validTypes = [INPUT_VALIDATOR_TYPE, SANITIZER_TYPE]
14
+
15
+ function parse (securityControlsConfiguration) {
16
+ const controls = new Map()
17
+
18
+ securityControlsConfiguration?.replace(/[\r\n\t\v\f]*/g, '')
19
+ .split(SECURITY_CONTROL_DELIMITER)
20
+ .map(parseControl)
21
+ .filter(control => !!control)
22
+ .forEach(control => {
23
+ if (!controls.has(control.file)) {
24
+ controls.set(control.file, [])
25
+ }
26
+ controls.get(control.file).push(control)
27
+ })
28
+
29
+ return controls
30
+ }
31
+
32
+ function parseControl (control) {
33
+ if (!control) return
34
+
35
+ const fields = control.split(SECURITY_CONTROL_FIELD_DELIMITER)
36
+
37
+ if (fields.length < 3 || fields.length > 5) {
38
+ log.warn('[ASM] Security control configuration is invalid: %s', control)
39
+ return
40
+ }
41
+
42
+ let [type, marks, file, method, parameters] = fields
43
+
44
+ type = type.trim().toUpperCase()
45
+ if (!validTypes.includes(type)) {
46
+ log.warn('[ASM] Invalid security control type: %s', type)
47
+ return
48
+ }
49
+
50
+ let secureMarks = CUSTOM_SECURE_MARK
51
+ getSecureMarks(marks).forEach(mark => { secureMarks |= mark })
52
+ if (secureMarks === CUSTOM_SECURE_MARK) {
53
+ log.warn('[ASM] Invalid security control mark: %s', marks)
54
+ return
55
+ }
56
+
57
+ file = file?.trim()
58
+
59
+ method = method?.trim()
60
+
61
+ try {
62
+ parameters = getParameters(parameters)
63
+ } catch (e) {
64
+ log.warn('[ASM] Invalid non-numeric security control parameter %s', parameters)
65
+ return
66
+ }
67
+
68
+ return { type, secureMarks, file, method, parameters }
69
+ }
70
+
71
+ function getSecureMarks (marks) {
72
+ return marks?.split(SECURITY_CONTROL_ELEMENT_DELIMITER)
73
+ .map(getMarkFromVulnerabilityType)
74
+ .filter(mark => !!mark)
75
+ }
76
+
77
+ function getParameters (parameters) {
78
+ return parameters?.split(SECURITY_CONTROL_ELEMENT_DELIMITER)
79
+ .map(param => {
80
+ const parsedParam = parseInt(param, 10)
81
+
82
+ // discard the securityControl if there is an incorrect parameter
83
+ if (isNaN(parsedParam)) {
84
+ throw new Error('Invalid non-numeric security control parameter')
85
+ }
86
+
87
+ return parsedParam
88
+ })
89
+ }
90
+
91
+ module.exports = {
92
+ parse,
93
+
94
+ INPUT_VALIDATOR_TYPE,
95
+ SANITIZER_TYPE
96
+ }
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ LOG_MESSAGE: 'LOG',
5
+ REWRITTEN_MESSAGE: 'REWRITTEN'
6
+ }
@@ -84,10 +84,10 @@ function getRanges (iastContext, string) {
84
84
  return result
85
85
  }
86
86
 
87
- function addSecureMark (iastContext, string, mark) {
87
+ function addSecureMark (iastContext, string, mark, createNewTainted = true) {
88
88
  const transactionId = iastContext?.[IAST_TRANSACTION_ID]
89
89
  if (transactionId) {
90
- return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark)
90
+ return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark, createNewTainted)
91
91
  }
92
92
 
93
93
  return string
@@ -39,7 +39,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
39
39
 
40
40
  onConfigure () {
41
41
  const onRequestBody = ({ req }) => {
42
- const iastContext = getIastContext(storage.getStore())
42
+ const iastContext = getIastContext(storage('legacy').getStore())
43
43
  if (iastContext && iastContext.body !== req.body) {
44
44
  this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
45
45
  iastContext.body = req.body
@@ -70,7 +70,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
70
70
  { channelName: 'apm:express:middleware:next', tag: HTTP_REQUEST_BODY },
71
71
  ({ req }) => {
72
72
  if (req && req.body !== null && typeof req.body === 'object') {
73
- const iastContext = getIastContext(storage.getStore())
73
+ const iastContext = getIastContext(storage('legacy').getStore())
74
74
  if (iastContext && iastContext.body !== req.body) {
75
75
  this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
76
76
  iastContext.body = req.body
@@ -115,7 +115,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
115
115
  this.addSub(
116
116
  { channelName: 'apm:graphql:resolve:start', tag: HTTP_REQUEST_BODY },
117
117
  (data) => {
118
- const iastContext = getIastContext(storage.getStore())
118
+ const iastContext = getIastContext(storage('legacy').getStore())
119
119
  const source = data.context?.source
120
120
  const ranges = source && getRanges(iastContext, source)
121
121
  if (ranges?.length) {
@@ -128,7 +128,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
128
128
  this.addSub(
129
129
  { channelName: 'datadog:url:parse:finish' },
130
130
  ({ input, base, parsed, isURL }) => {
131
- const iastContext = getIastContext(storage.getStore())
131
+ const iastContext = getIastContext(storage('legacy').getStore())
132
132
  let ranges
133
133
 
134
134
  if (base) {
@@ -157,7 +157,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
157
157
  const origRange = this._taintedURLs.get(context.urlObject)
158
158
  if (!origRange) return
159
159
 
160
- const iastContext = getIastContext(storage.getStore())
160
+ const iastContext = getIastContext(storage('legacy').getStore())
161
161
  if (!iastContext) return
162
162
 
163
163
  context.result =
@@ -168,7 +168,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
168
168
  this.addInstrumentedSource('http', [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME])
169
169
  }
170
170
 
171
- _taintTrackingHandler (type, target, property, iastContext = getIastContext(storage.getStore())) {
171
+ _taintTrackingHandler (type, target, property, iastContext = getIastContext(storage('legacy').getStore())) {
172
172
  if (!property) {
173
173
  taintObject(iastContext, target, type)
174
174
  } else if (target[property]) {
@@ -177,7 +177,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
177
177
  }
178
178
 
179
179
  _cookiesTaintTrackingHandler (target) {
180
- const iastContext = getIastContext(storage.getStore())
180
+ const iastContext = getIastContext(storage('legacy').getStore())
181
181
  // Prevent tainting cookie names since it leads to taint literal string with same value.
182
182
  taintObject(iastContext, target, HTTP_REQUEST_COOKIE_VALUE)
183
183
  }
@@ -206,7 +206,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
206
206
  this.taintUrl(req, iastContext)
207
207
  }
208
208
 
209
- _taintDatabaseResult (result, dbOrigin, iastContext = getIastContext(storage.getStore()), name) {
209
+ _taintDatabaseResult (result, dbOrigin, iastContext = getIastContext(storage('legacy').getStore()), name) {
210
210
  if (!iastContext) return result
211
211
 
212
212
  if (this._rowsToTaint === 0) return result
@@ -22,7 +22,7 @@ class KafkaConsumerIastPlugin extends SourceIastPlugin {
22
22
  }
23
23
 
24
24
  taintKafkaMessage (message) {
25
- const iastContext = getIastContext(storage.getStore())
25
+ const iastContext = getIastContext(storage('legacy').getStore())
26
26
 
27
27
  if (iastContext && message) {
28
28
  const { key, value } = message
@@ -0,0 +1,65 @@
1
+ 'use strict'
2
+
3
+ import path from 'path'
4
+ import { URL } from 'url'
5
+ import { getName } from '../telemetry/verbosity.js'
6
+ import { isNotLibraryFile, isPrivateModule } from './filter.js'
7
+ import constants from './constants.js'
8
+
9
+ const currentUrl = new URL(import.meta.url)
10
+ const ddTraceDir = path.join(currentUrl.pathname, '..', '..', '..', '..', '..', '..')
11
+
12
+ let port, rewriter
13
+
14
+ export async function initialize (data) {
15
+ if (rewriter) return Promise.reject(new Error('ALREADY INITIALIZED'))
16
+
17
+ const { csiMethods, telemetryVerbosity, chainSourceMap } = data
18
+ port = data.port
19
+
20
+ const iastRewriter = await import('@datadog/native-iast-rewriter')
21
+
22
+ const { NonCacheRewriter } = iastRewriter.default
23
+
24
+ rewriter = new NonCacheRewriter({
25
+ csiMethods,
26
+ telemetryVerbosity: getName(telemetryVerbosity),
27
+ chainSourceMap
28
+ })
29
+ }
30
+
31
+ export async function load (url, context, nextLoad) {
32
+ const result = await nextLoad(url, context)
33
+
34
+ if (!port) return result
35
+ if (!result.source) return result
36
+ if (url.includes(ddTraceDir) || url.includes('iitm=true')) return result
37
+
38
+ try {
39
+ if (isPrivateModule(url) && isNotLibraryFile(url)) {
40
+ const rewritten = rewriter.rewrite(result.source.toString(), url)
41
+
42
+ if (rewritten?.content) {
43
+ result.source = rewritten.content || result.source
44
+ const data = { url, rewritten }
45
+ port.postMessage({ type: constants.REWRITTEN_MESSAGE, data })
46
+ }
47
+ }
48
+ } catch (e) {
49
+ const newErrObject = {
50
+ message: e.message,
51
+ stack: e.stack
52
+ }
53
+
54
+ const data = {
55
+ level: 'error',
56
+ messages: ['[ASM] Error rewriting file %s', url, newErrObject]
57
+ }
58
+ port.postMessage({
59
+ type: constants.LOG_MESSAGE,
60
+ data
61
+ })
62
+ }
63
+
64
+ return result
65
+ }
@@ -12,10 +12,7 @@ const telemetryRewriter = {
12
12
  information (content, filename, rewriter) {
13
13
  const response = this.off(content, filename, rewriter)
14
14
 
15
- const metrics = response.metrics
16
- if (metrics && metrics.instrumentedPropagation) {
17
- INSTRUMENTED_PROPAGATION.inc(undefined, metrics.instrumentedPropagation)
18
- }
15
+ incrementTelemetry(response.metrics)
19
16
 
20
17
  return response
21
18
  }
@@ -30,4 +27,16 @@ function getRewriteFunction (rewriter) {
30
27
  }
31
28
  }
32
29
 
33
- module.exports = { getRewriteFunction }
30
+ function incrementTelemetry (metrics) {
31
+ if (metrics?.instrumentedPropagation) {
32
+ INSTRUMENTED_PROPAGATION.inc(undefined, metrics.instrumentedPropagation)
33
+ }
34
+ }
35
+
36
+ function incrementTelemetryIfNeeded (metrics) {
37
+ if (iastTelemetry.verbosity !== Verbosity.OFF) {
38
+ incrementTelemetry(metrics)
39
+ }
40
+ }
41
+
42
+ module.exports = { getRewriteFunction, incrementTelemetryIfNeeded }