dd-trace 3.21.0 → 3.22.1

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 (92) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/package.json +3 -2
  3. package/packages/datadog-esbuild/index.js +13 -1
  4. package/packages/datadog-instrumentations/src/cucumber.js +13 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -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/pg.js +14 -11
  10. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  11. package/packages/datadog-instrumentations/src/sequelize.js +51 -0
  12. package/packages/datadog-plugin-amqp10/src/consumer.js +1 -3
  13. package/packages/datadog-plugin-amqp10/src/producer.js +1 -3
  14. package/packages/datadog-plugin-amqplib/src/client.js +4 -3
  15. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  16. package/packages/datadog-plugin-amqplib/src/producer.js +1 -3
  17. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  18. package/packages/datadog-plugin-cypress/src/plugin.js +150 -30
  19. package/packages/datadog-plugin-cypress/src/support.js +6 -3
  20. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -3
  21. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -3
  22. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -3
  23. package/packages/datadog-plugin-http/src/client.js +70 -68
  24. package/packages/datadog-plugin-http2/src/client.js +50 -47
  25. package/packages/datadog-plugin-jest/src/index.js +5 -4
  26. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -4
  27. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -3
  28. package/packages/datadog-plugin-memcached/src/index.js +2 -3
  29. package/packages/datadog-plugin-mocha/src/index.js +4 -2
  30. package/packages/datadog-plugin-pg/src/index.js +1 -1
  31. package/packages/datadog-plugin-redis/src/index.js +2 -13
  32. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  33. package/packages/datadog-plugin-rhea/src/producer.js +1 -5
  34. package/packages/datadog-plugin-router/src/index.js +12 -1
  35. package/packages/dd-trace/src/appsec/blocked_templates.js +2 -101
  36. package/packages/dd-trace/src/appsec/blocking.js +60 -11
  37. package/packages/dd-trace/src/appsec/channels.js +3 -2
  38. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +7 -5
  39. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +3 -0
  40. package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +31 -0
  41. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -0
  42. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +47 -0
  43. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +30 -5
  44. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +26 -0
  45. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +35 -3
  46. package/packages/dd-trace/src/appsec/iast/path-line.js +14 -7
  47. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +19 -4
  48. package/packages/dd-trace/src/appsec/iast/telemetry/logs.js +1 -1
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +25 -2
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +49 -0
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -1
  52. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +7 -5
  53. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +0 -33
  54. package/packages/dd-trace/src/appsec/recommended.json +45 -46
  55. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +3 -1
  56. package/packages/dd-trace/src/appsec/remote_config/index.js +4 -0
  57. package/packages/dd-trace/src/appsec/rule_manager.js +49 -6
  58. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +2 -7
  59. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  60. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -6
  61. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +10 -4
  62. package/packages/dd-trace/src/config.js +36 -5
  63. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +11 -3
  64. package/packages/dd-trace/src/exporters/common/util.js +9 -0
  65. package/packages/dd-trace/src/exporters/common/writer.js +3 -2
  66. package/packages/dd-trace/src/plugin_manager.js +2 -0
  67. package/packages/dd-trace/src/plugins/cache.js +7 -0
  68. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
  69. package/packages/dd-trace/src/plugins/client.js +3 -2
  70. package/packages/dd-trace/src/plugins/consumer.js +14 -2
  71. package/packages/dd-trace/src/plugins/database.js +2 -2
  72. package/packages/dd-trace/src/plugins/inbound.js +7 -0
  73. package/packages/dd-trace/src/plugins/{outgoing.js → outbound.js} +2 -2
  74. package/packages/dd-trace/src/plugins/producer.js +19 -2
  75. package/packages/dd-trace/src/plugins/server.js +2 -2
  76. package/packages/dd-trace/src/plugins/storage.js +2 -0
  77. package/packages/dd-trace/src/plugins/tracing.js +11 -0
  78. package/packages/dd-trace/src/plugins/util/ci.js +1 -1
  79. package/packages/dd-trace/src/profiling/config.js +4 -2
  80. package/packages/dd-trace/src/service-naming/index.js +30 -0
  81. package/packages/dd-trace/src/service-naming/schemas/definition.js +24 -0
  82. package/packages/dd-trace/src/service-naming/schemas/index.js +6 -0
  83. package/packages/dd-trace/src/service-naming/schemas/util.js +5 -0
  84. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +5 -0
  85. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +64 -0
  86. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +33 -0
  87. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +5 -0
  88. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +52 -0
  89. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +21 -0
  90. package/packages/dd-trace/src/telemetry/index.js +5 -6
  91. package/packages/dd-trace/src/telemetry/send-data.js +17 -5
  92. package/packages/dd-trace/src/plugins/incoming.js +0 -7
