dd-trace 3.17.1 → 3.19.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 (97) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/esbuild.js +3 -0
  3. package/index.d.ts +10 -9
  4. package/package.json +12 -12
  5. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  6. package/packages/datadog-esbuild/index.js +9 -2
  7. package/packages/datadog-instrumentations/src/body-parser.js +15 -9
  8. package/packages/datadog-instrumentations/src/cucumber.js +11 -1
  9. package/packages/datadog-instrumentations/src/express.js +32 -0
  10. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  11. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  12. package/packages/datadog-instrumentations/src/http/server.js +2 -1
  13. package/packages/datadog-instrumentations/src/jest.js +6 -3
  14. package/packages/datadog-instrumentations/src/mocha.js +19 -2
  15. package/packages/datadog-instrumentations/src/playwright.js +5 -2
  16. package/packages/datadog-plugin-amqp10/src/consumer.js +1 -3
  17. package/packages/datadog-plugin-amqp10/src/producer.js +1 -3
  18. package/packages/datadog-plugin-amqplib/src/client.js +4 -3
  19. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  20. package/packages/datadog-plugin-amqplib/src/producer.js +1 -3
  21. package/packages/datadog-plugin-cucumber/src/index.js +6 -4
  22. package/packages/datadog-plugin-cypress/src/plugin.js +5 -1
  23. package/packages/datadog-plugin-cypress/src/support.js +4 -0
  24. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -3
  25. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -3
  26. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -3
  27. package/packages/datadog-plugin-http/src/client.js +1 -1
  28. package/packages/datadog-plugin-http/src/server.js +2 -2
  29. package/packages/datadog-plugin-http2/src/server.js +0 -5
  30. package/packages/datadog-plugin-jest/src/index.js +10 -5
  31. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -4
  32. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -3
  33. package/packages/datadog-plugin-mocha/src/index.js +9 -4
  34. package/packages/datadog-plugin-playwright/src/index.js +6 -5
  35. package/packages/datadog-plugin-redis/src/index.js +16 -5
  36. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  37. package/packages/datadog-plugin-rhea/src/producer.js +1 -5
  38. package/packages/dd-trace/src/appsec/addresses.js +0 -3
  39. package/packages/dd-trace/src/appsec/blocked_templates.js +2 -9
  40. package/packages/dd-trace/src/appsec/blocking.js +1 -1
  41. package/packages/dd-trace/src/appsec/{gateway/channels.js → channels.js} +4 -4
  42. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +21 -13
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -1
  45. package/packages/dd-trace/src/appsec/iast/telemetry/logs.js +1 -1
  46. package/packages/dd-trace/src/appsec/index.js +87 -79
  47. package/packages/dd-trace/src/appsec/recommended.json +448 -121
  48. package/packages/dd-trace/src/appsec/remote_config/apply_states.js +7 -0
  49. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  50. package/packages/dd-trace/src/appsec/remote_config/index.js +30 -11
  51. package/packages/dd-trace/src/appsec/remote_config/manager.js +33 -12
  52. package/packages/dd-trace/src/appsec/reporter.js +27 -58
  53. package/packages/dd-trace/src/appsec/rule_manager.js +160 -32
  54. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +4 -12
  55. package/packages/dd-trace/src/appsec/waf/index.js +75 -0
  56. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +57 -0
  57. package/packages/dd-trace/src/appsec/waf/waf_manager.js +66 -0
  58. package/packages/dd-trace/src/config.js +18 -1
  59. package/packages/dd-trace/src/dcitm.js +2 -0
  60. package/packages/dd-trace/src/encode/0.4.js +12 -4
  61. package/packages/dd-trace/src/encode/tags-processors.js +40 -68
  62. package/packages/dd-trace/src/exporters/common/request.js +2 -2
  63. package/packages/dd-trace/src/format.js +2 -1
  64. package/packages/dd-trace/src/iitm.js +1 -1
  65. package/packages/dd-trace/src/log/channels.js +11 -12
  66. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
  67. package/packages/dd-trace/src/plugin_manager.js +3 -1
  68. package/packages/dd-trace/src/plugins/client.js +3 -2
  69. package/packages/dd-trace/src/plugins/consumer.js +17 -2
  70. package/packages/dd-trace/src/plugins/inbound.js +7 -0
  71. package/packages/dd-trace/src/plugins/{outgoing.js → outbound.js} +2 -2
  72. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  73. package/packages/dd-trace/src/plugins/producer.js +17 -2
  74. package/packages/dd-trace/src/plugins/server.js +2 -2
  75. package/packages/dd-trace/src/plugins/tracing.js +11 -0
  76. package/packages/dd-trace/src/plugins/util/test.js +19 -1
  77. package/packages/dd-trace/src/profiling/profilers/cpu.js +1 -1
  78. package/packages/dd-trace/src/ritm.js +1 -1
  79. package/packages/dd-trace/src/service-naming/index.js +41 -0
  80. package/packages/dd-trace/src/service-naming/schemas/definition.js +28 -0
  81. package/packages/dd-trace/src/service-naming/schemas/index.js +6 -0
  82. package/packages/dd-trace/src/service-naming/schemas/v0.js +66 -0
  83. package/packages/dd-trace/src/service-naming/schemas/v1.js +58 -0
  84. package/packages/dd-trace/src/span_stats.js +1 -1
  85. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  86. package/packages/dd-trace/src/telemetry/index.js +1 -1
  87. package/packages/diagnostics_channel/index.js +3 -0
  88. package/packages/diagnostics_channel/src/index.js +57 -0
  89. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +0 -137
  90. package/packages/dd-trace/src/appsec/callbacks/index.js +0 -7
  91. package/packages/dd-trace/src/appsec/gateway/als.js +0 -6
  92. package/packages/dd-trace/src/appsec/gateway/engine/engine.js +0 -140
  93. package/packages/dd-trace/src/appsec/gateway/engine/index.js +0 -51
  94. package/packages/dd-trace/src/appsec/gateway/engine/runner.js +0 -42
  95. package/packages/dd-trace/src/instrumenter.js +0 -203
  96. package/packages/dd-trace/src/loader.js +0 -131
  97. package/packages/dd-trace/src/plugins/incoming.js +0 -7
