dd-trace 2.33.0 → 2.35.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 (120) hide show
  1. package/index.d.ts +8 -1
  2. package/package.json +5 -4
  3. package/packages/datadog-instrumentations/src/cucumber.js +13 -0
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  5. package/packages/datadog-instrumentations/src/helpers/register.js +4 -0
  6. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  7. package/packages/datadog-instrumentations/src/http/server.js +14 -0
  8. package/packages/datadog-instrumentations/src/http2/client.js +4 -0
  9. package/packages/datadog-instrumentations/src/jest.js +20 -17
  10. package/packages/datadog-instrumentations/src/next.js +6 -1
  11. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  12. package/packages/datadog-instrumentations/src/sequelize.js +51 -0
  13. package/packages/datadog-plugin-amqp10/src/consumer.js +1 -3
  14. package/packages/datadog-plugin-amqp10/src/producer.js +1 -3
  15. package/packages/datadog-plugin-amqplib/src/client.js +4 -3
  16. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  17. package/packages/datadog-plugin-amqplib/src/producer.js +1 -3
  18. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -0
  19. package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +2 -1
  20. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +4 -2
  21. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +4 -3
  22. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -1
  23. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -0
  24. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +2 -1
  25. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +2 -1
  26. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +8 -1
  27. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +7 -1
  28. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  29. package/packages/datadog-plugin-cypress/src/plugin.js +150 -30
  30. package/packages/datadog-plugin-cypress/src/support.js +6 -3
  31. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -3
  32. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -3
  33. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -3
  34. package/packages/datadog-plugin-http/src/client.js +70 -67
  35. package/packages/datadog-plugin-http2/src/client.js +50 -46
  36. package/packages/datadog-plugin-jest/src/index.js +5 -4
  37. package/packages/datadog-plugin-jest/src/util.js +10 -1
  38. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -4
  39. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -3
  40. package/packages/datadog-plugin-memcached/src/index.js +2 -3
  41. package/packages/datadog-plugin-mocha/src/index.js +4 -2
  42. package/packages/datadog-plugin-pg/src/index.js +1 -1
  43. package/packages/datadog-plugin-redis/src/index.js +2 -13
  44. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  45. package/packages/datadog-plugin-rhea/src/producer.js +1 -5
  46. package/packages/dd-trace/src/appsec/blocked_templates.js +2 -101
  47. package/packages/dd-trace/src/appsec/blocking.js +60 -11
  48. package/packages/dd-trace/src/appsec/channels.js +3 -2
  49. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +7 -5
  50. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +2 -1
  51. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +3 -0
  52. package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +31 -0
  53. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +2 -1
  54. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +26 -5
  55. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +47 -0
  56. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +65 -4
  57. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +26 -0
  58. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +35 -3
  59. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +2 -1
  60. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -1
  61. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  62. package/packages/dd-trace/src/appsec/iast/path-line.js +16 -8
  63. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +19 -4
  64. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js +37 -0
  65. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +29 -0
  66. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +35 -0
  67. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +118 -0
  68. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +49 -0
  69. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +146 -0
  70. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +113 -0
  71. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +10 -0
  72. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -109
  73. package/packages/dd-trace/src/appsec/recommended.json +45 -46
  74. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +3 -1
  75. package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
  76. package/packages/dd-trace/src/appsec/rule_manager.js +49 -6
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -7
  78. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -6
  80. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +10 -4
  81. package/packages/dd-trace/src/config.js +86 -9
  82. package/packages/dd-trace/src/constants.js +3 -1
  83. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +11 -3
  84. package/packages/dd-trace/src/exporters/common/util.js +9 -0
  85. package/packages/dd-trace/src/exporters/common/writer.js +3 -2
  86. package/packages/dd-trace/src/git_metadata_tagger.js +17 -0
  87. package/packages/dd-trace/src/git_properties.js +32 -0
  88. package/packages/dd-trace/src/plugin_manager.js +2 -0
  89. package/packages/dd-trace/src/plugins/cache.js +7 -0
  90. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
  91. package/packages/dd-trace/src/plugins/client.js +3 -2
  92. package/packages/dd-trace/src/plugins/consumer.js +14 -2
  93. package/packages/dd-trace/src/plugins/database.js +2 -2
  94. package/packages/dd-trace/src/plugins/inbound.js +7 -0
  95. package/packages/dd-trace/src/plugins/{outgoing.js → outbound.js} +2 -2
  96. package/packages/dd-trace/src/plugins/producer.js +19 -2
  97. package/packages/dd-trace/src/plugins/server.js +2 -2
  98. package/packages/dd-trace/src/plugins/storage.js +2 -0
  99. package/packages/dd-trace/src/plugins/tracing.js +11 -0
  100. package/packages/dd-trace/src/plugins/util/ci.js +63 -8
  101. package/packages/dd-trace/src/plugins/util/tags.js +5 -1
  102. package/packages/dd-trace/src/profiling/config.js +4 -2
  103. package/packages/dd-trace/src/profiling/constants.js +0 -1
  104. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  105. package/packages/dd-trace/src/proxy.js +4 -0
  106. package/packages/dd-trace/src/serverless.js +25 -0
  107. package/packages/dd-trace/src/service-naming/index.js +30 -0
  108. package/packages/dd-trace/src/service-naming/schemas/definition.js +24 -0
  109. package/packages/dd-trace/src/service-naming/schemas/index.js +6 -0
  110. package/packages/dd-trace/src/service-naming/schemas/util.js +5 -0
  111. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +5 -0
  112. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +64 -0
  113. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +33 -0
  114. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +5 -0
  115. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +52 -0
  116. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +21 -0
  117. package/packages/dd-trace/src/span_processor.js +3 -0
  118. package/packages/dd-trace/src/tracer.js +3 -2
  119. package/version.js +9 -0
  120. package/packages/dd-trace/src/plugins/incoming.js +0 -7
