dd-trace 4.18.0 → 4.22.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 (110) 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 +11 -10
  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/graphql.js +18 -4
  15. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  18. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  19. package/packages/datadog-instrumentations/src/http/client.js +10 -0
  20. package/packages/datadog-instrumentations/src/jest.js +11 -5
  21. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  22. package/packages/datadog-instrumentations/src/next.js +18 -6
  23. package/packages/datadog-instrumentations/src/restify.js +14 -1
  24. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  25. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  26. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  27. package/packages/datadog-plugin-http/src/client.js +19 -2
  28. package/packages/datadog-plugin-kafkajs/src/consumer.js +59 -6
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -6
  30. package/packages/datadog-plugin-next/src/index.js +40 -14
  31. package/packages/dd-trace/src/appsec/activation.js +29 -0
  32. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  33. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  34. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  35. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  36. package/packages/dd-trace/src/appsec/channels.js +5 -2
  37. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  38. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  39. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  40. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  41. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  42. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  46. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  47. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  52. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  53. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  54. package/packages/dd-trace/src/appsec/index.js +33 -32
  55. package/packages/dd-trace/src/appsec/recommended.json +1737 -120
  56. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  57. package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
  58. package/packages/dd-trace/src/appsec/reporter.js +50 -34
  59. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  60. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  61. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  62. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  63. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  64. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
  65. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +22 -6
  66. package/packages/dd-trace/src/config.js +48 -7
  67. package/packages/dd-trace/src/datastreams/processor.js +166 -26
  68. package/packages/dd-trace/src/format.js +6 -1
  69. package/packages/dd-trace/src/id.js +12 -0
  70. package/packages/dd-trace/src/iitm.js +1 -1
  71. package/packages/dd-trace/src/log/channels.js +1 -1
  72. package/packages/dd-trace/src/noop/proxy.js +4 -0
  73. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  74. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  75. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  76. package/packages/dd-trace/src/opentracing/span.js +6 -0
  77. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  78. package/packages/dd-trace/src/plugin_manager.js +1 -1
  79. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
  80. package/packages/dd-trace/src/plugins/database.js +1 -1
  81. package/packages/dd-trace/src/plugins/index.js +1 -0
  82. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  83. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  84. package/packages/dd-trace/src/plugins/util/git.js +4 -3
  85. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  86. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  87. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  88. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  89. package/packages/dd-trace/src/profiler.js +5 -3
  90. package/packages/dd-trace/src/profiling/config.js +26 -2
  91. package/packages/dd-trace/src/profiling/profiler.js +17 -10
  92. package/packages/dd-trace/src/profiling/profilers/events.js +264 -0
  93. package/packages/dd-trace/src/profiling/profilers/shared.js +39 -0
  94. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  95. package/packages/dd-trace/src/profiling/profilers/wall.js +121 -58
  96. package/packages/dd-trace/src/proxy.js +25 -1
  97. package/packages/dd-trace/src/ritm.js +1 -1
  98. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  99. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  100. package/packages/dd-trace/src/span_processor.js +4 -0
  101. package/packages/dd-trace/src/spanleak.js +98 -0
  102. package/packages/dd-trace/src/startup-log.js +7 -1
  103. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  104. package/packages/dd-trace/src/telemetry/index.js +136 -44
  105. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  106. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  107. package/packages/dd-trace/src/tracer.js +8 -2
  108. package/scripts/install_plugin_modules.js +11 -3
  109. package/packages/diagnostics_channel/index.js +0 -3
  110. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -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'),
@@ -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.isAccessControlAllowOriginExclusion(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
+ isAccessControlAllowOriginExclusion (name, ranges) {
88
+ if (name === 'access-control-allow-origin') {
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()
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('../../../../diagnostics_channel')
3
+ const dc = require('dc-polyfill')
4
4
  const log = require('../../log')
5
5
  const { calculateDDBasePath } = require('../../util')
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { channel } = require('../../../../diagnostics_channel')
3
+ const { channel } = require('dc-polyfill')
4
4
 
5
5
  const iastLog = require('./iast-log')
6
6
  const Plugin = require('../../plugins/plugin')
@@ -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('dc-polyfill')
7
7
  const iastContextFunctions = require('./iast-context')
8
8
  const {
9
9
  enableTaintTracking,
@@ -13,7 +13,7 @@ const pathLine = {
13
13
  }
14
14
 
15
15
  const EXCLUDED_PATHS = [
16
- path.join(path.sep, 'node_modules', 'diagnostics_channel')
16
+ path.join(path.sep, 'node_modules', 'dc-polyfill')
17
17
  ]
18
18
  const EXCLUDED_PATH_PREFIXES = [
19
19
  'node:diagnostics_channel',
@@ -7,7 +7,7 @@ const { isPrivateModule, isNotLibraryFile } = require('./filter')
7
7
  const { csiMethods } = require('./csi-methods')
8
8
  const { getName } = require('../telemetry/verbosity')
9
9
  const { getRewriteFunction } = require('./rewriter-telemetry')
10
- const dc = require('../../../../../diagnostics_channel')
10
+ const dc = require('dc-polyfill')
11
11
 
12
12
  const hardcodedSecretCh = dc.channel('datadog:secrets:result')
13
13
  let rewriter
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const HEADER_NAME_VALUE_SEPARATOR = ': '
4
+
5
+ module.exports = {
6
+ HEADER_NAME_VALUE_SEPARATOR
7
+ }
@@ -3,27 +3,20 @@
3
3
  const iastLog = require('../../../iast-log')
4
4
 
5
5
  const COMMAND_PATTERN = '^(?:\\s*(?:sudo|doas)\\s+)?\\b\\S+\\b\\s(.*)'
6
+ const pattern = new RegExp(COMMAND_PATTERN, 'gmi')
6
7
 
7
- class CommandSensitiveAnalyzer {
8
- constructor () {
9
- this._pattern = new RegExp(COMMAND_PATTERN, 'gmi')
10
- }
11
-
12
- extractSensitiveRanges (evidence) {
13
- try {
14
- this._pattern.lastIndex = 0
8
+ module.exports = function extractSensitiveRanges (evidence) {
9
+ try {
10
+ pattern.lastIndex = 0
15
11
 
16
- const regexResult = this._pattern.exec(evidence.value)
17
- if (regexResult && regexResult.length > 1) {
18
- const start = regexResult.index + (regexResult[0].length - regexResult[1].length)
19
- const end = start + regexResult[1].length
20
- return [{ start, end }]
21
- }
22
- } catch (e) {
23
- iastLog.debug(e)
12
+ const regexResult = pattern.exec(evidence.value)
13
+ if (regexResult && regexResult.length > 1) {
14
+ const start = regexResult.index + (regexResult[0].length - regexResult[1].length)
15
+ const end = start + regexResult[1].length
16
+ return [{ start, end }]
24
17
  }
25
- return []
18
+ } catch (e) {
19
+ iastLog.debug(e)
26
20
  }
21
+ return []
27
22
  }
28
-
29
- module.exports = CommandSensitiveAnalyzer