@@ -3,7 +3,7 @@
3
3
  const Plugin = require('../../dd-trace/src/plugins/plugin')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const web = require('../../dd-trace/src/plugins/util/web')
6
- const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/gateway/channels')
6
+ const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/channels')
7
7
  const { COMPONENT } = require('../../dd-trace/src/constants')
8
8
 
9
9
  class HttpServerPlugin extends Plugin {
@@ -33,7 +33,7 @@ class HttpServerPlugin extends Plugin {
33
33
  }
34
34
 
35
35
  if (incomingHttpRequestStart.hasSubscribers) {
36
- incomingHttpRequestStart.publish({ req, res, abortController })
36
+ incomingHttpRequestStart.publish({ req, res, abortController }) // TODO: no need to make a new object here
37
37
  }
38
38
  })
39
39
 
@@ -5,7 +5,6 @@
5
5
  const Plugin = require('../../dd-trace/src/plugins/plugin')
6
6
  const { storage } = require('../../datadog-core')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
- const { incomingHttpRequestStart } = require('../../dd-trace/src/appsec/gateway/channels')
9
8
  const { COMPONENT } = require('../../dd-trace/src/constants')
10
9
 
11
10
  class Http2ServerPlugin extends Plugin {
@@ -30,10 +29,6 @@ class Http2ServerPlugin extends Plugin {
30
29
  context.res.writeHead = web.wrapWriteHead(context)
31
30
  context.instrumented = true
32
31
  }
33
-
34
- if (incomingHttpRequestStart.hasSubscribers) {
35
- incomingHttpRequestStart.publish({ req, res })
36
- }
37
32
  })
38
33
 
