dd-trace 4.18.0 → 4.23.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 (137) hide show
  1. package/LICENSE-3rdparty.csv +3 -2
  2. package/README.md +3 -3
  3. package/ext/kinds.d.ts +1 -0
  4. package/ext/kinds.js +2 -1
  5. package/ext/tags.d.ts +2 -1
  6. package/ext/tags.js +6 -1
  7. package/index.d.ts +29 -0
  8. package/package.json +12 -11
  9. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  10. package/packages/datadog-esbuild/index.js +1 -20
  11. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  13. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  14. package/packages/datadog-instrumentations/src/child-process.js +4 -5
  15. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  16. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  17. package/packages/datadog-instrumentations/src/dns.js +2 -1
  18. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  19. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +10 -2
  21. package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
  22. package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
  23. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  24. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  25. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  26. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  27. package/packages/datadog-instrumentations/src/jest.js +12 -6
  28. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  29. package/packages/datadog-instrumentations/src/net.js +10 -2
  30. package/packages/datadog-instrumentations/src/next.js +18 -6
  31. package/packages/datadog-instrumentations/src/restify.js +14 -1
  32. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  33. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  34. package/packages/datadog-plugin-cucumber/src/index.js +34 -2
  35. package/packages/datadog-plugin-cypress/src/plugin.js +60 -8
  36. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  37. package/packages/datadog-plugin-http/src/client.js +19 -2
  38. package/packages/datadog-plugin-jest/src/index.js +38 -4
  39. package/packages/datadog-plugin-kafkajs/src/consumer.js +59 -6
  40. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -6
  41. package/packages/datadog-plugin-mocha/src/index.js +32 -1
  42. package/packages/datadog-plugin-next/src/index.js +40 -14
  43. package/packages/datadog-plugin-playwright/src/index.js +17 -1
  44. package/packages/dd-trace/src/appsec/activation.js +29 -0
  45. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  46. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  47. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  48. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  49. package/packages/dd-trace/src/appsec/channels.js +5 -2
  50. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  51. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  52. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  53. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  54. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  55. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  56. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  57. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  58. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  59. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  60. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  62. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  63. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  64. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  65. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  66. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  67. package/packages/dd-trace/src/appsec/index.js +33 -32
  68. package/packages/dd-trace/src/appsec/recommended.json +1737 -120
  69. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
  70. package/packages/dd-trace/src/appsec/remote_config/index.js +40 -15
  71. package/packages/dd-trace/src/appsec/reporter.js +50 -34
  72. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  73. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  74. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  75. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  78. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +110 -59
  80. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +40 -7
  81. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
  82. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  83. package/packages/dd-trace/src/config.js +145 -63
  84. package/packages/dd-trace/src/datastreams/processor.js +166 -26
  85. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
  86. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  87. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  88. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  89. package/packages/dd-trace/src/format.js +6 -1
  90. package/packages/dd-trace/src/id.js +12 -0
  91. package/packages/dd-trace/src/iitm.js +1 -1
  92. package/packages/dd-trace/src/log/channels.js +1 -1
  93. package/packages/dd-trace/src/noop/proxy.js +4 -0
  94. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  95. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  96. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  97. package/packages/dd-trace/src/opentracing/span.js +6 -0
  98. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  99. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  100. package/packages/dd-trace/src/plugin_manager.js +1 -1
  101. package/packages/dd-trace/src/plugins/ci_plugin.js +46 -9
  102. package/packages/dd-trace/src/plugins/database.js +1 -1
  103. package/packages/dd-trace/src/plugins/index.js +6 -0
  104. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  105. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  106. package/packages/dd-trace/src/plugins/util/exec.js +23 -2
  107. package/packages/dd-trace/src/plugins/util/git.js +98 -22
  108. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  109. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  110. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  111. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  112. package/packages/dd-trace/src/priority_sampler.js +30 -38
  113. package/packages/dd-trace/src/profiler.js +5 -3
  114. package/packages/dd-trace/src/profiling/config.js +26 -2
  115. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
  116. package/packages/dd-trace/src/profiling/profiler.js +17 -10
  117. package/packages/dd-trace/src/profiling/profilers/events.js +264 -0
  118. package/packages/dd-trace/src/profiling/profilers/shared.js +39 -0
  119. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  120. package/packages/dd-trace/src/profiling/profilers/wall.js +121 -58
  121. package/packages/dd-trace/src/proxy.js +25 -1
  122. package/packages/dd-trace/src/ritm.js +1 -1
  123. package/packages/dd-trace/src/sampling_rule.js +130 -0
  124. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  125. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  126. package/packages/dd-trace/src/span_processor.js +4 -0
  127. package/packages/dd-trace/src/span_sampler.js +6 -64
  128. package/packages/dd-trace/src/spanleak.js +98 -0
  129. package/packages/dd-trace/src/startup-log.js +7 -1
  130. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  131. package/packages/dd-trace/src/telemetry/index.js +171 -41
  132. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  133. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  134. package/packages/dd-trace/src/tracer.js +8 -2
  135. package/scripts/install_plugin_modules.js +11 -3
  136. package/packages/diagnostics_channel/index.js +0 -3
  137. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -6,6 +6,8 @@ const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
  const { COMPONENT } = require('../../dd-trace/src/constants')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
