dd-trace 4.18.0 → 5.6.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 (209) hide show
  1. package/CONTRIBUTING.md +98 -0
  2. package/LICENSE-3rdparty.csv +4 -5
  3. package/MIGRATING.md +15 -0
  4. package/README.md +20 -140
  5. package/ci/cypress/after-run.js +1 -0
  6. package/ci/cypress/after-spec.js +1 -0
  7. package/ci/init.js +1 -4
  8. package/ext/kinds.d.ts +1 -0
  9. package/ext/kinds.js +2 -1
  10. package/ext/tags.d.ts +2 -1
  11. package/ext/tags.js +6 -1
  12. package/index.d.ts +1523 -1460
  13. package/package.json +19 -19
  14. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  15. package/packages/datadog-core/src/utils/src/get.js +11 -0
  16. package/packages/datadog-core/src/utils/src/has.js +14 -0
  17. package/packages/datadog-core/src/utils/src/kebabcase.js +16 -0
  18. package/packages/datadog-core/src/utils/src/pick.js +11 -0
  19. package/packages/datadog-core/src/utils/src/set.js +16 -0
  20. package/packages/datadog-core/src/utils/src/uniq.js +5 -0
  21. package/packages/datadog-esbuild/index.js +1 -20
  22. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  23. package/packages/datadog-instrumentations/src/amqplib.js +2 -2
  24. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  25. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  26. package/packages/datadog-instrumentations/src/child_process.js +150 -0
  27. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  28. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  29. package/packages/datadog-instrumentations/src/cucumber.js +163 -46
  30. package/packages/datadog-instrumentations/src/dns.js +2 -1
  31. package/packages/datadog-instrumentations/src/express.js +20 -0
  32. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  33. package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
  34. package/packages/datadog-instrumentations/src/grpc/server.js +3 -1
  35. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  36. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -3
  37. package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
  38. package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
  39. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  40. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  41. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  42. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  43. package/packages/datadog-instrumentations/src/jest.js +239 -52
  44. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  45. package/packages/datadog-instrumentations/src/mocha.js +154 -18
  46. package/packages/datadog-instrumentations/src/mongodb-core.js +34 -3
  47. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  48. package/packages/datadog-instrumentations/src/mquery.js +65 -0
  49. package/packages/datadog-instrumentations/src/net.js +10 -2
  50. package/packages/datadog-instrumentations/src/next.js +35 -9
  51. package/packages/datadog-instrumentations/src/playwright.js +110 -16
  52. package/packages/datadog-instrumentations/src/restify.js +14 -1
  53. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  54. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  55. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -1
  56. package/packages/datadog-plugin-amqplib/src/producer.js +13 -1
  57. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -2
  58. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +163 -27
  59. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +46 -8
  60. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +129 -22
  61. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  62. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +70 -13
  64. package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
  65. package/packages/datadog-plugin-cypress/src/after-spec.js +3 -0
  66. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +625 -0
  67. package/packages/datadog-plugin-cypress/src/plugin.js +6 -454
  68. package/packages/datadog-plugin-cypress/src/support.js +50 -3
  69. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/index.js +1 -6
  71. package/packages/datadog-plugin-graphql/src/resolve.js +28 -18
  72. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  73. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  74. package/packages/datadog-plugin-http/src/client.js +19 -2
  75. package/packages/datadog-plugin-jest/src/index.js +118 -12
  76. package/packages/datadog-plugin-jest/src/util.js +38 -16
  77. package/packages/datadog-plugin-kafkajs/src/consumer.js +76 -6
  78. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -8
  79. package/packages/datadog-plugin-mocha/src/index.js +87 -17
  80. package/packages/datadog-plugin-next/src/index.js +40 -14
  81. package/packages/datadog-plugin-playwright/src/index.js +71 -8
  82. package/packages/datadog-plugin-rhea/src/consumer.js +16 -1
  83. package/packages/datadog-plugin-rhea/src/producer.js +10 -0
  84. package/packages/dd-trace/src/appsec/activation.js +29 -0
  85. package/packages/dd-trace/src/appsec/addresses.js +5 -1
  86. package/packages/dd-trace/src/appsec/api_security_sampler.js +61 -0
  87. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  88. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  89. package/packages/dd-trace/src/appsec/channels.js +7 -3
  90. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  91. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
  92. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  94. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +22 -17
  95. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  96. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  97. package/packages/dd-trace/src/appsec/iast/analyzers/weak-randomness-analyzer.js +19 -0
  98. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  99. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  100. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  101. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +13 -2
  102. package/packages/dd-trace/src/appsec/iast/index.js +15 -5
  103. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  104. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +2 -0
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  107. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  109. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  110. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  111. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +19 -6
  112. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +41 -3
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  115. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  116. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  117. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  118. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  119. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  120. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  121. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  122. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  123. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
  124. package/packages/dd-trace/src/appsec/index.js +49 -33
  125. package/packages/dd-trace/src/appsec/recommended.json +1763 -106
  126. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +7 -1
  127. package/packages/dd-trace/src/appsec/remote_config/index.js +42 -16
  128. package/packages/dd-trace/src/appsec/remote_config/manager.js +9 -8
  129. package/packages/dd-trace/src/appsec/reporter.js +51 -34
  130. package/packages/dd-trace/src/appsec/rule_manager.js +11 -8
  131. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  132. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  133. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  134. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → early-flake-detection/get-known-tests.js} +17 -22
  135. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  136. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  137. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  139. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +95 -37
  140. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +134 -61
  141. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +37 -4
  142. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +131 -0
  143. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  144. package/packages/dd-trace/src/config.js +561 -470
  145. package/packages/dd-trace/src/data_streams_context.js +1 -1
  146. package/packages/dd-trace/src/datastreams/pathway.js +58 -1
  147. package/packages/dd-trace/src/datastreams/processor.js +196 -27
  148. package/packages/dd-trace/src/datastreams/writer.js +11 -5
  149. package/packages/dd-trace/src/dogstatsd.js +3 -5
  150. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +44 -6
  151. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  152. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  153. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  154. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  155. package/packages/dd-trace/src/format.js +30 -2
  156. package/packages/dd-trace/src/id.js +12 -0
  157. package/packages/dd-trace/src/iitm.js +1 -1
  158. package/packages/dd-trace/src/log/channels.js +1 -1
  159. package/packages/dd-trace/src/noop/proxy.js +4 -0
  160. package/packages/dd-trace/src/noop/span.js +1 -0
  161. package/packages/dd-trace/src/opentelemetry/span.js +104 -4
  162. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  163. package/packages/dd-trace/src/opentracing/propagation/text_map.js +16 -7
  164. package/packages/dd-trace/src/opentracing/span.js +48 -4
  165. package/packages/dd-trace/src/opentracing/span_context.js +15 -6
  166. package/packages/dd-trace/src/opentracing/tracer.js +4 -3
  167. package/packages/dd-trace/src/plugin_manager.js +1 -1
  168. package/packages/dd-trace/src/plugins/ci_plugin.js +78 -19
  169. package/packages/dd-trace/src/plugins/database.js +1 -1
  170. package/packages/dd-trace/src/plugins/index.js +7 -0
  171. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  172. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  173. package/packages/dd-trace/src/plugins/util/git.js +104 -22
  174. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  175. package/packages/dd-trace/src/plugins/util/test.js +60 -10
  176. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  177. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  178. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  179. package/packages/dd-trace/src/priority_sampler.js +30 -38
  180. package/packages/dd-trace/src/profiler.js +5 -3
  181. package/packages/dd-trace/src/profiling/config.js +77 -24
  182. package/packages/dd-trace/src/profiling/exporters/agent.js +77 -31
  183. package/packages/dd-trace/src/profiling/exporters/file.js +2 -1
  184. package/packages/dd-trace/src/profiling/profiler.js +33 -22
  185. package/packages/dd-trace/src/profiling/profilers/events.js +270 -0
  186. package/packages/dd-trace/src/profiling/profilers/shared.js +45 -0
  187. package/packages/dd-trace/src/profiling/profilers/space.js +18 -2
  188. package/packages/dd-trace/src/profiling/profilers/wall.js +146 -70
  189. package/packages/dd-trace/src/proxy.js +56 -24
  190. package/packages/dd-trace/src/ritm.js +1 -1
  191. package/packages/dd-trace/src/sampling_rule.js +130 -0
  192. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  193. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  194. package/packages/dd-trace/src/span_processor.js +9 -1
  195. package/packages/dd-trace/src/span_sampler.js +6 -64
  196. package/packages/dd-trace/src/spanleak.js +98 -0
  197. package/packages/dd-trace/src/startup-log.js +7 -1
  198. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  199. package/packages/dd-trace/src/telemetry/index.js +182 -53
  200. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  201. package/packages/dd-trace/src/telemetry/send-data.js +65 -7
  202. package/packages/dd-trace/src/tracer.js +12 -5
  203. package/register.js +4 -0
  204. package/scripts/install_plugin_modules.js +11 -3
  205. package/scripts/st.js +105 -0
  206. package/packages/datadog-instrumentations/src/child-process.js +0 -30
  207. package/packages/dd-trace/src/plugins/util/exec.js +0 -13
  208. package/packages/diagnostics_channel/index.js +0 -3
  209. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -3,93 +3,142 @@
