dd-trace 5.24.0 → 5.26.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 (138) hide show
  1. package/LICENSE-3rdparty.csv +3 -0
  2. package/index.d.ts +345 -8
  3. package/init.js +60 -47
  4. package/package.json +16 -7
  5. package/packages/datadog-code-origin/index.js +4 -4
  6. package/packages/datadog-core/index.js +1 -3
  7. package/packages/datadog-core/src/storage.js +21 -0
  8. package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
  9. package/packages/datadog-esbuild/index.js +4 -2
  10. package/packages/datadog-instrumentations/src/amqplib.js +65 -5
  11. package/packages/datadog-instrumentations/src/child_process.js +135 -27
  12. package/packages/datadog-instrumentations/src/express.js +1 -1
  13. package/packages/datadog-instrumentations/src/handlebars.js +40 -0
  14. package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -0
  15. package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
  16. package/packages/datadog-instrumentations/src/jest.js +6 -2
  17. package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
  19. package/packages/datadog-instrumentations/src/multer.js +37 -0
  20. package/packages/datadog-instrumentations/src/openai.js +2 -2
  21. package/packages/datadog-instrumentations/src/pug.js +23 -0
  22. package/packages/datadog-instrumentations/src/router.js +2 -3
  23. package/packages/datadog-instrumentations/src/url.js +84 -0
  24. package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
  25. package/packages/datadog-plugin-amqplib/src/consumer.js +6 -5
  26. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
  27. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  28. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +10 -7
  29. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +35 -0
  30. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +11 -9
  31. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
  32. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  33. package/packages/datadog-plugin-fastify/src/code_origin.js +2 -2
  34. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +10 -2
  35. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
  36. package/packages/datadog-plugin-grpc/src/client.js +3 -0
  37. package/packages/datadog-plugin-grpc/src/server.js +5 -1
  38. package/packages/datadog-plugin-http/src/client.js +42 -1
  39. package/packages/datadog-plugin-http2/src/client.js +26 -1
  40. package/packages/datadog-plugin-jest/src/index.js +2 -1
  41. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
  42. package/packages/datadog-plugin-kafkajs/src/consumer.js +10 -5
  43. package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
  44. package/packages/datadog-plugin-mocha/src/index.js +5 -2
  45. package/packages/datadog-plugin-moleculer/src/server.js +2 -2
  46. package/packages/datadog-plugin-openai/src/index.js +9 -1015
  47. package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
  48. package/packages/datadog-plugin-rhea/src/consumer.js +2 -1
  49. package/packages/datadog-plugin-vitest/src/index.js +2 -1
  50. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  51. package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
  52. package/packages/dd-trace/src/appsec/channels.js +3 -1
  53. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  54. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
  55. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
  56. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
  57. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
  58. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  59. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
  60. package/packages/dd-trace/src/appsec/index.js +9 -6
  61. package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
  62. package/packages/dd-trace/src/appsec/rasp/index.js +3 -0
  63. package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
  64. package/packages/dd-trace/src/appsec/rasp/utils.js +3 -2
  65. package/packages/dd-trace/src/appsec/recommended.json +354 -158
  66. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  67. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -7
  68. package/packages/dd-trace/src/appsec/reporter.js +6 -4
  69. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -3
  70. package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
  71. package/packages/dd-trace/src/azure_metadata.js +120 -0
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
  73. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
  74. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
  78. package/packages/dd-trace/src/config.js +88 -10
  79. package/packages/dd-trace/src/constants.js +8 -1
  80. package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
  81. package/packages/dd-trace/src/crashtracking/index.js +15 -0
  82. package/packages/dd-trace/src/crashtracking/noop.js +8 -0
  83. package/packages/dd-trace/src/datastreams/pathway.js +1 -0
  84. package/packages/dd-trace/src/debugger/devtools_client/index.js +9 -13
  85. package/packages/dd-trace/src/debugger/devtools_client/send.js +15 -1
  86. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +57 -23
  87. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +12 -2
  88. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +31 -20
  89. package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
  90. package/packages/dd-trace/src/debugger/devtools_client/state.js +11 -2
  91. package/packages/dd-trace/src/debugger/index.js +10 -3
  92. package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
  93. package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
  94. package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
  95. package/packages/dd-trace/src/llmobs/index.js +103 -0
  96. package/packages/dd-trace/src/llmobs/noop.js +82 -0
  97. package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
  98. package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
  99. package/packages/dd-trace/src/llmobs/sdk.js +377 -0
  100. package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
  101. package/packages/dd-trace/src/llmobs/storage.js +7 -0
  102. package/packages/dd-trace/src/llmobs/tagger.js +322 -0
  103. package/packages/dd-trace/src/llmobs/util.js +176 -0
  104. package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
  105. package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
  106. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
  107. package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
  108. package/packages/dd-trace/src/llmobs/writers/spans/base.js +52 -0
  109. package/packages/dd-trace/src/log/index.js +10 -13
  110. package/packages/dd-trace/src/log/log.js +52 -0
  111. package/packages/dd-trace/src/log/writer.js +50 -19
  112. package/packages/dd-trace/src/noop/proxy.js +3 -0
  113. package/packages/dd-trace/src/noop/span.js +4 -0
  114. package/packages/dd-trace/src/opentelemetry/span.js +16 -1
  115. package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
  116. package/packages/dd-trace/src/opentracing/propagation/text_map.js +106 -32
  117. package/packages/dd-trace/src/opentracing/span.js +26 -0
  118. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  119. package/packages/dd-trace/src/opentracing/tracer.js +8 -1
  120. package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
  121. package/packages/dd-trace/src/plugins/outbound.js +9 -0
  122. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  123. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
  124. package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
  125. package/packages/dd-trace/src/plugins/util/web.js +39 -11
  126. package/packages/dd-trace/src/priority_sampler.js +16 -0
  127. package/packages/dd-trace/src/profiling/config.js +3 -1
  128. package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
  129. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -1
  130. package/packages/dd-trace/src/proxy.js +13 -1
  131. package/packages/dd-trace/src/span_processor.js +5 -0
  132. package/packages/dd-trace/src/telemetry/index.js +11 -1
  133. package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
  134. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
  135. package/packages/dd-trace/src/telemetry/metrics.js +6 -1
  136. package/packages/dd-trace/src/util.js +16 -1
  137. package/version.js +4 -2
  138. /package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/{code-injection-sensitive-analyzer.js → tainted-range-based-sensitive-analyzer.js} +0 -0