39
34
  this.addSub('apm:http2:server:request:error', (error) => {
@@ -9,7 +9,8 @@ const {
9
9
  addIntelligentTestRunnerSpanTags,
10
10
  TEST_PARAMETERS,
11
11
  TEST_COMMAND,
12
- TEST_FRAMEWORK_VERSION
12
+ TEST_FRAMEWORK_VERSION,
13
+ TEST_SOURCE_START
13
14
  } = require('../../dd-trace/src/plugins/util/test')
14
15
  const { COMPONENT } = require('../../dd-trace/src/constants')
15
16
  const id = require('../../dd-trace/src/id')
@@ -31,8 +32,11 @@ class JestPlugin extends CiPlugin {
31
32
  // Used to handle the end of a jest worker to be able to flush
32
33
  const handler = ([message]) => {
33
34
  if (message === CHILD_MESSAGE_END) {
34
- this.testSuiteSpan.finish()
35
- finishAllTraceSpans(this.testSuiteSpan)
35
+ // testSuiteSpan is not defined for older versions of jest, where jest-jasmine2 is still used
36
+ if (this.testSuiteSpan) {
37
+ this.testSuiteSpan.finish()
38
+ finishAllTraceSpans(this.testSuiteSpan)
39
+ }
36
40
  this.tracer._exporter.flush()
37
41
  process.removeListener('message', handler)
38
42
  }
@@ -187,12 +191,13 @@ class JestPlugin extends CiPlugin {
187
191
  }
188
192
 
189
193
  startTestSpan (test) {
190
- const { suite, name, runner, testParameters, frameworkVersion } = test
194
+ const { suite, name, runner, testParameters, frameworkVersion, testStartLine } = test
191
195
 
192
196
  const extraTags = {
193
197
  [JEST_TEST_RUNNER]: runner,
194
198
  [TEST_PARAMETERS]: testParameters,
195
- [TEST_FRAMEWORK_VERSION]: frameworkVersion
199
+ [TEST_FRAMEWORK_VERSION]: frameworkVersion,
200
+ [TEST_SOURCE_START]: testStartLine
196
201
  }
197
202
 
198
203
  return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
@@ -8,12 +8,9 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
8
8
 
9
9
  start ({ topic, partition, message }) {
10
10
  const childOf = extract(this.tracer, message.headers)
11
-
12
- this.startSpan('kafka.consume', {
11
+ this.startSpan({
13
12
  childOf,
14
- service: this.config.service || `${this.tracer._service}-kafka`,
15
13
  resource: topic,
16
- kind: 'consumer',
17
14
  type: 'worker',
18
15
  meta: {
19
16
  'component': 'kafkajs',
@@ -7,10 +7,8 @@ class KafkajsProducerPlugin extends ProducerPlugin {
7
7
  static get operation () { return 'produce' }
8
8
 
9
9
  start ({ topic, messages }) {
10
- const span = this.startSpan('kafka.produce', {
11
- service: this.config.service || `${this.tracer._service}-kafka`,
10
+ const span = this.startSpan({
12
11
  resource: topic,
13
- kind: 'producer',
14
12
  meta: {
15
13
  'component': 'kafkajs',
16
14
  'kafka.topic': topic
@@ -10,7 +10,8 @@ const {
10
10
  getTestSuitePath,
11
11
  getTestParametersString,
12
12
  getTestSuiteCommonTags,
13
- addIntelligentTestRunnerSpanTags
13
+ addIntelligentTestRunnerSpanTags,
14
+ TEST_SOURCE_START
14
15
  } = require('../../dd-trace/src/plugins/util/test')
15
16
  const { COMPONENT } = require('../../dd-trace/src/constants')
16
17
 
@@ -85,9 +86,9 @@ class MochaPlugin extends CiPlugin {
85
86
  }
86
87
  })
87
88
 
88
- this.addSub('ci:mocha:test:start', (test) => {
89
+ this.addSub('ci:mocha:test:start', ({ test, testStartLine }) => {
89
90
  const store = storage.getStore()
90
- const span = this.startTestSpan(test)
91
+ const span = this.startTestSpan(test, testStartLine)
91
92
 
92
93
  this.enter(span, store)
93
94
  })
@@ -153,7 +154,7 @@ class MochaPlugin extends CiPlugin {
153
154
  })
154
155
  }
155
156
 
156
- startTestSpan (test) {
157
+ startTestSpan (test, testStartLine) {
157
158
  const testName = test.fullTitle()
158
159
  const { file: testSuiteAbsolutePath, title } = test
159
160
 
@@ -163,6 +164,10 @@ class MochaPlugin extends CiPlugin {
163
164
  extraTags[TEST_PARAMETERS] = testParametersString
164
165
  }
165
166
 
167
+ if (testStartLine) {
168
+ extraTags[TEST_SOURCE_START] = testStartLine
169
+ }
170
+
166
171
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
167
172
  const testSuiteSpan = this._testSuites.get(testSuiteAbsolutePath)
168
173
 
@@ -7,7 +7,8 @@ const {
7
7
  TEST_STATUS,
8
8
  finishAllTraceSpans,
9
9
  getTestSuitePath,
10
- getTestSuiteCommonTags
10
+ getTestSuiteCommonTags,
11
+ TEST_SOURCE_START
11
12
  } = require('../../dd-trace/src/plugins/util/test')
12
13
  const { RESOURCE_NAME } = require('../../../ext/tags')
13
14
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -64,10 +65,10 @@ class PlaywrightPlugin extends CiPlugin {
64
65
  span.finish()
65
66
  })
66
67
 
67
- this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath }) => {
68
+ this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine }) => {
68
69
  const store = storage.getStore()
69
70
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
70
- const span = this.startTestSpan(testName, testSuite)
71
+ const span = this.startTestSpan(testName, testSuite, testSourceLine)
71
72
 
72
73
  this.enter(span, store)
73
74
  })
@@ -104,9 +105,9 @@ class PlaywrightPlugin extends CiPlugin {
104
105
  })
105
106
  }
106
107
 
107
- startTestSpan (testName, testSuite) {
108
+ startTestSpan (testName, testSuite, testSourceLine) {
108
109
  const testSuiteSpan = this._testSuites.get(testSuite)
109
- return super.startTestSpan(testName, testSuite, testSuiteSpan)
110
+ return super.startTestSpan(testName, testSuite, testSuiteSpan, { [TEST_SOURCE_START]: testSourceLine })
110
111
  }
111
112
  }
112
113
 
@@ -9,17 +9,19 @@ class RedisPlugin extends CachePlugin {
9
9
  static get system () { return 'redis' }
10
10
 
11
11
  start ({ db, command, args, connectionOptions = {}, connectionName }) {
12
- if (!this.config.filter(command)) return this.skip()
12
+ const resource = command
13
+ const normalizedCommand = command.toUpperCase()
14
+ if (!this.config.filter(normalizedCommand)) return this.skip()
13
15
 
14
16
  this.startSpan('redis.command', {
15
17
  service: getService(this.config, connectionName),
16
- resource: command,
18
+ resource,
17
19
  type: 'redis',
18
20
  kind: 'client',
19
21
  meta: {
20
22
  'db.type': 'redis',
21
23
  'db.name': db || '0',
22
- 'redis.raw_command': formatCommand(command, args),
24
+ 'redis.raw_command': formatCommand(normalizedCommand, args),
23
25
  'out.host': connectionOptions.host,
24
26
  [CLIENT_PORT_KEY]: connectionOptions.port
25
27
  }
@@ -42,8 +44,6 @@ function getService (config, connectionName) {
42
44
  }
43
45
 
44
46
  function formatCommand (command, args) {
45
- command = command.toUpperCase()
46
-
47
47
  if (!args || command === 'AUTH') return command
48
48
 
49
49
  for (let i = 0, l = args.length; i < l; i++) {
@@ -76,6 +76,11 @@ function trim (str, maxlen) {
76
76
  }
77
77
 
78
78
  function normalizeConfig (config) {
79
+ if (config.allowlist) uppercaseAllEntries(config.allowlist)
80
+ if (config.whitelist) uppercaseAllEntries(config.whitelist)
81
+ if (config.blocklist) uppercaseAllEntries(config.blocklist)
82
+ if (config.blacklist) uppercaseAllEntries(config.blacklist)
83
+
79
84
  const filter = urlFilter.getFilter(config)
80
85
 
81
86
  return Object.assign({}, config, {
@@ -83,4 +88,10 @@ function normalizeConfig (config) {
83
88
  })
84
89
  }
85
90
 
91
+ function uppercaseAllEntries (entries) {
92
+ for (let i = 0; i < entries.length; i++) {
93
+ entries[i] = String(entries[i]).toUpperCase()
94
+ }
95
+ }
96
+
86
97
  module.exports = RedisPlugin
@@ -19,12 +19,10 @@ class RheaConsumerPlugin extends ConsumerPlugin {
19
19
  const name = getResourceNameFromMessage(msgObj)
20
20
  const childOf = extractTextMap(msgObj, this.tracer)
21
21
 
22
- this.startSpan('amqp.receive', {
22
+ this.startSpan({
23
23
  childOf,
24
- service: this.config.service,
25
24
  resource: name,
26
25
  type: 'worker',
27
- kind: 'consumer',
28
26
  meta: {
29
27
  'component': 'rhea',
30
28
  'amqp.link.source.address': name,
@@ -9,17 +9,13 @@ class RheaProducerPlugin extends ProducerPlugin {
9
9
 
10
10
  constructor (...args) {
11
11
  super(...args)
12
-
13
12
  this.addTraceSub('encode', this.encode.bind(this))
14
13
  }
15
14
 
16
15
  start ({ targetAddress, host, port }) {
17
16
  const name = targetAddress || 'amq.topic'
18
-
19
- this.startSpan('amqp.send', {
20
- service: this.config.service || `${this.tracer._service}-amqp-producer`,
17
+ this.startSpan({
21
18
  resource: name,
22
- kind: 'producer',
23
19
  meta: {
24
20
  'component': 'rhea',
25
21
  'amqp.link.target.address': name,
@@ -7,14 +7,11 @@ module.exports = {
7
7
  // TODO: 'server.request.trailers',
8
8
  HTTP_INCOMING_URL: 'server.request.uri.raw',
9
9
  HTTP_INCOMING_METHOD: 'server.request.method',
10
- HTTP_INCOMING_ENDPOINT: 'server.request.framework_endpoint',
11
10
  HTTP_INCOMING_PARAMS: 'server.request.path_params',
12
11
  HTTP_INCOMING_COOKIES: 'server.request.cookies',
13
12
  HTTP_INCOMING_RESPONSE_CODE: 'server.response.status',
14
13
  HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
15
14
  // TODO: 'server.response.trailers',
16
- HTTP_INCOMING_REMOTE_IP: 'server.request.client_ip',
17
- HTTP_INCOMING_REMOTE_PORT: 'server.request.client_port',
18
15
 
19
16
  HTTP_CLIENT_IP: 'http.client_ip',
20
17
 
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable max-len */
2
2
  'use strict'
3
3
 
4
- const html = `<!-- Sorry, you've been blocked -->
4
+ const html = `<!-- Sorry, youve been blocked -->
5
5
  <!DOCTYPE html>
6
6
  <html lang="en">
7
7
 
@@ -102,14 +102,7 @@ const html = `<!-- Sorry, you've been blocked -->
102
102
  </html>
103
103
  `
104
104
 
105
- const json = `{
106
- "errors": [
107
- {
108
- "title": "You've been blocked",
109
- "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."
110
- }
111
- ]
112
- }`
105
+ const json = `{"errors": [{"title": "You've been blocked", "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}`
113
106
 
114
107
  module.exports = {
115
108
  html,
@@ -19,7 +19,7 @@ function block (req, res, rootSpan, abortController) {
19
19
  const accept = req.headers.accept && req.headers.accept.split(',').map((str) => str.split(';', 1)[0].trim())
20
20
 
21
21
  if (accept && accept.includes('text/html') && !accept.includes('application/json')) {
22
- type = 'text/html'
22
+ type = 'text/html; charset=utf-8'
23
23
  body = templateHtml
24
24
  } else {
25
25
  type = 'application/json'
@@ -1,11 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('diagnostics_channel')
3
+ const dc = require('../../../diagnostics_channel')
4
4
 
5
5
  // TODO: use TBD naming convention
6
- // or directly use http plugin's channels
7
- // when it gets converted to new plugin system
8
6
  module.exports = {
9
7
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
10
- incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd')
8
+ incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
9
+ bodyParser: dc.channel('datadog:body-parser:read:finish'),
10
+ queryParser: dc.channel('datadog:query:read:finish')
11
11
  }
@@ -3,7 +3,7 @@ const { enableAllAnalyzers, disableAllAnalyzers } = require('./analyzers')
3
3
  const web = require('../../plugins/util/web')
4
4
  const { storage } = require('../../../../datadog-core')
5
5
  const overheadController = require('./overhead-controller')
6
- const dc = require('diagnostics_channel')
6
+ const dc = require('../../../../diagnostics_channel')
7
7
  const iastContextFunctions = require('./iast-context')
8
8
  const { enableTaintTracking, disableTaintTracking, createTransaction, removeTransaction } = require('./taint-tracking')
9
9
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const TaintedUtils = require('@datadog/native-iast-taint-tracking')
4
4
  const { IAST_TRANSACTION_ID } = require('../iast-context')
5
+ const iastLog = require('../iast-log')
5
6
  const { TaintTracking, TaintTrackingDummy } = require('./taint-tracking-impl')
6
7
 
7
8
  function createTransaction (id, iastContext) {
@@ -37,20 +38,27 @@ function taintObject (iastContext, object, type) {
37
38
  const visited = new WeakSet()
38
39
  while (queue.length > 0) {
39
40
  const { parent, property, value } = queue.pop()
40
- if (typeof value === 'string') {
41
- const tainted = TaintedUtils.newTaintedString(transactionId, value, property, type)
42
- if (!parent) {
43
- result = tainted
44
- } else {
45
- parent[property] = tainted
46
- }
47
- } else if (typeof value === 'object' && !visited.has(value)) {
48
- visited.add(value)
49
- const keys = Object.keys(value)
50
- for (let i = 0; i < keys.length; i++) {
51
- const key = keys[i]
52
- queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key] })
41
+ if (value === null) {
42
+ continue
43
+ }
44
+ try {
45
+ if (typeof value === 'string') {
46
+ const tainted = TaintedUtils.newTaintedString(transactionId, value, property, type)
47
+ if (!parent) {
48
+ result = tainted
49
+ } else {
50
+ parent[property] = tainted
51
+ }
52
+ } else if (typeof value === 'object' && !visited.has(value)) {
53
+ visited.add(value)
54
+ const keys = Object.keys(value)
55
+ for (let i = 0; i < keys.length; i++) {
56
+ const key = keys[i]
57
+ queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key] })
58
+ }
53
59
  }
60
+ } catch (e) {
61
+ iastLog.error(`Error visiting property : ${property}`).errorAndPublish(e)
54
62
  }
55
63
  }
56
64
  }
@@ -12,7 +12,7 @@ class TaintTrackingPlugin extends Plugin {
12
12
  this._type = 'taint-tracking'
13
13
  this.addSub(
14
14
  'datadog:body-parser:read:finish',
15
- ({ request }) => this._taintTrackingHandler(HTTP_REQUEST_BODY, request, 'body')
15
+ ({ req }) => this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body')
16
16
  )
17
17
  this.addSub(
18
18
  'datadog:qs:parse:finish',
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('diagnostics_channel')
3
+ const dc = require('../../../../../diagnostics_channel')
4
4
  const logCollector = require('./log_collector')
5
5
  const { sendData } = require('../../../telemetry/send-data')
6
6
  const log = require('../../../log')
@@ -3,8 +3,13 @@
3
3
  const log = require('../log')
4
4
  const RuleManager = require('./rule_manager')
5
5
  const remoteConfig = require('./remote_config')
6
- const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('./gateway/channels')
7
- const Gateway = require('./gateway/engine')
6
+ const {
7
+ incomingHttpRequestStart,
8
+ incomingHttpRequestEnd,
9
+ bodyParser,
10
+ queryParser
11
+ } = require('./channels')
12
+ const waf = require('./waf')
8
13
  const addresses = require('./addresses')
9
14
  const Reporter = require('./reporter')
10
15
  const web = require('../plugins/util/web')
@@ -21,39 +26,25 @@ function enable (_config) {
21
26
  try {
22
27
  setTemplates(_config)
23
28
 
24
- // TODO: inline this function
25
- enableFromRules(_config, _config.appsec.rules)
26
- } catch (err) {
27
- abortEnable(err)
28
- }
29
- }
29
+ RuleManager.applyRules(_config.appsec.rules, _config.appsec)
30
30
 
31
- function enableFromRules (_config, rules) {
32
- RuleManager.applyRules(rules, _config.appsec)
33
- remoteConfig.enableAsmData(_config.appsec)
31
+ remoteConfig.enableWafUpdate(_config.appsec)
34
32
 
35
- Reporter.setRateLimit(_config.appsec.rateLimit)
33
+ Reporter.setRateLimit(_config.appsec.rateLimit)
36
34
 
37
- incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
38
- incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
39
-
40
- // add fields needed for HTTP context reporting
41
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_HEADERS)
42
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_ENDPOINT)
43
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_RESPONSE_HEADERS)
44
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_REMOTE_IP)
45
-
46
- isEnabled = true
47
- config = _config
48
- }
35
+ incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
36
+ incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
37
+ bodyParser.subscribe(onRequestBodyParsed)
38
+ queryParser.subscribe(onRequestQueryParsed)
49
39
 
50
- function abortEnable (err) {
51
- log.error('Unable to start AppSec')
52
- log.error(err)
40
+ isEnabled = true
41
+ config = _config
42
+ } catch (err) {
43
+ log.error('Unable to start AppSec')
44
+ log.error(err)
53
45
 
54
- // abort AppSec start
55
- RuleManager.clearAllRules()
56
- remoteConfig.disableAsmData()
46
+ disable()
47
+ }
57
48
  }
58
49
 
59
50
  function incomingHttpStartTranslator ({ req, res, abortController }) {
@@ -68,78 +59,92 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
68
59
  [HTTP_CLIENT_IP]: clientIp
69
60
  })
70
61
 
71
- const store = Gateway.startContext()
72
-
73
- store.set('req', req)
74
- store.set('res', res)
62
+ const requestHeaders = Object.assign({}, req.headers)
63
+ delete requestHeaders.cookie
75
64
 
76
- const context = store.get('context')
65
+ const payload = {
66
+ [addresses.HTTP_INCOMING_URL]: req.url,
67
+ [addresses.HTTP_INCOMING_HEADERS]: requestHeaders,
68
+ [addresses.HTTP_INCOMING_METHOD]: req.method
69
+ }
77
70
 
78
71
  if (clientIp) {
79
- const results = Gateway.propagate({
80
- [addresses.HTTP_CLIENT_IP]: clientIp
81
- }, context)
82
-
83
- if (!results || !abortController) return
84
-
85
- for (const entry of results) {
86
- if (entry && entry.includes('block')) {
87
- block(req, res, rootSpan, abortController)
88
- break
89
- }
90
- }
72
+ payload[addresses.HTTP_CLIENT_IP] = clientIp
91
73
  }
92
- }
93
74
 
94
- function incomingHttpEndTranslator (data) {
95
- const context = Gateway.getContext()
96
- if (!context) return
75
+ const actions = waf.run(payload, req)
97
76
 
98
- const requestHeaders = Object.assign({}, data.req.headers)
99
- delete requestHeaders.cookie
77
+ handleResults(actions, req, res, rootSpan, abortController)
78
+ }
100
79
 
80
+ function incomingHttpEndTranslator ({ req, res }) {
101
81
  // TODO: this doesn't support headers sent with res.writeHead()
102
- const responseHeaders = Object.assign({}, data.res.getHeaders())
82
+ const responseHeaders = Object.assign({}, res.getHeaders())
103
83
  delete responseHeaders['set-cookie']
104
84
 
105
85
  const payload = {
106
- [addresses.HTTP_INCOMING_URL]: data.req.url,
107
- [addresses.HTTP_INCOMING_HEADERS]: requestHeaders,
108
- [addresses.HTTP_INCOMING_METHOD]: data.req.method,
109
- [addresses.HTTP_INCOMING_REMOTE_IP]: data.req.socket.remoteAddress,
110
- [addresses.HTTP_INCOMING_REMOTE_PORT]: data.req.socket.remotePort,
111
- [addresses.HTTP_INCOMING_RESPONSE_CODE]: data.res.statusCode,
86
+ [addresses.HTTP_INCOMING_RESPONSE_CODE]: res.statusCode,
112
87
  [addresses.HTTP_INCOMING_RESPONSE_HEADERS]: responseHeaders
113
88
  }
114
89
 
115
- // TODO: temporary express instrumentation, will use express plugin later
116
- if (data.req.body !== undefined && data.req.body !== null) {
117
- payload[addresses.HTTP_INCOMING_BODY] = data.req.body
118
- }
119
-
120
- if (data.req.query && typeof data.req.query === 'object') {
121
- payload[addresses.HTTP_INCOMING_QUERY] = data.req.query
90
+ // we need to keep this to support other body parsers
91
+ // TODO: no need to analyze it if it was already done by the body-parser hook
92
+ if (req.body !== undefined && req.body !== null) {
93
+ payload[addresses.HTTP_INCOMING_BODY] = req.body
122
94
  }
123
95
 
124
- if (data.req.route && typeof data.req.route.path === 'string') {
125
- payload[addresses.HTTP_INCOMING_ENDPOINT] = data.req.route.path
126
- }
127
-
128
- if (data.req.params && typeof data.req.params === 'object') {
129
- payload[addresses.HTTP_INCOMING_PARAMS] = data.req.params
96
+ // TODO: temporary express instrumentation, will use express plugin later
97
+ if (req.params && typeof req.params === 'object') {
98
+ payload[addresses.HTTP_INCOMING_PARAMS] = req.params
130
99
  }
131
100
 
132
- if (data.req.cookies && typeof data.req.cookies === 'object') {
101
+ if (req.cookies && typeof req.cookies === 'object') {
133
102
  payload[addresses.HTTP_INCOMING_COOKIES] = {}
134
103
 
135
- for (const k of Object.keys(data.req.cookies)) {
136
- payload[addresses.HTTP_INCOMING_COOKIES][k] = [data.req.cookies[k]]
104
+ for (const k of Object.keys(req.cookies)) {
105
+ payload[addresses.HTTP_INCOMING_COOKIES][k] = [req.cookies[k]]
137
106
  }
138
107
  }
139
108
 
140
- Gateway.propagate(payload, context)
109
+ waf.run(payload, req)
110
+
111
+ waf.disposeContext(req)
112
+
113
+ Reporter.finishRequest(req, res)
114
+ }
115
+
116
+ function onRequestBodyParsed ({ req, res, abortController }) {
117
+ const rootSpan = web.root(req)
118
+ if (!rootSpan) return
119
+
120
+ if (req.body === undefined || req.body === null) return
121
+
122
+ const results = waf.run({
123
+ [addresses.HTTP_INCOMING_BODY]: req.body
124
+ }, req)
141
125
 
142
- Reporter.finishRequest(data.req, context)
126
+ handleResults(results, req, res, rootSpan, abortController)
127
+ }
128
+
129
+ function onRequestQueryParsed ({ req, res, abortController }) {
130
+ const rootSpan = web.root(req)
131
+ if (!rootSpan) return
132
+
133
+ if (!req.query || typeof req.query !== 'object') return
134
+
135
+ const results = waf.run({
136
+ [addresses.HTTP_INCOMING_QUERY]: req.query
137
+ }, req)
138
+
139
+ handleResults(results, req, res, rootSpan, abortController)
140
+ }
141
+
142
+ function handleResults (actions, req, res, rootSpan, abortController) {
143
+ if (!actions || !req || !res || !rootSpan || !abortController) return
144
+
145
+ if (actions.includes('block')) {
146
+ block(req, res, rootSpan, abortController)
147
+ }
143
148
  }
144
149
 
145
150
  function disable () {
@@ -147,11 +152,14 @@ function disable () {
147
152
  config = null
148
153
 
149
154
  RuleManager.clearAllRules()
150
- remoteConfig.disableAsmData()
155
+
156
+ remoteConfig.disableWafUpdate()
151
157
 
152
158
  // Channel#unsubscribe() is undefined for non active channels
153
159
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
154
160
  if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
161
+ if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
162
+ if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed)
155
163
  }
156
164
 
157
165
  module.exports = {