3
3
  const log = require('../log')
4
4
  const blockedTemplates = require('./blocked_templates')
5
5
 
6
+ const detectedSpecificEndpoints = {}
7
+
6
8
  let templateHtml = blockedTemplates.html
7
9
  let templateJson = blockedTemplates.json
10
+ let templateGraphqlJson = blockedTemplates.graphqlJson
8
11
  let blockingConfiguration
9
12
 
10
- function blockWithRedirect (res, rootSpan, abortController) {
11
- rootSpan.addTags({
12
- 'appsec.blocked': 'true'
13
- })
13
+ const specificBlockingTypes = {
14
+ GRAPHQL: 'graphql'
15
+ }
14
16
 
17
+ function getSpecificKey (method, url) {
18
+ return `${method}+${url}`
19
+ }
20
+
21
+ function addSpecificEndpoint (method, url, type) {
22
+ detectedSpecificEndpoints[getSpecificKey(method, url)] = type
23
+ }
24
+
25
+ function getBlockWithRedirectData (rootSpan) {
15
26
  let statusCode = blockingConfiguration.parameters.status_code
16
27
  if (!statusCode || statusCode < 300 || statusCode >= 400) {
17
28
  statusCode = 303
18
29
  }
19
-
20
- res.writeHead(statusCode, {
30
+ const headers = {
21
31
  'Location': blockingConfiguration.parameters.location
22
- }).end()
32
+ }
33
+
34
+ rootSpan.addTags({
35
+ 'appsec.blocked': 'true'
36
+ })
23
37
 
24
- if (abortController) {
25
- abortController.abort()
38
+ return { headers, statusCode }
39
+ }
40
+
41
+ function getSpecificBlockingData (type) {
42
+ switch (type) {
43
+ case specificBlockingTypes.GRAPHQL:
44
+ return {
45
+ type: 'application/json',
46
+ body: templateGraphqlJson
47
+ }
26
48
  }
27
49
  }
28
50
 
29
- function blockWithContent (req, res, rootSpan, abortController) {
51
+ function getBlockWithContentData (req, specificType, rootSpan) {
30
52
  let type
31
53
  let body
54
+ let statusCode
32
55
 
33
- // parse the Accept header, ex: Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
34
- const accept = req.headers.accept && req.headers.accept.split(',').map((str) => str.split(';', 1)[0].trim())
56
+ const specificBlockingType = specificType || detectedSpecificEndpoints[getSpecificKey(req.method, req.url)]
57
+ if (specificBlockingType) {
58
+ const specificBlockingContent = getSpecificBlockingData(specificBlockingType)
59
+ type = specificBlockingContent?.type
60
+ body = specificBlockingContent?.body
61
+ }
35
62
 
36
- if (!blockingConfiguration || blockingConfiguration.parameters.type === 'auto') {
37
- if (accept && accept.includes('text/html') && !accept.includes('application/json')) {
38
- type = 'text/html; charset=utf-8'
39
- body = templateHtml
63
+ if (!type) {
64
+ // parse the Accept header, ex: Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
65
+ const accept = req.headers.accept?.split(',').map((str) => str.split(';', 1)[0].trim())
66
+
67
+ if (!blockingConfiguration || blockingConfiguration.parameters.type === 'auto') {
68
+ if (accept?.includes('text/html') && !accept.includes('application/json')) {
69
+ type = 'text/html; charset=utf-8'
70
+ body = templateHtml
71
+ } else {
72
+ type = 'application/json'
73
+ body = templateJson
74
+ }
40
75
  } else {
41
- type = 'application/json'
42
- body = templateJson
76
+ if (blockingConfiguration.parameters.type === 'html') {
77
+ type = 'text/html; charset=utf-8'
78
+ body = templateHtml
79
+ } else {
80
+ type = 'application/json'
81
+ body = templateJson
82
+ }
43
83
  }
84
+ }
85
+
86
+ if (blockingConfiguration?.type === 'block_request' && blockingConfiguration.parameters.status_code) {
87
+ statusCode = blockingConfiguration.parameters.status_code
44
88
  } else {
45
- if (blockingConfiguration.parameters.type === 'html') {
46
- type = 'text/html; charset=utf-8'
47
- body = templateHtml
48
- } else {
49
- type = 'application/json'
50
- body = templateJson
51
- }
89
+ statusCode = 403
90
+ }
91
+
92
+ const headers = {
93
+ 'Content-Type': type,
94
+ 'Content-Length': Buffer.byteLength(body)
52
95
  }
53
96
 
54
97
  rootSpan.addTags({
55
98
  'appsec.blocked': 'true'
56
99
  })
57
100
 
58
- if (blockingConfiguration && blockingConfiguration.type === 'block_request' &&
59
- blockingConfiguration.parameters.status_code) {
60
- res.statusCode = blockingConfiguration.parameters.status_code
61
- } else {
62
- res.statusCode = 403
63
- }
64
- res.setHeader('Content-Type', type)
65
- res.setHeader('Content-Length', Buffer.byteLength(body))
66
- res.end(body)
101
+ return { body, statusCode, headers }
102
+ }
67
103
 
68
- if (abortController) {
69
- abortController.abort()
104
+ function getBlockingData (req, specificType, rootSpan) {
105
+ if (blockingConfiguration?.type === 'redirect_request' && blockingConfiguration.parameters.location) {
106
+ return getBlockWithRedirectData(rootSpan)
107
+ } else {
108
+ return getBlockWithContentData(req, specificType, rootSpan)
70
109
  }
71
110
  }
72
111
 
73
- function block (req, res, rootSpan, abortController) {
112
+ function block (req, res, rootSpan, abortController, type) {
74
113
  if (res.headersSent) {
75
114
  log.warn('Cannot send blocking response when headers have already been sent')
76
115
  return
77
116
  }
78
117
 
79
- if (blockingConfiguration && blockingConfiguration.type === 'redirect_request' &&
80
- blockingConfiguration.parameters.location) {
81
- blockWithRedirect(res, rootSpan, abortController)
82
- } else {
83
- blockWithContent(req, res, rootSpan, abortController)
84
- }
118
+ const { body, headers, statusCode } = getBlockingData(req, type, rootSpan)
119
+
120
+ res.writeHead(statusCode, headers).end(body)
121
+
122
+ abortController?.abort()
85
123
  }
86
124
 
87
125
  function setTemplates (config) {
88
126
  if (config.appsec.blockedTemplateHtml) {
89
127
  templateHtml = config.appsec.blockedTemplateHtml
128
+ } else {
129
+ templateHtml = blockedTemplates.html
90
130
  }
131
+
91
132
  if (config.appsec.blockedTemplateJson) {
92
133
  templateJson = config.appsec.blockedTemplateJson
134
+ } else {
135
+ templateJson = blockedTemplates.json
136
+ }
137
+
138
+ if (config.appsec.blockedTemplateGraphql) {
139
+ templateGraphqlJson = config.appsec.blockedTemplateGraphql
140
+ } else {
141
+ templateGraphqlJson = blockedTemplates.graphqlJson
93
142
  }
94
143
  }
95
144
 
@@ -98,7 +147,10 @@ function updateBlockingConfiguration (newBlockingConfiguration) {
98
147
  }
99
148
 
100
149
  module.exports = {
150
+ addSpecificEndpoint,
101
151
  block,
152
+ specificBlockingTypes,
153
+ getBlockingData,
102
154
  setTemplates,
103
155
  updateBlockingConfiguration
104
156
  }
@@ -1,17 +1,21 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('../../../diagnostics_channel')
3
+ const dc = require('dc-polyfill')
4
4
 
5
5
  // TODO: use TBD naming convention
6
6
  module.exports = {
7
7
  bodyParser: dc.channel('datadog:body-parser:read:finish'),
8
8
  cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
9
- graphqlFinishExecute: dc.channel('apm:graphql:execute:finish'),
9
+ startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
10
+ graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
11
+ apolloChannel: dc.tracingChannel('datadog:apollo:request'),
12
+ apolloServerCoreChannel: dc.tracingChannel('datadog:apollo-server-core:request'),
10
13
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
11
14
  incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
12
15
  passportVerify: dc.channel('datadog:passport:verify:finish'),
13
16
  queryParser: dc.channel('datadog:query:read:finish'),
14
17
  setCookieChannel: dc.channel('datadog:iast:set-cookie'),
15
18
  nextBodyParsed: dc.channel('apm:next:body-parsed'),
16
- nextQueryParsed: dc.channel('apm:next:query-parsed')
19
+ nextQueryParsed: dc.channel('apm:next:query-parsed'),
20
+ responseBody: dc.channel('datadog:express:response:json:start')
17
21
  }
@@ -0,0 +1,146 @@
1
+ 'use strict'
2
+
3
+ const { storage } = require('../../../datadog-core')
4
+ const { addSpecificEndpoint, specificBlockingTypes, getBlockingData } = require('./blocking')
5
+ const waf = require('./waf')
6
+ const addresses = require('./addresses')
7
+ const web = require('../plugins/util/web')
8
+ const {
9
+ startGraphqlResolve,
10
+ graphqlMiddlewareChannel,
11
+ apolloChannel,
12
+ apolloServerCoreChannel
13
+ } = require('./channels')
14
+
15
+ const graphqlRequestData = new WeakMap()
16
+
17
+ function enable () {
18
+ enableApollo()
19
+ enableGraphql()
20
+ }
21
+
22
+ function disable () {
23
+ disableApollo()
24
+ disableGraphql()
25
+ }
26
+
27
+ function onGraphqlStartResolve ({ context, resolverInfo }) {
28
+ const req = storage.getStore()?.req
29
+
30
+ if (!req) return
31
+
32
+ if (!resolverInfo || typeof resolverInfo !== 'object') return
33
+
34
+ const actions = waf.run({ ephemeral: { [addresses.HTTP_INCOMING_GRAPHQL_RESOLVER]: resolverInfo } }, req)
35
+ if (actions?.includes('block')) {
36
+ const requestData = graphqlRequestData.get(req)
37
+ if (requestData?.isInGraphqlRequest) {
38
+ requestData.blocked = true
39
+ context?.abortController?.abort()
40
+ }
41
+ }
42
+ }
43
+
44
+ function enterInApolloMiddleware (data) {
45
+ const req = data?.req || storage.getStore()?.req
46
+ if (!req) return
47
+
48
+ graphqlRequestData.set(req, {
49
+ inApolloMiddleware: true,
50
+ blocked: false
51
+ })
52
+ }
53
+
54
+ function enterInApolloServerCoreRequest () {
55
+ const req = storage.getStore()?.req
56
+ if (!req) return
57
+
58
+ graphqlRequestData.set(req, {
59
+ isInGraphqlRequest: true,
60
+ blocked: false
61
+ })
62
+ }
63
+
64
+ function exitFromApolloMiddleware (data) {
65
+ const req = data?.req || storage.getStore()?.req
66
+ const requestData = graphqlRequestData.get(req)
67
+ if (requestData) requestData.inApolloMiddleware = false
68
+ }
69
+
70
+ function enterInApolloRequest () {
71
+ const req = storage.getStore()?.req
72
+
73
+ const requestData = graphqlRequestData.get(req)
74
+ if (requestData?.inApolloMiddleware) {
75
+ requestData.isInGraphqlRequest = true
76
+ addSpecificEndpoint(req.method, req.originalUrl || req.url, specificBlockingTypes.GRAPHQL)
77
+ }
78
+ }
79
+
80
+ function beforeWriteApolloGraphqlResponse ({ abortController, abortData }) {
81
+ const req = storage.getStore()?.req
82
+ if (!req) return
83
+
84
+ const requestData = graphqlRequestData.get(req)
85
+
86
+ if (requestData?.blocked) {
87
+ const rootSpan = web.root(req)
88
+ if (!rootSpan) return
89
+
90
+ const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, rootSpan)
91
+ abortData.statusCode = blockingData.statusCode
92
+ abortData.headers = blockingData.headers
93
+ abortData.message = blockingData.body
94
+
95
+ abortController?.abort()
96
+ }
97
+
98
+ graphqlRequestData.delete(req)
99
+ }
100
+
101
+ function enableApollo () {
102
+ graphqlMiddlewareChannel.subscribe({
103
+ start: enterInApolloMiddleware,
104
+ end: exitFromApolloMiddleware
105
+ })
106
+
107
+ apolloServerCoreChannel.subscribe({
108
+ start: enterInApolloServerCoreRequest,
109
+ asyncEnd: beforeWriteApolloGraphqlResponse
110
+ })
111
+
112
+ apolloChannel.subscribe({
113
+ start: enterInApolloRequest,
114
+ asyncEnd: beforeWriteApolloGraphqlResponse
115
+ })
116
+ }
117
+
118
+ function disableApollo () {
119
+ graphqlMiddlewareChannel.unsubscribe({
120
+ start: enterInApolloMiddleware,
121
+ end: exitFromApolloMiddleware
122
+ })
123
+
124
+ apolloServerCoreChannel.unsubscribe({
125
+ start: enterInApolloServerCoreRequest,
126
+ asyncEnd: beforeWriteApolloGraphqlResponse
127
+ })
128
+
129
+ apolloChannel.unsubscribe({
130
+ start: enterInApolloRequest,
131
+ asyncEnd: beforeWriteApolloGraphqlResponse
132
+ })
133
+ }
134
+
135
+ function enableGraphql () {
136
+ startGraphqlResolve.subscribe(onGraphqlStartResolve)
137
+ }
138
+
139
+ function disableGraphql () {
140
+ if (startGraphqlResolve.hasSubscribers) startGraphqlResolve.unsubscribe(onGraphqlStartResolve)
141
+ }
142
+
143
+ module.exports = {
144
+ enable,
145
+ disable
146
+ }
@@ -3,6 +3,7 @@
3
3
  module.exports = {
4
4
  'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
5
5
  'HARCODED_SECRET_ANALYZER': require('./hardcoded-secret-analyzer'),
6
+ 'HEADER_INJECTION_ANALYZER': require('./header-injection-analyzer'),
6
7
  'HSTS_HEADER_MISSING_ANALYZER': require('./hsts-header-missing-analyzer'),
7
8
  'INSECURE_COOKIE_ANALYZER': require('./insecure-cookie-analyzer'),
8
9
  'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
@@ -15,5 +16,6 @@ module.exports = {
15
16
  'UNVALIDATED_REDIRECT_ANALYZER': require('./unvalidated-redirect-analyzer'),
16
17
  'WEAK_CIPHER_ANALYZER': require('./weak-cipher-analyzer'),
17
18
  'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer'),
19
+ 'WEAK_RANDOMNESS_ANALYZER': require('./weak-randomness-analyzer'),
18
20
  'XCONTENTTYPE_HEADER_MISSING_ANALYZER': require('./xcontenttype-header-missing-analyzer')
19
21
  }
@@ -8,7 +8,7 @@ class CommandInjectionAnalyzer extends InjectionAnalyzer {
8
8
  }
9
9
 
10
10
  onConfigure () {
11
- this.addSub('datadog:child_process:execution:start', ({ command }) => this.analyze(command))
11
+ this.addSub('tracing:datadog:child_process:execution:start', ({ command }) => this.analyze(command))
12
12
  }
13
13
  }
14
14
 
@@ -0,0 +1,105 @@
1
+ 'use strict'
2
+
3
+ const InjectionAnalyzer = require('./injection-analyzer')
4
+ const { HEADER_INJECTION } = require('../vulnerabilities')
5
+ const { getNodeModulesPaths } = require('../path-line')
6
+ const { HEADER_NAME_VALUE_SEPARATOR } = require('../vulnerabilities-formatter/constants')
7
+ const { getRanges } = require('../taint-tracking/operations')
8
+ const {
9
+ HTTP_REQUEST_COOKIE_NAME,
10
+ HTTP_REQUEST_COOKIE_VALUE,
11
+ HTTP_REQUEST_HEADER_VALUE
12
+ } = require('../taint-tracking/source-types')
13
+
14
+ const EXCLUDED_PATHS = getNodeModulesPaths('express')
15
+ const EXCLUDED_HEADER_NAMES = [
16
+ 'location',
17
+ 'sec-websocket-location',
18
+ 'sec-websocket-accept',
19
+ 'upgrade',
20
+ 'connection'
21
+ ]
22
+
23
+ class HeaderInjectionAnalyzer extends InjectionAnalyzer {
24
+ constructor () {
25
+ super(HEADER_INJECTION)
26
+ }
27
+
28
+ onConfigure () {
29
+ this.addSub('datadog:http:server:response:set-header:finish', ({ name, value }) => {
30
+ if (Array.isArray(value)) {
31
+ for (let i = 0; i < value.length; i++) {
32
+ const headerValue = value[i]
33
+
34
+ this.analyze({ name, value: headerValue })
35
+ }
36
+ } else {
37
+ this.analyze({ name, value })
38
+ }
39
+ })
40
+ }
41
+
42
+ _isVulnerable ({ name, value }, iastContext) {
43
+ const lowerCasedHeaderName = name?.trim().toLowerCase()
44
+
45
+ if (this.isExcludedHeaderName(lowerCasedHeaderName) || typeof value !== 'string') return
46
+
47
+ const ranges = getRanges(iastContext, value)
48
+ if (ranges?.length > 0) {
49
+ return !(this.isCookieExclusion(lowerCasedHeaderName, ranges) ||
50
+ this.isSameHeaderExclusion(lowerCasedHeaderName, ranges) ||
51
+ this.isAccessControlAllowExclusion(lowerCasedHeaderName, ranges))
52
+ }
53
+
54
+ return false
55
+ }
56
+
57
+ _getEvidence (headerInfo, iastContext) {
58
+ const prefix = headerInfo.name + HEADER_NAME_VALUE_SEPARATOR
59
+ const prefixLength = prefix.length
60
+
61
+ const evidence = super._getEvidence(headerInfo.value, iastContext)
62
+ evidence.value = prefix + evidence.value
63
+ evidence.ranges = evidence.ranges.map(range => {
64
+ return {
65
+ ...range,
66
+ start: range.start + prefixLength,
67
+ end: range.end + prefixLength
68
+ }
69
+ })
70
+
71
+ return evidence
72
+ }
73
+
74
+ isExcludedHeaderName (name) {
75
+ return EXCLUDED_HEADER_NAMES.includes(name)
76
+ }
77
+
78
+ isCookieExclusion (name, ranges) {
79
+ if (name === 'set-cookie') {
80
+ return ranges
81
+ .every(range => range.iinfo.type === HTTP_REQUEST_COOKIE_VALUE || range.iinfo.type === HTTP_REQUEST_COOKIE_NAME)
82
+ }
83
+
84
+ return false
85
+ }
86
+
87
+ isAccessControlAllowExclusion (name, ranges) {
88
+ if (name?.startsWith('access-control-allow-')) {
89
+ return ranges
90
+ .every(range => range.iinfo.type === HTTP_REQUEST_HEADER_VALUE)
91
+ }
92
+
93
+ return false
94
+ }
95
+
96
+ isSameHeaderExclusion (name, ranges) {
97
+ return ranges.length === 1 && name === ranges[0].iinfo.parameterName?.toLowerCase()
98
+ }
99
+
100
+ _getExcludedPaths () {
101
+ return EXCLUDED_PATHS
102
+ }
103
+ }
104
+
105
+ module.exports = new HeaderInjectionAnalyzer()
@@ -9,7 +9,7 @@ const { storage } = require('../../../../../datadog-core')
9
9
  const { getIastContext } = require('../iast-context')
10
10
  const { HTTP_REQUEST_PARAMETER, HTTP_REQUEST_BODY } = require('../taint-tracking/source-types')
11
11
 
12
- const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose')
12
+ const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose', 'mquery')
13
13
  const MONGODB_NOSQL_SECURE_MARK = getNextSecureMark()
14
14
 
15
15
  function iterateObjectStrings (target, fn, levelKeys = [], depth = 50, visited = new Set()) {
@@ -37,34 +37,39 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
37
37
  onConfigure () {
38
38
  this.configureSanitizers()
39
39
 
40
- this.addSub('datadog:mongodb:collection:filter:start', ({ filters }) => {
40
+ const onStart = ({ filters }) => {
41
41
  const store = storage.getStore()
42
42
  if (store && !store.nosqlAnalyzed && filters?.length) {
43
43
  filters.forEach(filter => {
44
44
  this.analyze({ filter }, store)
45
45
  })
46
46
  }
47
- })
48
47
 
49
- this.addSub('datadog:mongoose:model:filter:start', ({ filters }) => {
50
- const store = storage.getStore()
51
- if (!store) return
48
+ return store
49
+ }
52
50
 
53
- if (filters?.length) {
54
- filters.forEach(filter => {
55
- this.analyze({ filter }, store)
56
- })
51
+ const onStartAndEnterWithStore = (message) => {
52
+ const store = onStart(message || {})
53
+ if (store) {
54
+ storage.enterWith({ ...store, nosqlAnalyzed: true, nosqlParentStore: store })
57
55
  }
56
+ }
58
57
 
59
- storage.enterWith({ ...store, nosqlAnalyzed: true, mongooseParentStore: store })
60
- })
61
-
62
- this.addSub('datadog:mongoose:model:filter:finish', () => {
58
+ const onFinish = () => {
63
59
  const store = storage.getStore()
64
- if (store?.mongooseParentStore) {
65
- storage.enterWith(store.mongooseParentStore)
60
+ if (store?.nosqlParentStore) {
61
+ storage.enterWith(store.nosqlParentStore)
66
62
  }
67
- })
63
+ }
64
+
65
+ this.addSub('datadog:mongodb:collection:filter:start', onStart)
66
+
67
+ this.addSub('datadog:mongoose:model:filter:start', onStartAndEnterWithStore)
68
+ this.addSub('datadog:mongoose:model:filter:finish', onFinish)
69
+
70
+ this.addSub('datadog:mquery:filter:prepare', onStart)
71
+ this.addSub('tracing:datadog:mquery:filter:start', onStartAndEnterWithStore)
72
+ this.addSub('tracing:datadog:mquery:filter:asyncEnd', onFinish)
68
73
  }
69
74
 
70
75
  configureSanitizers () {
@@ -4,8 +4,6 @@ const InjectionAnalyzer = require('./injection-analyzer')
4
4
  const { SQL_INJECTION } = require('../vulnerabilities')
5
5
  const { getRanges } = require('../taint-tracking/operations')
6
6
  const { storage } = require('../../../../../datadog-core')
7
- const { getIastContext } = require('../iast-context')
8
- const { addVulnerability } = require('../vulnerability-reporter')
9
7
  const { getNodeModulesPaths } = require('../path-line')
10
8
 
11
9
  const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool', 'knex')
@@ -16,9 +14,9 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
16
14
  }
17
15
 
18
16
  onConfigure () {
19
- this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
20
- this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
21
- this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, 'POSTGRES'))
17
+ this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, undefined, 'MYSQL'))
18
+ this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, undefined, 'MYSQL'))
19
+ this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, undefined, 'POSTGRES'))
22
20
 
23
21
  this.addSub(
24
22
  'datadog:sequelize:query:start',
@@ -42,7 +40,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
42
40
  getStoreAndAnalyze (query, dialect) {
43
41
  const parentStore = storage.getStore()
44
42
  if (parentStore) {
45
- this.analyze(query, dialect, parentStore)
43
+ this.analyze(query, parentStore, dialect)
46
44
 
47
45
  storage.enterWith({ ...parentStore, sqlAnalyzed: true, sqlParentStore: parentStore })
48
46
  }
@@ -60,29 +58,10 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
60
58
  return { value, ranges, dialect }
61
59
  }
62
60
 
63
- analyze (value, dialect, store = storage.getStore()) {
61
+ analyze (value, store, dialect) {
62
+ store = store || storage.getStore()
64
63
  if (!(store && store.sqlAnalyzed)) {
65
- const iastContext = getIastContext(store)
66
- if (this._isInvalidContext(store, iastContext)) return
67
- this._reportIfVulnerable(value, iastContext, dialect)
68
- }
69
- }
70
-
71
- _reportIfVulnerable (value, context, dialect) {
72
- if (this._isVulnerable(value, context) && this._checkOCE(context)) {
73
- this._report(value, context, dialect)
74
- return true
75
- }
76
- return false
77
- }
78
-
79
- _report (value, context, dialect) {
80
- const evidence = this._getEvidence(value, context, dialect)
81
- const location = this._getLocation()
82
- if (!this._isExcluded(location)) {
83
- const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
84
- const vulnerability = this._createVulnerability(this._type, evidence, spanId, location)
85
- addVulnerability(context, vulnerability)
64
+ super.analyze(value, store, dialect)
86
65
  }
87
66
  }
88
67
 
@@ -22,8 +22,12 @@ class Analyzer extends SinkIastPlugin {
22
22
  return false
23
23
  }
24
24
 
25
- _report (value, context) {
26
- const evidence = this._getEvidence(value, context)
25
+ _report (value, context, meta) {
26
+ const evidence = this._getEvidence(value, context, meta)
27
+ this._reportEvidence(value, context, evidence)
28
+ }
29
+
30
+ _reportEvidence (value, context, evidence) {
27
31
  const location = this._getLocation(value)
28
32
  if (!this._isExcluded(location)) {
29
33
  const locationSourceMap = this._replaceLocationFromSourceMap(location)
@@ -33,9 +37,9 @@ class Analyzer extends SinkIastPlugin {
33
37
  }
34
38
  }
35
39
 
36
- _reportIfVulnerable (value, context) {
40
+ _reportIfVulnerable (value, context, meta) {
37
41
  if (this._isVulnerable(value, context) && this._checkOCE(context, value)) {
38
- this._report(value, context)
42
+ this._report(value, context, meta)
39
43
  return true
40
44
  }
41
45
  return false
@@ -71,11 +75,11 @@ class Analyzer extends SinkIastPlugin {
71
75
  return store && !iastContext
72
76
  }
73
77
 
74
- analyze (value, store = storage.getStore()) {
78
+ analyze (value, store = storage.getStore(), meta) {
75
79
  const iastContext = getIastContext(store)
76
80
  if (this._isInvalidContext(store, iastContext)) return
77
81
 
78
- this._reportIfVulnerable(value, iastContext)
82
+ this._reportIfVulnerable(value, iastContext, meta)
79
83
  }
80
84
 
81
85
  analyzeAll (...values) {