@@ -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,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()
@@ -6,10 +6,14 @@ const { storage } = require('../../../../../datadog-core')
6
6
  const InjectionAnalyzer = require('./injection-analyzer')
7
7
  const { PATH_TRAVERSAL } = require('../vulnerabilities')
8
8
 
9
+ const ignoredOperations = ['dir.close', 'close']
10
+
9
11
  class PathTraversalAnalyzer extends InjectionAnalyzer {
10
12
  constructor () {
11
13
  super(PATH_TRAVERSAL)
12
14
  this.addSub('apm:fs:operation:start', obj => {
15
+ if (ignoredOperations.includes(obj.operation)) return
16
+
13
17
  const pathArguments = []
14
18
  if (obj.dest) {
15
19
  pathArguments.push(obj.dest)
@@ -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()
@@ -5,7 +5,10 @@ const { SQL_INJECTION } = require('../vulnerabilities')
5
5
  const { getRanges } = require('../taint-tracking/operations')
6
6
  const { storage } = require('../../../../../datadog-core')
7
7
  const { getIastContext } = require('../iast-context')
8
- const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
8
+ const { addVulnerability } = require('../vulnerability-reporter')
9
+
10
+ const EXCLUDED_PATHS = ['node_modules/mysql2', 'node_modules/sequelize', 'node_modules\\mysql2',
11
+ 'node_modules\\sequelize']
9
12
 
10
13
  class SqlInjectionAnalyzer extends InjectionAnalyzer {
11
14
  constructor () {
@@ -13,6 +16,22 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
13
16
  this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
14
17
  this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
15
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
+ })
16
35
  }
17
36
 
18
37
  _getEvidence (value, iastContext, dialect) {
@@ -22,9 +41,12 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
22
41
 
23
42
  analyze (value, dialect) {
24
43
  const store = storage.getStore()
25
- const iastContext = getIastContext(store)
26
- if (store && !iastContext) return
27
- this._reportIfVulnerable(value, iastContext, dialect)
44
+
45
+ if (!(store && store.sqlAnalyzed)) {
46
+ const iastContext = getIastContext(store)
47
+ if (store && !iastContext) return
48
+ this._reportIfVulnerable(value, iastContext, dialect)
49
+ }
28
50
  }
29
51
 
30
52
  _reportIfVulnerable (value, context, dialect) {
@@ -40,10 +62,13 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
40
62
  const location = this._getLocation()
41
63
  if (!this._isExcluded(location)) {
42
64
  const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
43
- const vulnerability = createVulnerability(this._type, evidence, spanId, location)
65
+ const vulnerability = this._createVulnerability(this._type, evidence, spanId, location)
44
66
  addVulnerability(context, vulnerability)
45
67
  }
46
68
  }
69
+ _getExcludedPaths () {
70
+ return EXCLUDED_PATHS
71
+ }
47
72
  }
48
73
 
49
74
  module.exports = new SqlInjectionAnalyzer()
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const InjectionAnalyzer = require('./injection-analyzer')
4
+ const { SSRF } = require('../vulnerabilities')
5
+
6
+ class SSRFAnalyzer extends InjectionAnalyzer {
7
+ constructor () {
8
+ super(SSRF)
9
+
10
+ this.addSub('apm:http:client:request:start', ({ args }) => {
11
+ if (typeof args.originalUrl === 'string') {
12
+ this.analyze(args.originalUrl)
13
+ } else if (args.options && args.options.host) {
14
+ this.analyze(args.options.host)
15
+ }
16
+ })
17
+
18
+ this.addSub('apm:http2:client:connect:start', ({ authority }) => {
19
+ if (authority && typeof authority === 'string') {
20
+ this.analyze(authority)
21
+ }
22
+ })
23
+ }
24
+ }
25
+
26
+ module.exports = new SSRFAnalyzer()
@@ -4,7 +4,7 @@ const Plugin = require('../../../../src/plugins/plugin')
4
4
  const { storage } = require('../../../../../datadog-core')
5
5
  const iastLog = require('../iast-log')
6
6
  const { getFirstNonDDPathAndLine } = require('../path-line')
7
- const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
7
+ const { addVulnerability } = require('../vulnerability-reporter')
8
8
  const { getIastContext } = require('../iast-context')
9
9
  const overheadController = require('../overhead-controller')
10
10
 
@@ -41,7 +41,7 @@ class Analyzer extends Plugin {
41
41
  const location = this._getLocation()
42
42
  if (!this._isExcluded(location)) {
43
43
  const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
44
- const vulnerability = createVulnerability(this._type, evidence, spanId, location)
44
+ const vulnerability = this._createVulnerability(this._type, evidence, spanId, location)
45
45
  addVulnerability(context, vulnerability)
46
46
  }
47
47
  }
@@ -59,9 +59,11 @@ class Analyzer extends Plugin {
59
59
  }
60
60
 
61
61
  _getLocation () {
62
- return getFirstNonDDPathAndLine()
62
+ return getFirstNonDDPathAndLine(this._getExcludedPaths())
63
63
  }
64
64
 
65
+ _getExcludedPaths () {}
66
+
65
67
  analyze (value) {
66
68
  const store = storage.getStore()
67
69
  const iastContext = getIastContext(store)
@@ -87,6 +89,36 @@ class Analyzer extends Plugin {
87
89
  _checkOCE (context) {
88
90
  return overheadController.hasQuota(overheadController.OPERATIONS.REPORT_VULNERABILITY, context)
89
91
  }
92
+
93
+ _createVulnerability (type, evidence, spanId, location) {
94
+ if (type && evidence) {
95
+ const _spanId = spanId || 0
96
+ return {
97
+ type,
98
+ evidence,
99
+ location: {
100
+ spanId: _spanId,
101
+ ...location
102
+ },
103
+ hash: this._createHash(this._createHashSource(type, evidence, location))
104
+ }
105
+ }
106
+ return null
107
+ }
108
+
109
+ _createHashSource (type, evidence, location) {
110
+ return location ? `${type}:${location.path}:${location.line}` : type
111
+ }
112
+
113
+ _createHash (hashSource) {
114
+ let hash = 0
115
+ let offset = 0
116
+ const size = hashSource.length
117
+ for (let i = 0; i < size; i++) {
118
+ hash = ((hash << 5) - hash) + hashSource.charCodeAt(offset++)
119
+ }
120
+ return hash
121
+ }
90
122
  }
91
123
 
92
124
  module.exports = Analyzer
@@ -37,12 +37,12 @@ function getCallSiteInfo () {
37
37
  return callsiteList
38
38
  }
39
39
 
40
- function getFirstNonDDPathAndLineFromCallsites (callsites) {
40
+ function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPaths) {
41
41
  if (callsites) {
42
42
  for (let i = 0; i < callsites.length; i++) {
43
43
  const callsite = callsites[i]
44
44
  const filepath = callsite.getFileName()
45
- if (!isExcluded(callsite) && filepath.indexOf(pathLine.ddBasePath) === -1) {
45
+ if (!isExcluded(callsite, externallyExcludedPaths) && filepath.indexOf(pathLine.ddBasePath) === -1) {
46
46
  return {
47
47
  path: path.relative(process.cwd(), filepath),
48
48
  line: callsite.getLineNumber(),
@@ -54,26 +54,33 @@ function getFirstNonDDPathAndLineFromCallsites (callsites) {
54
54
  return null
55
55
  }
56
56
 
57
- function isExcluded (callsite) {
57
+ function isExcluded (callsite, externallyExcludedPaths) {
58
58
  if (callsite.isNative()) return true
59
59
  const filename = callsite.getFileName()
60
60
  if (!filename) {
61
61
  return true
62
62
  }
63
- for (let i = 0; i < EXCLUDED_PATHS.length; i++) {
64
- if (filename.indexOf(EXCLUDED_PATHS[i]) > -1) {
63
+ let excludedPaths = EXCLUDED_PATHS
64
+ if (externallyExcludedPaths) {
65
+ excludedPaths = [...excludedPaths, ...externallyExcludedPaths]
66
+ }
67
+
68
+ for (let i = 0; i < excludedPaths.length; i++) {
69
+ if (filename.indexOf(excludedPaths[i]) > -1) {
65
70
  return true
66
71
  }
67
72
  }
73
+
68
74
  for (let i = 0; i < EXCLUDED_PATH_PREFIXES.length; i++) {
69
75
  if (filename.indexOf(EXCLUDED_PATH_PREFIXES[i]) === 0) {
70
76
  return true
71
77
  }
72
78
  }
79
+
73
80
  return false
74
81
  }
75
82
 
76
- function getFirstNonDDPathAndLine () {
77
- return getFirstNonDDPathAndLineFromCallsites(getCallSiteInfo())
83
+ function getFirstNonDDPathAndLine (externallyExcludedPaths) {
84
+ return getFirstNonDDPathAndLineFromCallsites(getCallSiteInfo(), externallyExcludedPaths)
78
85
  }
79
86
  module.exports = pathLine
@@ -12,17 +12,32 @@ class TaintTrackingPlugin extends Plugin {
12
12
  this._type = 'taint-tracking'
13
13
  this.addSub(
14
14
  'datadog:body-parser:read:finish',
15
- ({ req }) => this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body')
15
+ ({ req }) => {
16
+ const iastContext = getIastContext(storage.getStore())
17
+ if (iastContext && iastContext['body'] !== req.body) {
18
+ this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
19
+ iastContext['body'] = req.body
20
+ }
21
+ }
16
22
  )
17
23
  this.addSub(
18
24
  'datadog:qs:parse:finish',
19
25
  ({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs))
26
+
27
+ this.addSub('apm:express:middleware:next', ({ req }) => {
28
+ if (req && req.body && typeof req.body === 'object') {
29
+ const iastContext = getIastContext(storage.getStore())
30
+ if (iastContext && iastContext['body'] !== req.body) {
31
+ this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
32
+ iastContext['body'] = req.body
33
+ }
34
+ }
35
+ })
20
36
  }
21
37
 
22
- _taintTrackingHandler (type, target, property) {
23
- const iastContext = getIastContext(storage.getStore())
38
+ _taintTrackingHandler (type, target, property, iastContext = getIastContext(storage.getStore())) {
24
39
  if (!property) {
25
- target = taintObject(iastContext, target, type)
40
+ taintObject(iastContext, target, type)
26
41
  } else {
27
42
  target[property] = taintObject(iastContext, target[property], type)
28
43
  }
@@ -28,7 +28,7 @@ function sendLogs () {
28
28
  }
29
29
 
30
30
  function isLevelEnabled (level) {
31
- return isLogCollectionEnabled(config) && (level !== 'DEBUG' || config.telemetry.debug)
31
+ return isLogCollectionEnabled(config) && level !== 'DEBUG'
32
32
  }
33
33
 
34
34
  function isLogCollectionEnabled (config) {
@@ -21,10 +21,20 @@ const NUMERIC_LITERAL =
21
21
  INTEGER_NUMBER + EXPONENT
22
22
  ].join('|')
23
23
  })`
24
+ const ORACLE_ESCAPED_LITERAL = 'q\'<.*?>\'|q\'\\(.*?\\)\'|q\'\\{.*?\\}\'|q\'\\[.*?\\]\'|q\'(?<ESCAPE>.).*?\\k<ESCAPE>\''
24
25
 
25
26
  class SqlSensitiveAnalyzer {
26
27
  constructor () {
27
28
  this._patterns = {
29
+ ANSI: new RegExp( // Default
30
+ [
31
+ NUMERIC_LITERAL,
32
+ STRING_LITERAL,
33
+ LINE_COMMENT,
34
+ BLOCK_COMMENT
35
+ ].join('|'),
36
+ 'gmi'
37
+ ),
28
38
  MYSQL: new RegExp(
29
39
  [
30
40
  NUMERIC_LITERAL,
@@ -43,13 +53,26 @@ class SqlSensitiveAnalyzer {
43
53
  BLOCK_COMMENT
44
54
  ].join('|'),
45
55
  'gmi'
46
- )
56
+ ),
57
+ ORACLE: new RegExp([
58
+ NUMERIC_LITERAL,
59
+ ORACLE_ESCAPED_LITERAL,
60
+ STRING_LITERAL,
61
+ LINE_COMMENT,
62
+ BLOCK_COMMENT
63
+ ].join('|'),
64
+ 'gmi')
47
65
  }
66
+ this._patterns.SQLITE = this._patterns.MYSQL
67
+ this._patterns.MARIADB = this._patterns.MYSQL
48
68
  }
49
69
 
50
70
  extractSensitiveRanges (evidence) {
51
71
  try {
52
- const pattern = this._patterns[evidence.dialect]
72
+ let pattern = this._patterns[evidence.dialect]
73
+ if (!pattern) {
74
+ pattern = this._patterns['ANSI']
75
+ }
53
76
  pattern.lastIndex = 0
54
77
  const tokens = []
55
78
 
@@ -0,0 +1,49 @@
1
+ 'use strict'
2
+
3
+ const iastLog = require('../../../iast-log')
4
+
5
+ const AUTHORITY = '^(?:[^:]+:)?//([^@]+)@'
6
+ const QUERY_FRAGMENT = '[?#&]([^=&;]+)=([^?#&]+)'
7
+
8
+ class UrlSensitiveAnalyzer {
9
+ constructor () {
10
+ this._pattern = new RegExp([AUTHORITY, QUERY_FRAGMENT].join('|'), 'gmi')
11
+ }
12
+
13
+ extractSensitiveRanges (evidence) {
14
+ try {
15
+ const pattern = this._pattern
16
+
17
+ const ranges = []
18
+ let regexResult = pattern.exec(evidence.value)
19
+
20
+ while (regexResult != null) {
21
+ if (typeof regexResult[1] === 'string') {
22
+ // AUTHORITY regex match always ends by group + @
23
+ // it means that the match last chars - 1 are always the group
24
+ const end = regexResult.index + (regexResult[0].length - 1)
25
+ const start = end - regexResult[1].length
26
+ ranges.push({ start, end })
27
+ }
28
+
29
+ if (typeof regexResult[3] === 'string') {
30
+ // QUERY_FRAGMENT regex always ends with the group
31
+ // it means that the match last chars are always the group
32
+ const end = regexResult.index + regexResult[0].length
33
+ const start = end - regexResult[3].length
34
+ ranges.push({ start, end })
35
+ }
36
+
37
+ regexResult = pattern.exec(evidence.value)
38
+ }
39
+
40
+ return ranges
41
+ } catch (e) {
42
+ iastLog.debug(e)
43
+ }
44
+
45
+ return []
46
+ }
47
+ }
48
+
49
+ module.exports = UrlSensitiveAnalyzer
@@ -7,6 +7,7 @@ const { contains, intersects, remove } = require('./range-utils')
7
7
  const CommandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
8
8
  const LdapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
9
9
  const SqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
10
+ const UrlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
10
11
 
11
12
  // eslint-disable-next-line max-len
12
13
  const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)'
@@ -22,6 +23,7 @@ class SensitiveHandler {
22
23
  this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
23
24
  this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
24
25
  this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
26
+ this._sensitiveAnalyzers.set(vulnerabilities.SSRF, new UrlSensitiveAnalyzer())
25
27
  }
26
28
 
27
29
  isSensibleName (name) {
@@ -102,7 +104,7 @@ class SensitiveHandler {
102
104
  if (entry.start === i) {
103
105
  nextSensitive = entry
104
106
  } else {
105
- sensitive.push(entry)
107
+ sensitive.unshift(entry)
106
108
  }
107
109
  }
108
110
  }
@@ -1,8 +1,10 @@
1
1
  module.exports = {
2
- WEAK_HASH: 'WEAK_HASH',
3
- WEAK_CIPHER: 'WEAK_CIPHER',
4
- SQL_INJECTION: 'SQL_INJECTION',
5
- PATH_TRAVERSAL: 'PATH_TRAVERSAL',
6
2
  COMMAND_INJECTION: 'COMMAND_INJECTION',
7
- LDAP_INJECTION: 'LDAP_INJECTION'
3
+ INSECURE_COOKIE: 'INSECURE_COOKIE',
4
+ LDAP_INJECTION: 'LDAP_INJECTION',
5
+ PATH_TRAVERSAL: 'PATH_TRAVERSAL',
6
+ SQL_INJECTION: 'SQL_INJECTION',
7
+ SSRF: 'SSRF',
8
+ WEAK_CIPHER: 'WEAK_CIPHER',
9
+ WEAK_HASH: 'WEAK_HASH'
8
10
  }