@@ -28,7 +28,8 @@ class RheaConsumerPlugin extends ConsumerPlugin {
28
28
  component: 'rhea',
29
29
  'amqp.link.source.address': name,
30
30
  'amqp.link.role': 'receiver'
31
- }
31
+ },
32
+ extractedLinks: childOf?._links
32
33
  })
33
34
 
34
35
  if (
@@ -191,7 +191,8 @@ class VitestPlugin extends CiPlugin {
191
191
  [COMPONENT]: this.constructor.id,
192
192
  ...this.testEnvironmentMetadata,
193
193
  ...testSuiteMetadata
194
- }
194
+ },
195
+ extractedLinks: testSessionSpanContext?._links
195
196
  })
196
197
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
197
198
  const store = storage.getStore()
@@ -28,6 +28,8 @@ module.exports = {
28
28
  DB_STATEMENT: 'server.db.statement',
29
29
  DB_SYSTEM: 'server.db.system',
30
30
 
31
+ SHELL_COMMAND: 'server.sys.shell.cmd',
32
+
31
33
  LOGIN_SUCCESS: 'server.business_logic.users.login.success',
32
34
  LOGIN_FAILURE: 'server.business_logic.users.login.failure'
33
35
  }
@@ -1,61 +1,84 @@
1
1
  'use strict'