8
 
9
+ const errorPages = ['/404', '/500', '/_error', '/_not-found']
10
+
9
11
  class NextPlugin extends ServerPlugin {
10
12
  static get id () {
11
13
  return 'next'
@@ -40,6 +42,13 @@ class NextPlugin extends ServerPlugin {
40
42
  }
41
43
 
42
44
  error ({ span, error }) {
45
+ if (!span) {
46
+ const store = storage.getStore()
47
+ if (!store) return
48
+
49
+ span = store.span
50
+ }
51
+
43
52
  this.addError(error, span)
44
53
  }
45
54
 
@@ -50,10 +59,20 @@ class NextPlugin extends ServerPlugin {
50
59
 
51
60
  const span = store.span
52
61
  const error = span.context()._tags['error']
53
-
54
- if (!this.config.validateStatus(res.statusCode) && !error) {
55
- span.setTag('error', req.error || nextRequest.error || true)
56
- web.addError(req, req.error || nextRequest.error || true)
62
+ const requestError = req.error || nextRequest.error
63
+
64
+ if (requestError) {
65
+ // prioritize user-set errors from API routes
66
+ span.setTag('error', requestError)
67
+ web.addError(req, requestError)
68
+ } else if (error) {
69
+ // general error handling
70
+ span.setTag('error', error)
71
+ web.addError(req, requestError || error)
72
+ } else if (!this.config.validateStatus(res.statusCode)) {
73
+ // where there's no error, we still need to validate status
74
+ span.setTag('error', true)
75
+ web.addError(req, true)
57
76
  }
58
77
 
59
78
  span.addTags({
@@ -65,7 +84,7 @@ class NextPlugin extends ServerPlugin {
65
84
  span.finish()
66
85
  }
67
86
 
68
- pageLoad ({ page, isAppPath = false }) {
87
+ pageLoad ({ page, isAppPath = false, isStatic = false }) {
69
88
  const store = storage.getStore()
70
89
 
71
90
  if (!store) return
@@ -73,21 +92,28 @@ class NextPlugin extends ServerPlugin {
73
92
  const span = store.span
74
93
  const req = this._requests.get(span)
75
94
 
95
+ // safeguard against missing req in complicated timeout scenarios
96
+ if (!req) return
97
+
76
98
  // Only use error page names if there's not already a name
77
99
  const current = span.context()._tags['next.page']
78
- if (current && ['/404', '/500', '/_error', '/_not-found'].includes(page)) {
100
+ const isErrorPage = errorPages.includes(page)
101
+
102
+ if (current && isErrorPage) {
79
103
  return
80
104
  }
81
105
 
82
106
  // remove ending /route or /page for appDir projects
83
- if (isAppPath) page = page.substring(0, page.lastIndexOf('/'))
84
-
85
- // This is for static files whose 'page' includes the whole file path
86
- // For normal page matches, like /api/hello/[name] and a req.url like /api/hello/world,
87
- // nothing should happen
88
- // For page matches like /User/something/public/text.txt and req.url like /text.txt,
89
- // it should disregard the extra absolute path Next.js sometimes sets
90
- if (page.includes(req.url)) page = req.url
107
+ // need to check if not an error page too, as those are marked as app directory
108
+ // in newer versions
109
+ if (isAppPath && !isErrorPage) page = page.substring(0, page.lastIndexOf('/'))
110
+
111
+ // handle static resource
112
+ if (isStatic) {
113
+ page = req.url.includes('_next/static')
114
+ ? '/_next/static/*'
115
+ : '/public/*'
116
+ }
91
117
 
92
118
  span.addTags({
93
119
  [COMPONENT]: this.constructor.id,
@@ -8,10 +8,15 @@ const {
8
8
  finishAllTraceSpans,
9
9
  getTestSuitePath,
10
10
  getTestSuiteCommonTags,
11
- TEST_SOURCE_START
11
+ TEST_SOURCE_START,
12
+ TEST_CODE_OWNERS
12
13
  } = require('../../dd-trace/src/plugins/util/test')
13
14
  const { RESOURCE_NAME } = require('../../../ext/tags')
14
15
  const { COMPONENT } = require('../../dd-trace/src/constants')
16
+ const {
17
+ TELEMETRY_EVENT_CREATED,
18
+ TELEMETRY_EVENT_FINISHED
19
+ } = require('../../dd-trace/src/ci-visibility/telemetry')
15
20
 
16
21
  class PlaywrightPlugin extends CiPlugin {
17
22
  static get id () {
@@ -28,7 +33,9 @@ class PlaywrightPlugin extends CiPlugin {
28
33
  this.testSessionSpan.setTag(TEST_STATUS, status)
29
34
 
30
35
  this.testModuleSpan.finish()
36
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
31
37
  this.testSessionSpan.finish()
38
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
32
39
  finishAllTraceSpans(this.testSessionSpan)
33
40
  this.tracer._exporter.flush(onDone)
34
41
  })
@@ -52,6 +59,7 @@ class PlaywrightPlugin extends CiPlugin {
52
59
  ...testSuiteMetadata
53
60
  }
54
61
  })
62
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
55
63
  this.enter(testSuiteSpan, store)
56
64
 
57
65
  this._testSuites.set(testSuite, testSuiteSpan)
@@ -63,6 +71,7 @@ class PlaywrightPlugin extends CiPlugin {
63
71
  if (!span) return
64
72
  span.setTag(TEST_STATUS, status)
65
73
  span.finish()
74
+ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
66
75
  })
67
76
 
68
77
  this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine }) => {
@@ -104,6 +113,13 @@ class PlaywrightPlugin extends CiPlugin {
104
113
  })
105
114
 
106
115
  span.finish()
116
+
117
+ this.telemetry.ciVisEvent(
118
+ TELEMETRY_EVENT_FINISHED,
119
+ 'test',
120
+ { hasCodeOwners: !!span.context()._tags[TEST_CODE_OWNERS] }
121
+ )
122
+
107
123
  finishAllTraceSpans(span)
108
124
  })
109
125
  }
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ const Activation = {
4
+ ONECLICK: 'OneClick',
5
+ ENABLED: 'Enabled',
6
+ DISABLED: 'Disabled',
7
+
8
+ fromConfig (config) {
9
+ switch (config.appsec.enabled) {
10
+ // ASM is activated by an env var DD_APPSEC_ENABLED=true
11
+ case true:
12
+ return Activation.ENABLED
13
+
14
+ // ASM is disabled by an env var DD_APPSEC_ENABLED=false
15
+ case false:
16
+ return Activation.DISABLED
17
+
18
+ // ASM is activated by one click remote config
19
+ case undefined:
20
+ return Activation.ONECLICK
21
+
22
+ // Any other value should never occur
23
+ default:
24
+ return Activation.DISABLED
25
+ }
26
+ }
27
+ }
28
+
29
+ module.exports = Activation
@@ -13,8 +13,10 @@ module.exports = {
13
13
  HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
14
14
  // TODO: 'server.response.trailers',
15
15
  HTTP_INCOMING_GRAPHQL_RESOLVERS: 'graphql.server.all_resolvers',
16
+ HTTP_INCOMING_GRAPHQL_RESOLVER: 'graphql.server.resolver',
16
17
 
17
18
  HTTP_CLIENT_IP: 'http.client_ip',
18
19
 
19
- USER_ID: 'usr.id'
20
+ USER_ID: 'usr.id',
21
+ WAF_CONTEXT_PROCESSOR: 'waf.context.processor'
20
22
  }
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+
5
+ let enabled
6
+ let requestSampling
7
+
8
+ function configure ({ apiSecurity }) {
9
+ enabled = apiSecurity.enabled
10
+ setRequestSampling(apiSecurity.requestSampling)
11
+ }
12
+
13
+ function disable () {
14
+ enabled = false
15
+ }
16
+
17
+ function setRequestSampling (sampling) {
18
+ requestSampling = parseRequestSampling(sampling)
19
+ }
20
+
21
+ function parseRequestSampling (requestSampling) {
22
+ let parsed = parseFloat(requestSampling)
23
+
24
+ if (isNaN(parsed)) {
25
+ log.warn(`Incorrect API Security request sampling value: ${requestSampling}`)
26
+
27
+ parsed = 0
28
+ } else {
29
+ parsed = Math.min(1, Math.max(0, parsed))
30
+ }
31
+
32
+ return parsed
33
+ }
34
+
35
+ function sampleRequest () {
36
+ if (!enabled || !requestSampling) {
37
+ return false
38
+ }
39
+
40
+ return Math.random() <= requestSampling
41
+ }
42
+
43
+ module.exports = {
44
+ configure,
45
+ disable,
46
+ setRequestSampling,
47
+ sampleRequest
48
+ }
@@ -5,7 +5,10 @@ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta n
5
5
 
6
6
  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."}]}`
7
7
 
8
+ const graphqlJson = `{"errors":[{"message":"You've been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}]}`
9
+
8
10
  module.exports = {
9
11
  html,
10
- json
12
+ json,
13
+ graphqlJson
11
14
  }
@@ -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,12 +1,15 @@
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'),
@@ -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'),