@@ -8,12 +8,9 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
8
8
 
9
9
  start ({ topic, partition, message }) {
10
10
  const childOf = extract(this.tracer, message.headers)
11
-
12
- this.startSpan('kafka.consume', {
11
+ this.startSpan({
13
12
  childOf,
14
- service: this.config.service || `${this.tracer._service}-kafka`,
15
13
  resource: topic,
16
- kind: 'consumer',
17
14
  type: 'worker',
18
15
  meta: {
19
16
  'component': 'kafkajs',
@@ -7,10 +7,8 @@ class KafkajsProducerPlugin extends ProducerPlugin {
7
7
  static get operation () { return 'produce' }
8
8
 
9
9
  start ({ topic, messages }) {
10
- const span = this.startSpan('kafka.produce', {
11
- service: this.config.service || `${this.tracer._service}-kafka`,
10
+ const span = this.startSpan({
12
11
  resource: topic,
13
- kind: 'producer',
14
12
  meta: {
15
13
  'component': 'kafkajs',
16
14
  'kafka.topic': topic
@@ -9,11 +9,10 @@ class MemcachedPlugin extends CachePlugin {
9
9
  start ({ client, server, query }) {
10
10
  const address = getAddress(client, server, query)
11
11
 
12
- this.startSpan('memcached.command', {
13
- service: this.config.service,
12
+ this.startSpan({
13
+ service: this.serviceName(this.config, this.system),
14
14
  resource: query.type,
15
15
  type: 'memcached',
16
- kind: 'client',
17
16
  meta: {
18
17
  'memcached.command': query.command,
19
18
  'out.host': address[0],
@@ -36,9 +36,11 @@ class MochaPlugin extends CiPlugin {
36
36
  const relativeCoverageFiles = [...coverageFiles, suiteFile]
37
37
  .map(filename => getTestSuitePath(filename, this.sourceRoot))
38
38
 
39
+ const { _traceId, _spanId } = testSuiteSpan.context()
40
+
39
41
  const formattedCoverage = {
40
- traceId: testSuiteSpan.context()._traceId,
41
- spanId: testSuiteSpan.context()._spanId,
42
+ sessionId: _traceId,
43
+ suiteId: _spanId,
42
44
  files: relativeCoverageFiles
43
45
  }
44
46
 
@@ -27,7 +27,7 @@ class PGPlugin extends DatabasePlugin {
27
27
  }
28
28
  })
29
29
 
30
- query.text = this.injectDbmQuery(query.text, service)
30
+ query.text = this.injectDbmQuery(query.text, service, !!query.name)
31
31
  }
32
32
  }
33
33
 
@@ -13,11 +13,10 @@ class RedisPlugin extends CachePlugin {
13
13
  const normalizedCommand = command.toUpperCase()
14
14
  if (!this.config.filter(normalizedCommand)) return this.skip()
15
15
 
16
- this.startSpan('redis.command', {
17
- service: getService(this.config, connectionName),
16
+ this.startSpan({
18
17
  resource,
18
+ service: this.serviceName(this.config, this.system, connectionName),
19
19
  type: 'redis',
20
- kind: 'client',
21
20
  meta: {
22
21
  'db.type': 'redis',
23
22
  'db.name': db || '0',
@@ -33,16 +32,6 @@ class RedisPlugin extends CachePlugin {
33
32
  }
34
33
  }
35
34
 
36
- function getService (config, connectionName) {
37
- if (config.splitByInstance && connectionName) {
38
- return config.service
39
- ? `${config.service}-${connectionName}`
40
- : connectionName
41
- }
42
-
43
- return config.service
44
- }
45
-
46
35
  function formatCommand (command, args) {
47
36
  if (!args || command === 'AUTH') return command
48
37
 
@@ -19,12 +19,10 @@ class RheaConsumerPlugin extends ConsumerPlugin {
19
19
  const name = getResourceNameFromMessage(msgObj)
20
20
  const childOf = extractTextMap(msgObj, this.tracer)
21
21
 
22
- this.startSpan('amqp.receive', {
22
+ this.startSpan({
23
23
  childOf,
24
- service: this.config.service,
25
24
  resource: name,
26
25
  type: 'worker',
27
- kind: 'consumer',
28
26
  meta: {
29
27
  'component': 'rhea',
30
28
  'amqp.link.source.address': name,
@@ -9,17 +9,13 @@ class RheaProducerPlugin extends ProducerPlugin {
9
9
 
10
10
  constructor (...args) {
11
11
  super(...args)
12
-
13
12
  this.addTraceSub('encode', this.encode.bind(this))
14
13
  }
15
14
 
16
15
  start ({ targetAddress, host, port }) {
17
16
  const name = targetAddress || 'amq.topic'
18
-
19
- this.startSpan('amqp.send', {
20
- service: this.config.service || `${this.tracer._service}-amqp-producer`,
17
+ this.startSpan({
21
18
  resource: name,
22
- kind: 'producer',
23
19
  meta: {
24
20
  'component': 'rhea',
25
21
  'amqp.link.target.address': name,
@@ -1,108 +1,9 @@
1
1
  /* eslint-disable max-len */
2
2
  'use strict'
3
3
 
4
- const html = `<!-- Sorry, you’ve been blocked -->
5
- <!DOCTYPE html>
6
- <html lang="en">
4
+ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>`
7
5
 
8
- <head>
9
- <meta charset="UTF-8">
10
- <meta name="viewport" content="width=device-width,initial-scale=1">
11
- <title>You've been blocked</title>
12
- <style>
13
- a,
14
- body,
15
- div,
16
- html,
17
- span {
18
- margin: 0;
19
- padding: 0;
20
- border: 0;
21
- font-size: 100%;
22
- font: inherit;
23
- vertical-align: baseline
24
- }
25
-
26
- body {
27
- background: -webkit-radial-gradient(26% 19%, circle, #fff, #f4f7f9);
28
- background: radial-gradient(circle at 26% 19%, #fff, #f4f7f9);
29
- display: -webkit-box;
30
- display: -ms-flexbox;
31
- display: flex;
32
- -webkit-box-pack: center;
33
- -ms-flex-pack: center;
34
- justify-content: center;
35
- -webkit-box-align: center;
36
- -ms-flex-align: center;
37
- align-items: center;
38
- -ms-flex-line-pack: center;
39
- align-content: center;
40
- width: 100%;
41
- min-height: 100vh;
42
- line-height: 1;
43
- flex-direction: column
44
- }
45
-
46
- p {
47
- display: block
48
- }
49
-
50
-
51
- main {
52
- text-align: center;
53
- flex: 1;
54
- display: -webkit-box;
55
- display: -ms-flexbox;
56
- display: flex;
57
- -webkit-box-pack: center;
58
- -ms-flex-pack: center;
59
- justify-content: center;
60
- -webkit-box-align: center;
61
- -ms-flex-align: center;
62
- align-items: center;
63
- -ms-flex-line-pack: center;
64
- align-content: center;
65
- flex-direction: column
66
- }
67
-
68
- p {
69
- font-size: 18px;
70
- line-height: normal;
71
- color: #646464;
72
- font-family: sans-serif;
73
- font-weight: 400
74
- }
75
-
76
- a {
77
- color: #4842b7
78
- }
79
-
80
- footer {
81
- width: 100%;
82
- text-align: center
83
- }
84
-
85
- footer p {
86
- font-size: 16px
87
- }
88
- </style>
89
- </head>
90
-
91
- <body>
92
- <main>
93
- <p>Sorry, you cannot access this page. Please contact the customer service team.</p>
94
- </main>
95
- <footer>
96
- <p>Security provided by <a
97
- href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/"
98
- target="_blank">Datadog</a></p>
99
- </footer>
100
- </body>
101
-
102
- </html>
103
- `
104
-
105
- const json = `{"errors": [{"title": "You've been blocked", "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}`
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."}]}`
106
7
 
107
8
  module.exports = {
108
9
  html,
@@ -5,32 +5,62 @@ const blockedTemplates = require('./blocked_templates')
5
5
 
6
6
  let templateHtml = blockedTemplates.html
7
7
  let templateJson = blockedTemplates.json
8
+ let blockingConfiguration
8
9
 
9
- function block (req, res, rootSpan, abortController) {
10
- if (res.headersSent) {
11
- log.warn('Cannot send blocking response when headers have already been sent')
12
- return
10
+ function blockWithRedirect (res, rootSpan, abortController) {
11
+ rootSpan.addTags({
12
+ 'appsec.blocked': 'true'
13
+ })
14
+
15
+ let statusCode = blockingConfiguration.parameters.status_code
16
+ if (!statusCode || statusCode < 300 || statusCode >= 400) {
17
+ statusCode = 303
13
18
  }
14
19
 
20
+ res.writeHead(statusCode, {
21
+ 'Location': blockingConfiguration.parameters.location
22
+ }).end()
23
+
24
+ if (abortController) {
25
+ abortController.abort()
26
+ }
27
+ }
28
+
29
+ function blockWithContent (req, res, rootSpan, abortController) {
15
30
  let type
16
31
  let body
17
32
 
18
33
  // parse the Accept header, ex: Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
19
34
  const accept = req.headers.accept && req.headers.accept.split(',').map((str) => str.split(';', 1)[0].trim())
20
35
 
21
- if (accept && accept.includes('text/html') && !accept.includes('application/json')) {
22
- type = 'text/html; charset=utf-8'
23
- body = templateHtml
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
40
+ } else {
41
+ type = 'application/json'
42
+ body = templateJson
43
+ }
24
44
  } else {
25
- type = 'application/json'
26
- body = templateJson
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
+ }
27
52
  }
28
53
 
29
54
  rootSpan.addTags({
30
55
  'appsec.blocked': 'true'
31
56
  })
32
57
 
33
- res.statusCode = 403
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
+ }
34
64
  res.setHeader('Content-Type', type)
35
65
  res.setHeader('Content-Length', Buffer.byteLength(body))
36
66
  res.end(body)
@@ -40,6 +70,20 @@ function block (req, res, rootSpan, abortController) {
40
70
  }
41
71
  }
42
72
 
73
+ function block (req, res, rootSpan, abortController) {
74
+ if (res.headersSent) {
75
+ log.warn('Cannot send blocking response when headers have already been sent')
76
+ return
77
+ }
78
+
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
+ }
85
+ }
86
+
43
87
  function setTemplates (config) {
44
88
  if (config.appsec.blockedTemplateHtml) {
45
89
  templateHtml = config.appsec.blockedTemplateHtml
@@ -49,7 +93,12 @@ function setTemplates (config) {
49
93
  }
50
94
  }
51
95
 
96
+ function updateBlockingConfiguration (newBlockingConfiguration) {
97
+ blockingConfiguration = newBlockingConfiguration
98
+ }
99
+
52
100
  module.exports = {
53
101
  block,
54
- setTemplates
102
+ setTemplates,
103
+ updateBlockingConfiguration
55
104
  }
@@ -4,8 +4,9 @@ const dc = require('../../../diagnostics_channel')
4
4
 
5
5
  // TODO: use TBD naming convention
6
6
  module.exports = {
7
+ bodyParser: dc.channel('datadog:body-parser:read:finish'),
7
8
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
8
9
  incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
9
- bodyParser: dc.channel('datadog:body-parser:read:finish'),
10
- queryParser: dc.channel('datadog:query:read:finish')
10
+ queryParser: dc.channel('datadog:query:read:finish'),
11
+ setCookieChannel: dc.channel('datadog:iast:set-cookie')
11
12
  }
@@ -1,10 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
- 'WEAK_CIPHER_ANALYZER': require('./weak-cipher-analyzer'),
5
- 'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer'),
6
- 'SQL_INJECTION_ANALYZER': require('./sql-injection-analyzer'),
7
- 'PATH_TRAVERSAL_ANALYZER': require('./path-traversal-analyzer'),
8
4
  'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
9
- 'LDAP_ANALYZER': require('./ldap-injection-analyzer')
5
+ 'INSECURE_COOKIE_ANALYZER': require('./insecure-cookie-analyzer'),
6
+ 'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
7
+ 'PATH_TRAVERSAL_ANALYZER': require('./path-traversal-analyzer'),
8
+ 'SQL_INJECTION_ANALYZER': require('./sql-injection-analyzer'),
9
+ 'SSRF': require('./ssrf-analyzer'),
10
+ 'WEAK_CIPHER_ANALYZER': require('./weak-cipher-analyzer'),
11
+ 'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer')
10
12
  }
@@ -1,9 +1,10 @@
1
1
  'use strict'
2
2
  const InjectionAnalyzer = require('./injection-analyzer')
3
+ const { COMMAND_INJECTION } = require('../vulnerabilities')
3
4
 
4
5
  class CommandInjectionAnalyzer extends InjectionAnalyzer {
5
6
  constructor () {
6
- super('COMMAND_INJECTION')
7
+ super(COMMAND_INJECTION)
7
8
  this.addSub('datadog:child_process:execution:start', ({ command }) => this.analyze(command))
8
9
  }
9
10
  }
@@ -1,14 +1,17 @@
1
1
  'use strict'
2
2
 
3
3
  const analyzers = require('./analyzers')
4
+ const setCookiesHeaderInterceptor = require('./set-cookies-header-interceptor')
4
5
 
5
6
  function enableAllAnalyzers () {
7
+ setCookiesHeaderInterceptor.configure(true)
6
8
  for (const analyzer in analyzers) {
7
9
  analyzers[analyzer].configure(true)
8
10
  }
9
11
  }
10
12
 
11
13
  function disableAllAnalyzers () {
14
+ setCookiesHeaderInterceptor.configure(false)
12
15
  for (const analyzer in analyzers) {
13
16
  analyzers[analyzer].configure(false)
14
17
  }
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ const Analyzer = require('./vulnerability-analyzer')
4
+ const { INSECURE_COOKIE } = require('../vulnerabilities')
5
+
6
+ const EXCLUDED_PATHS = ['node_modules/express/lib/response.js', 'node_modules\\express\\lib\\response.js']
7
+
8
+ class InsecureCookieAnalyzer extends Analyzer {
9
+ constructor () {
10
+ super(INSECURE_COOKIE)
11
+ this.addSub('datadog:iast:set-cookie', (cookieInfo) => this.analyze(cookieInfo))
12
+ }
13
+
14
+ _isVulnerable ({ cookieProperties, cookieValue }) {
15
+ return cookieValue && !(cookieProperties && cookieProperties.map(x => x.toLowerCase().trim()).includes('secure'))
16
+ }
17
+
18
+ _getEvidence ({ cookieName }) {
19
+ return { value: cookieName }
20
+ }
21
+
22
+ _createHashSource (type, evidence, location) {
23
+ return `${type}:${evidence.value}`
24
+ }
25
+
26
+ _getExcludedPaths () {
27
+ return EXCLUDED_PATHS
28
+ }
29
+ }
30
+
31
+ module.exports = new InsecureCookieAnalyzer()
@@ -1,9 +1,10 @@
1
1
  'use strict'
2
2
  const InjectionAnalyzer = require('./injection-analyzer')
3
+ const { LDAP_INJECTION } = require('../vulnerabilities')
3
4
 
4
5
  class LdapInjectionAnalyzer extends InjectionAnalyzer {
5
6
  constructor () {
6
- super('LDAP_INJECTION')
7
+ super(LDAP_INJECTION)
7
8
  this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
8
9
  }
9
10
  }
@@ -4,11 +4,16 @@ const path = require('path')
4
4
  const { getIastContext } = require('../iast-context')
5
5
  const { storage } = require('../../../../../datadog-core')
6
6
  const InjectionAnalyzer = require('./injection-analyzer')
7
+ const { PATH_TRAVERSAL } = require('../vulnerabilities')
8
+
9
+ const ignoredOperations = ['dir.close', 'close']
7
10
 
8
11
  class PathTraversalAnalyzer extends InjectionAnalyzer {
9
12
  constructor () {
10
- super('PATH_TRAVERSAL')
13
+ super(PATH_TRAVERSAL)
11
14
  this.addSub('apm:fs:operation:start', obj => {
15
+ if (ignoredOperations.includes(obj.operation)) return
16
+
12
17
  const pathArguments = []
13
18
  if (obj.dest) {
14
19
  pathArguments.push(obj.dest)
@@ -40,13 +45,29 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
40
45
  this.analyze(pathArguments)
41
46
  })
42
47
 
43
- this.exclusionList = [ path.join('node_modules', 'send') + path.sep ]
48
+ this.exclusionList = [
49
+ path.join('node_modules', 'send') + path.sep
50
+ ]
51
+
52
+ this.internalExclusionList = [
53
+ 'node:fs',
54
+ 'node:internal/fs',
55
+ 'node:internal\\fs',
56
+ 'fs.js',
57
+ 'internal/fs',
58
+ 'internal\\fs'
59
+ ]
44
60
  }
45
61
 
46
62
  _isExcluded (location) {
47
- let ret = false
63
+ let ret = true
48
64
  if (location && location.path) {
49
- ret = this.exclusionList.some(elem => location.path.includes(elem))
65
+ // Exclude from reporting those vulnerabilities which location is from an internal fs call
66
+ if (location.isInternal) {
67
+ ret = this.internalExclusionList.some(elem => location.path.includes(elem))
68
+ } else {
69
+ ret = this.exclusionList.some(elem => location.path.includes(elem))
70
+ }
50
71
  }
51
72
  return ret
52
73
  }
@@ -59,7 +80,7 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
59
80
 
60
81
  if (value && value.constructor === Array) {
61
82
  for (const val of value) {
62
- if (this._isVulnerable(val, iastContext)) {
83
+ if (this._isVulnerable(val, iastContext) && this._checkOCE(iastContext)) {
63
84
  this._report(val, iastContext)
64
85
  // no support several evidences in the same vulnerability, just report the 1st one
65
86
  break
@@ -0,0 +1,47 @@
1
+ 'use strict'
2
+
3
+ const Plugin = require('../../../plugins/plugin')
4
+ const { setCookieChannel } = require('../../channels')
5
+
6
+ class SetCookiesHeaderInterceptor extends Plugin {
7
+ constructor () {
8
+ super()
9
+ this.cookiesInRequest = new WeakMap()
10
+ this.addSub('datadog:http:server:response:set-header:finish', ({ name, value, res }) => {
11
+ if (name.toLowerCase() === 'set-cookie') {
12
+ let allCookies = value
13
+ if (typeof value === 'string') {
14
+ allCookies = [value]
15
+ }
16
+ const alreadyCheckedCookies = this._getAlreadyCheckedCookiesInResponse(res)
17
+ allCookies.forEach(cookieString => {
18
+ if (!alreadyCheckedCookies.includes(cookieString)) {
19
+ alreadyCheckedCookies.push(cookieString)
20
+ setCookieChannel.publish(this._parseCookie(cookieString))
21
+ }
22
+ })
23
+ }
24
+ })
25
+ }
26
+
27
+ _parseCookie (cookieString) {
28
+ const cookieParts = cookieString.split(';')
29
+ const nameValueParts = cookieParts[0].split('=')
30
+ const cookieName = nameValueParts[0]
31
+ const cookieValue = nameValueParts.slice(1).join('=')
32
+ const cookieProperties = cookieParts.slice(1).map(part => part.trim())
33
+
34
+ return { cookieName, cookieValue, cookieProperties, cookieString }
35
+ }
36
+
37
+ _getAlreadyCheckedCookiesInResponse (res) {
38
+ let alreadyCheckedCookies = this.cookiesInRequest.get(res)
39
+ if (!alreadyCheckedCookies) {
40
+ alreadyCheckedCookies = []
41
+ this.cookiesInRequest.set(res, alreadyCheckedCookies)
42
+ }
43
+ return alreadyCheckedCookies
44
+ }
45
+ }
46
+
47
+ module.exports = new SetCookiesHeaderInterceptor()
@@ -1,12 +1,73 @@
1
1
  'use strict'
2
+
2
3
  const InjectionAnalyzer = require('./injection-analyzer')
4
+ const { SQL_INJECTION } = require('../vulnerabilities')
5
+ const { getRanges } = require('../taint-tracking/operations')
6
+ const { storage } = require('../../../../../datadog-core')
7
+ const { getIastContext } = require('../iast-context')
8
+ const { addVulnerability } = require('../vulnerability-reporter')
9
+
10
+ const EXCLUDED_PATHS = ['node_modules/mysql2', 'node_modules/sequelize', 'node_modules\\mysql2',
11
+ 'node_modules\\sequelize']
3
12
 
4
13
  class SqlInjectionAnalyzer extends InjectionAnalyzer {
5
14
  constructor () {
6
- super('SQL_INJECTION')
7
- this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql))
8
- this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql))
9
- this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text))
15
+ super(SQL_INJECTION)
16
+ this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
17
+ this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
18
+ this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, 'POSTGRES'))
19
+
20
+ this.addSub('datadog:sequelize:query:start', ({ sql, dialect }) => {
21
+ const parentStore = storage.getStore()
22
+ if (parentStore) {
23
+ this.analyze(sql, dialect.toUpperCase())
24
+
25
+ storage.enterWith({ ...parentStore, sqlAnalyzed: true, sequelizeParentStore: parentStore })
26
+ }
27
+ })
28
+
29
+ this.addSub('datadog:sequelize:query:finish', () => {
30
+ const store = storage.getStore()
31
+ if (store.sequelizeParentStore) {
32
+ storage.enterWith(store.sequelizeParentStore)
33
+ }
34
+ })
35
+ }
36
+
37
+ _getEvidence (value, iastContext, dialect) {
38
+ const ranges = getRanges(iastContext, value)
39
+ return { value, ranges, dialect }
40
+ }
41
+
42
+ analyze (value, dialect) {
43
+ const store = storage.getStore()
44
+
45
+ if (!(store && store.sqlAnalyzed)) {
46
+ const iastContext = getIastContext(store)
47
+ if (store && !iastContext) return
48
+ this._reportIfVulnerable(value, iastContext, dialect)
49
+ }
50
+ }
51
+
52
+ _reportIfVulnerable (value, context, dialect) {
53
+ if (this._isVulnerable(value, context) && this._checkOCE(context)) {
54
+ this._report(value, context, dialect)
55
+ return true
56
+ }
57
+ return false
58
+ }
59
+
60
+ _report (value, context, dialect) {
61
+ const evidence = this._getEvidence(value, context, dialect)
62
+ const location = this._getLocation()
63
+ if (!this._isExcluded(location)) {
64
+ const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
65
+ const vulnerability = this._createVulnerability(this._type, evidence, spanId, location)
66
+ addVulnerability(context, vulnerability)
67
+ }
68
+ }
69
+ _getExcludedPaths () {
70
+ return EXCLUDED_PATHS
10
71
  }
11
72
  }
12
73