2
2
 
3
+ const TTLCache = require('@isaacs/ttlcache')
4
+ const web = require('../plugins/util/web')
3
5
  const log = require('../log')
6
+ const { AUTO_REJECT, USER_REJECT } = require('../../../../ext/priority')
7
+
8
+ const MAX_SIZE = 4096
4
9
 
5
10
  let enabled
6
- let requestSampling
11
+ let sampledRequests
7
12
 
8
- const sampledRequests = new WeakSet()
13
+ class NoopTTLCache {
14
+ clear () { }
15
+ set (key) { return undefined }
16
+ has (key) { return false }
17
+ }
9
18
 
10
19
  function configure ({ apiSecurity }) {
11
20
  enabled = apiSecurity.enabled
12
- setRequestSampling(apiSecurity.requestSampling)
21
+ sampledRequests = apiSecurity.sampleDelay === 0
22
+ ? new NoopTTLCache()
23
+ : new TTLCache({ max: MAX_SIZE, ttl: apiSecurity.sampleDelay * 1000 })
13
24
  }
14
25
 
15
26
  function disable () {
16
27
  enabled = false
28
+ sampledRequests?.clear()
17
29
  }
18
30
 
19
- function setRequestSampling (sampling) {
20
- requestSampling = parseRequestSampling(sampling)
21
- }
31
+ function sampleRequest (req, res, force = false) {
32
+ if (!enabled) return false
22
33
 
23
- function parseRequestSampling (requestSampling) {
24
- let parsed = parseFloat(requestSampling)
34
+ const key = computeKey(req, res)
35
+ if (!key || isSampled(key)) return false
25
36
 
26
- if (isNaN(parsed)) {
27
- log.warn(`Incorrect API Security request sampling value: ${requestSampling}`)
37
+ const rootSpan = web.root(req)
38
+ if (!rootSpan) return false
28
39
 
29
- parsed = 0
30
- } else {
31
- parsed = Math.min(1, Math.max(0, parsed))
40
+ let priority = getSpanPriority(rootSpan)
41
+ if (!priority) {
42
+ rootSpan._prioritySampler?.sample(rootSpan)
43
+ priority = getSpanPriority(rootSpan)
32
44
  }
33
45
 
34
- return parsed
35
- }
36
-
37
- function sampleRequest (req) {
38
- if (!enabled || !requestSampling) {
46
+ if (priority === AUTO_REJECT || priority === USER_REJECT) {
39
47
  return false
40
48
  }
41
49
 
42
- const shouldSample = Math.random() <= requestSampling
43
-
44
- if (shouldSample) {
45
- sampledRequests.add(req)
50
+ if (force) {
51
+ sampledRequests.set(key)
46
52
  }
47
53
 
48
- return shouldSample
54
+ return true
55
+ }
56
+
57
+ function isSampled (key) {
58
+ return sampledRequests.has(key)
59
+ }
60
+
61
+ function computeKey (req, res) {
62
+ const route = web.getContext(req)?.paths?.join('') || ''
63
+ const method = req.method
64
+ const status = res.statusCode
65
+
66
+ if (!method || !status) {
67
+ log.warn('Unsupported groupkey for API security')
68
+ return null
69
+ }
70
+ return method + route + status
49
71
  }
50
72
 
51
- function isSampled (req) {
52
- return sampledRequests.has(req)
73
+ function getSpanPriority (span) {
74
+ const spanContext = span.context?.()
75
+ return spanContext._sampling?.priority
53
76
  }
54
77
 
55
78
  module.exports = {
56
79
  configure,
57
80
  disable,
58
- setRequestSampling,
59
81
  sampleRequest,
60
- isSampled
82
+ isSampled,
83
+ computeKey
61
84
  }
@@ -6,6 +6,7 @@ const dc = require('dc-polyfill')
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
+ multerParser: dc.channel('datadog:multer:read:finish'),
9
10
  startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
10
11
  graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
11
12
  apolloChannel: dc.tracingChannel('datadog:apollo:request'),
@@ -28,5 +29,6 @@ module.exports = {
28
29
  mysql2OuterQueryStart: dc.channel('datadog:mysql2:outerquery:start'),
29
30
  wafRunFinished: dc.channel('datadog:waf:run:finish'),
30
31
  fsOperationStart: dc.channel('apm:fs:operation:start'),
31
- expressMiddlewareError: dc.channel('apm:express:middleware:error')
32
+ expressMiddlewareError: dc.channel('apm:express:middleware:error'),
33
+ childProcessExecutionTracingChannel: dc.tracingChannel('datadog:child_process:execution')
32
34
  }
@@ -15,6 +15,7 @@ module.exports = {
15
15
  PATH_TRAVERSAL_ANALYZER: require('./path-traversal-analyzer'),
16
16
  SQL_INJECTION_ANALYZER: require('./sql-injection-analyzer'),
17
17
  SSRF: require('./ssrf-analyzer'),
18
+ TEMPLATE_INJECTION_ANALYZER: require('./template-injection-analyzer'),
18
19
  UNVALIDATED_REDIRECT_ANALYZER: require('./unvalidated-redirect-analyzer'),
19
20
  WEAK_CIPHER_ANALYZER: require('./weak-cipher-analyzer'),
20
21
  WEAK_HASH_ANALYZER: require('./weak-hash-analyzer'),
@@ -6,7 +6,6 @@ const { getNodeModulesPaths } = require('../path-line')
6
6
  const { HEADER_NAME_VALUE_SEPARATOR } = require('../vulnerabilities-formatter/constants')
7
7
  const { getRanges } = require('../taint-tracking/operations')
8
8
  const {
9
- HTTP_REQUEST_COOKIE_NAME,
10
9
  HTTP_REQUEST_COOKIE_VALUE,
11
10
  HTTP_REQUEST_HEADER_VALUE
12
11
  } = require('../taint-tracking/source-types')
@@ -45,13 +44,7 @@ class HeaderInjectionAnalyzer extends InjectionAnalyzer {
45
44
  if (this.isExcludedHeaderName(lowerCasedHeaderName) || typeof value !== 'string') return
46
45
 
47
46
  const ranges = getRanges(iastContext, value)
48
- if (ranges?.length > 0) {
49
- return !(this.isCookieExclusion(lowerCasedHeaderName, ranges) ||
50
- this.isSameHeaderExclusion(lowerCasedHeaderName, ranges) ||
51
- this.isAccessControlAllowExclusion(lowerCasedHeaderName, ranges))
52
- }
53
-
54
- return false
47
+ return ranges?.length > 0 && !this.shouldIgnoreHeader(lowerCasedHeaderName, ranges)
55
48
  }
56
49
 
57
50
  _getEvidence (headerInfo, iastContext) {
@@ -75,28 +68,52 @@ class HeaderInjectionAnalyzer extends InjectionAnalyzer {
75
68
  return EXCLUDED_HEADER_NAMES.includes(name)
76
69
  }
77
70
 
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
- }
71
+ isAllRangesFromHeader (ranges, headerName) {
72
+ return ranges
73
+ .every(range =>
74
+ range.iinfo.type === HTTP_REQUEST_HEADER_VALUE && range.iinfo.parameterName?.toLowerCase() === headerName
75
+ )
76
+ }
83
77
 
84
- return false
78
+ isAllRangesFromSource (ranges, source) {
79
+ return ranges
80
+ .every(range => range.iinfo.type === source)
85
81
  }
86
82
 
83
+ /**
84
+ * Exclude access-control-allow-*: when the header starts with access-control-allow- and the
85
+ * source of the tainted range is a request header
86
+ */
87
87
  isAccessControlAllowExclusion (name, ranges) {
88
88
  if (name?.startsWith('access-control-allow-')) {
89
- return ranges
90
- .every(range => range.iinfo.type === HTTP_REQUEST_HEADER_VALUE)
89
+ return this.isAllRangesFromSource(ranges, HTTP_REQUEST_HEADER_VALUE)
91
90
  }
92
91
 
93
92
  return false
94
93
  }
95
94
 
95
+ /** Exclude when the header is reflected from the request */
96
96
  isSameHeaderExclusion (name, ranges) {
97
97
  return ranges.length === 1 && name === ranges[0].iinfo.parameterName?.toLowerCase()
98
98
  }
99
99
 
100
+ shouldIgnoreHeader (headerName, ranges) {
101
+ switch (headerName) {
102
+ case 'set-cookie':
103
+ /** Exclude set-cookie header if the source of all the tainted ranges are cookies */
104
+ return this.isAllRangesFromSource(ranges, HTTP_REQUEST_COOKIE_VALUE)
105
+ case 'pragma':
106
+ /** Ignore pragma headers when the source is the cache control header. */
107
+ return this.isAllRangesFromHeader(ranges, 'cache-control')
108
+ case 'transfer-encoding':
109
+ case 'content-encoding':
110
+ /** Ignore transfer and content encoding headers when the source is the accept encoding header. */
111
+ return this.isAllRangesFromHeader(ranges, 'accept-encoding')
112
+ }
113
+
114
+ return this.isAccessControlAllowExclusion(headerName, ranges) || this.isSameHeaderExclusion(headerName, ranges)
115
+ }
116
+
100
117
  _getExcludedPaths () {
101
118
  return EXCLUDED_PATHS
102
119
  }
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const InjectionAnalyzer = require('./injection-analyzer')
4
+ const { TEMPLATE_INJECTION } = require('../vulnerabilities')
5
+
6
+ class TemplateInjectionAnalyzer extends InjectionAnalyzer {
7
+ constructor () {
8
+ super(TEMPLATE_INJECTION)
9
+ }
10
+
11
+ onConfigure () {
12
+ this.addSub('datadog:handlebars:compile:start', ({ source }) => this.analyze(source))
13
+ this.addSub('datadog:handlebars:register-partial:start', ({ partial }) => this.analyze(partial))
14
+ this.addSub('datadog:pug:compile:start', ({ source }) => this.analyze(source))
15
+ }
16
+ }
17
+
18
+ module.exports = new TemplateInjectionAnalyzer()
@@ -23,18 +23,26 @@ class TaintTrackingPlugin extends SourceIastPlugin {
23
23
  constructor () {
24
24
  super()
25
25
  this._type = 'taint-tracking'
26
+ this._taintedURLs = new WeakMap()
26
27
  }
27
28
 
28
29
  onConfigure () {
30
+ const onRequestBody = ({ req }) => {
31
+ const iastContext = getIastContext(storage.getStore())
32
+ if (iastContext && iastContext.body !== req.body) {
33
+ this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
34
+ iastContext.body = req.body
35
+ }
36
+ }
37
+
29
38
  this.addSub(
30
39
  { channelName: 'datadog:body-parser:read:finish', tag: HTTP_REQUEST_BODY },
31
- ({ req }) => {
32
- const iastContext = getIastContext(storage.getStore())
33
- if (iastContext && iastContext.body !== req.body) {
34
- this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
35
- iastContext.body = req.body
36
- }
37
- }
40
+ onRequestBody
41
+ )
42
+
43
+ this.addSub(
44
+ { channelName: 'datadog:multer:read:finish', tag: HTTP_REQUEST_BODY },
45
+ onRequestBody
38
46
  )
39
47
 
40
48
  this.addSub(
@@ -81,6 +89,46 @@ class TaintTrackingPlugin extends SourceIastPlugin {
81
89
  }
82
90
  )
83
91
 
92
+ const urlResultTaintedProperties = ['host', 'origin', 'hostname']
93
+ this.addSub(
94
+ { channelName: 'datadog:url:parse:finish' },
95
+ ({ input, base, parsed, isURL }) => {
96
+ const iastContext = getIastContext(storage.getStore())
97
+ let ranges
98
+
99
+ if (base) {
100
+ ranges = getRanges(iastContext, base)
101
+ } else {
102
+ ranges = getRanges(iastContext, input)
103
+ }
104
+
105
+ if (ranges?.length) {
106
+ if (isURL) {
107
+ this._taintedURLs.set(parsed, ranges[0])
108
+ } else {
109
+ urlResultTaintedProperties.forEach(param => {
110
+ this._taintTrackingHandler(ranges[0].iinfo.type, parsed, param, iastContext)
111
+ })
112
+ }
113
+ }
114
+ }
115
+ )
116
+
117
+ this.addSub(
118
+ { channelName: 'datadog:url:getter:finish' },
119
+ (context) => {
120
+ if (!urlResultTaintedProperties.includes(context.property)) return
121
+
122
+ const origRange = this._taintedURLs.get(context.urlObject)
123
+ if (!origRange) return
124
+
125
+ const iastContext = getIastContext(storage.getStore())
126
+ if (!iastContext) return
127
+
128
+ context.result =
129
+ newTaintedString(iastContext, context.result, origRange.iinfo.parameterName, origRange.iinfo.type)
130
+ })
131
+
84
132
  // this is a special case to increment INSTRUMENTED_SOURCE metric for header
85
133
  this.addInstrumentedSource('http', [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME])
86
134
  }
@@ -5,13 +5,13 @@ const vulnerabilities = require('../../vulnerabilities')
5
5
 
6
6
  const { contains, intersects, remove } = require('./range-utils')
7
7
 
8
- const codeInjectionSensitiveAnalyzer = require('./sensitive-analyzers/code-injection-sensitive-analyzer')
9
8
  const commandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
10
9
  const hardcodedPasswordAnalyzer = require('./sensitive-analyzers/hardcoded-password-analyzer')
11
10
  const headerSensitiveAnalyzer = require('./sensitive-analyzers/header-sensitive-analyzer')
12
11
  const jsonSensitiveAnalyzer = require('./sensitive-analyzers/json-sensitive-analyzer')
13
12
  const ldapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
14
13
  const sqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
14
+ const taintedRangeBasedSensitiveAnalyzer = require('./sensitive-analyzers/tainted-range-based-sensitive-analyzer')
15
15
  const urlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
16
16
 
17
17
  const { DEFAULT_IAST_REDACTION_NAME_PATTERN, DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./sensitive-regex')
@@ -24,7 +24,8 @@ class SensitiveHandler {
24
24
  this._valuePattern = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
25
25
 
26
26
  this._sensitiveAnalyzers = new Map()
27
- this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, codeInjectionSensitiveAnalyzer)
27
+ this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
28
+ this._sensitiveAnalyzers.set(vulnerabilities.TEMPLATE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
28
29
  this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, commandSensitiveAnalyzer)
29
30
  this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer)
30
31
  this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, ldapSensitiveAnalyzer)
@@ -13,6 +13,7 @@ module.exports = {
13
13
  PATH_TRAVERSAL: 'PATH_TRAVERSAL',
14
14
  SQL_INJECTION: 'SQL_INJECTION',
15
15
  SSRF: 'SSRF',
16
+ TEMPLATE_INJECTION: 'TEMPLATE_INJECTION',
16
17
  UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
17
18
  WEAK_CIPHER: 'WEAK_CIPHER',
18
19
  WEAK_HASH: 'WEAK_HASH',
@@ -1,10 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const { MANUAL_KEEP } = require('../../../../../ext/tags')
4
3
  const LRU = require('lru-cache')
5
4
  const vulnerabilitiesFormatter = require('./vulnerabilities-formatter')
6
5
  const { IAST_ENABLED_TAG_KEY, IAST_JSON_TAG_KEY } = require('./tags')
7
6
  const standalone = require('../standalone')
7
+ const { SAMPLING_MECHANISM_APPSEC } = require('../../constants')
8
+ const { keepTrace } = require('../../priority_sampler')
8
9
 
9
10
  const VULNERABILITIES_KEY = 'vulnerabilities'
10
11
  const VULNERABILITY_HASHES_MAX_SIZE = 1000
@@ -56,9 +57,10 @@ function sendVulnerabilities (vulnerabilities, rootSpan) {
56
57
  const tags = {}
57
58
  // TODO: Store this outside of the span and set the tag in the exporter.
58
59
  tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
59
- tags[MANUAL_KEEP] = 'true'
60
60
  span.addTags(tags)
61
61
 
62
+ keepTrace(span, SAMPLING_MECHANISM_APPSEC)
63
+
62
64
  standalone.sample(span)
63
65
 
64
66
  if (!rootSpan) span.finish()
@@ -6,6 +6,7 @@ const remoteConfig = require('./remote_config')
6
6
  const {
7
7
  bodyParser,
8
8
  cookieParser,
9
+ multerParser,
9
10
  incomingHttpRequestStart,
10
11
  incomingHttpRequestEnd,
11
12
  passportVerify,
@@ -58,6 +59,7 @@ function enable (_config) {
58
59
  apiSecuritySampler.configure(_config.appsec)
59
60
 
60
61
  bodyParser.subscribe(onRequestBodyParsed)
62
+ multerParser.subscribe(onRequestBodyParsed)
61
63
  cookieParser.subscribe(onRequestCookieParser)
62
64
  incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
63
65
  incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
@@ -143,10 +145,6 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
143
145
  persistent[addresses.HTTP_CLIENT_IP] = clientIp
144
146
  }
145
147
 
146
- if (apiSecuritySampler.sampleRequest(req)) {
147
- persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
148
- }
149
-
150
148
  const actions = waf.run({ persistent }, req)
151
149
 
152
150
  handleResults(actions, req, res, rootSpan, abortController)
@@ -170,6 +168,10 @@ function incomingHttpEndTranslator ({ req, res }) {
170
168
  persistent[addresses.HTTP_INCOMING_QUERY] = req.query
171
169
  }
172
170
 
171
+ if (apiSecuritySampler.sampleRequest(req, res, true)) {
172
+ persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
173
+ }
174
+
173
175
  if (Object.keys(persistent).length) {
174
176
  waf.run({ persistent }, req)
175
177
  }
@@ -226,9 +228,9 @@ function onRequestProcessParams ({ req, res, abortController, params }) {
226
228
  handleResults(results, req, res, rootSpan, abortController)
227
229
  }
228
230
 
229
- function onResponseBody ({ req, body }) {
231
+ function onResponseBody ({ req, res, body }) {
230
232
  if (!body || typeof body !== 'object') return
231
- if (!apiSecuritySampler.isSampled(req)) return
233
+ if (!apiSecuritySampler.sampleRequest(req, res)) return
232
234
 
233
235
  // we don't support blocking at this point, so no results needed
234
236
  waf.run({
@@ -299,6 +301,7 @@ function disable () {
299
301
 
300
302
  // Channel#unsubscribe() is undefined for non active channels
301
303
  if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
304
+ if (multerParser.hasSubscribers) multerParser.unsubscribe(onRequestBodyParsed)
302
305
  if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
303
306
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
304
307
  if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
@@ -0,0 +1,49 @@
1
+ 'use strict'
2
+
3
+ const { childProcessExecutionTracingChannel } = require('../channels')
4
+ const { RULE_TYPES, handleResult } = require('./utils')
5
+ const { storage } = require('../../../../datadog-core')
6
+ const addresses = require('../addresses')
7
+ const waf = require('../waf')
8
+
9
+ let config
10
+
11
+ function enable (_config) {
12
+ config = _config
13
+
14
+ childProcessExecutionTracingChannel.subscribe({
15
+ start: analyzeCommandInjection
16
+ })
17
+ }
18
+
19
+ function disable () {
20
+ if (childProcessExecutionTracingChannel.start.hasSubscribers) {
21
+ childProcessExecutionTracingChannel.unsubscribe({
22
+ start: analyzeCommandInjection
23
+ })
24
+ }
25
+ }
26
+
27
+ function analyzeCommandInjection ({ file, fileArgs, shell, abortController }) {
28
+ if (!file || !shell) return
29
+
30
+ const store = storage.getStore()
31
+ const req = store?.req
32
+ if (!req) return
33
+
34
+ const commandParams = fileArgs ? [file, ...fileArgs] : file
35
+
36
+ const persistent = {
37
+ [addresses.SHELL_COMMAND]: commandParams
38
+ }
39
+
40
+ const result = waf.run({ persistent }, req, RULE_TYPES.COMMAND_INJECTION)
41
+
42
+ const res = store?.res
43
+ handleResult(result, req, res, abortController, config)
44
+ }
45
+
46
+ module.exports = {
47
+ enable,
48
+ disable
49
+ }
@@ -6,6 +6,7 @@ const { block, isBlocked } = require('../blocking')
6
6
  const ssrf = require('./ssrf')
7
7
  const sqli = require('./sql_injection')
8
8
  const lfi = require('./lfi')
9
+ const cmdi = require('./command_injection')
9
10
 
10
11
  const { DatadogRaspAbortError } = require('./utils')
11
12
 
@@ -95,6 +96,7 @@ function enable (config) {
95
96
  ssrf.enable(config)
96
97
  sqli.enable(config)
97
98
  lfi.enable(config)
99
+ cmdi.enable(config)
98
100
 
99
101
  process.on('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
100
102
  expressMiddlewareError.subscribe(blockOnDatadogRaspAbortError)
@@ -104,6 +106,7 @@ function disable () {
104
106
  ssrf.disable()
105
107
  sqli.disable()
106
108
  lfi.disable()
109
+ cmdi.disable()
107
110
 
108
111
  process.off('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
109
112
  if (expressMiddlewareError.hasSubscribers) expressMiddlewareError.unsubscribe(blockOnDatadogRaspAbortError)
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { format } = require('url')
3
4
  const { httpClientRequestStart } = require('../channels')
4
5
  const { storage } = require('../../../../datadog-core')
5
6
  const addresses = require('../addresses')
@@ -20,12 +21,12 @@ function disable () {
20
21
  function analyzeSsrf (ctx) {
21
22
  const store = storage.getStore()
22
23
  const req = store?.req
23
- const url = ctx.args.uri
24
+ const outgoingUrl = (ctx.args.options?.uri && format(ctx.args.options.uri)) ?? ctx.args.uri
24
25
 
25
- if (!req || !url) return
26
+ if (!req || !outgoingUrl) return
26
27
 
27
28
  const persistent = {
28
- [addresses.HTTP_OUTGOING_URL]: url
29
+ [addresses.HTTP_OUTGOING_URL]: outgoingUrl
29
30
  }
30
31
 
31
32
  const result = waf.run({ persistent }, req, RULE_TYPES.SSRF)
@@ -12,9 +12,10 @@ if (abortOnUncaughtException) {
12
12
  }
13
13
 
14
14
  const RULE_TYPES = {
15
- SSRF: 'ssrf',
15
+ COMMAND_INJECTION: 'command_injection',
16
+ LFI: 'lfi',
16
17
  SQL_INJECTION: 'sql_injection',
17
- LFI: 'lfi'
18
+ SSRF: 'ssrf'
18
19
  }
19
20
 
20
21
  class DatadogRaspAbortError extends Error {