dd-trace 4.49.0 → 4.50.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 (67) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +10 -8
  3. package/init.js +60 -47
  4. package/package.json +5 -2
  5. package/packages/datadog-core/index.js +1 -3
  6. package/packages/datadog-core/src/storage.js +21 -0
  7. package/packages/datadog-instrumentations/src/express.js +1 -1
  8. package/packages/datadog-instrumentations/src/handlebars.js +40 -0
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  10. package/packages/datadog-instrumentations/src/jest.js +6 -2
  11. package/packages/datadog-instrumentations/src/pug.js +23 -0
  12. package/packages/datadog-instrumentations/src/router.js +2 -3
  13. package/packages/datadog-plugin-amqplib/src/consumer.js +2 -1
  14. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -7
  16. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +34 -0
  17. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +10 -9
  18. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
  19. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  20. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  21. package/packages/datadog-plugin-grpc/src/server.js +2 -1
  22. package/packages/datadog-plugin-http/src/client.js +42 -1
  23. package/packages/datadog-plugin-http2/src/client.js +26 -1
  24. package/packages/datadog-plugin-jest/src/index.js +2 -1
  25. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -1
  26. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  27. package/packages/datadog-plugin-moleculer/src/server.js +2 -2
  28. package/packages/datadog-plugin-rhea/src/consumer.js +2 -1
  29. package/packages/datadog-plugin-vitest/src/index.js +2 -1
  30. package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
  31. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
  33. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  36. package/packages/dd-trace/src/appsec/index.js +6 -6
  37. package/packages/dd-trace/src/appsec/recommended.json +353 -155
  38. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -1
  39. package/packages/dd-trace/src/appsec/remote_config/index.js +0 -7
  40. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  41. package/packages/dd-trace/src/config.js +13 -4
  42. package/packages/dd-trace/src/constants.js +6 -1
  43. package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
  44. package/packages/dd-trace/src/crashtracking/index.js +15 -0
  45. package/packages/dd-trace/src/crashtracking/noop.js +8 -0
  46. package/packages/dd-trace/src/llmobs/sdk.js +1 -1
  47. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  48. package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -0
  49. package/packages/dd-trace/src/log/index.js +10 -13
  50. package/packages/dd-trace/src/log/log.js +52 -0
  51. package/packages/dd-trace/src/log/writer.js +50 -19
  52. package/packages/dd-trace/src/noop/span.js +1 -0
  53. package/packages/dd-trace/src/opentelemetry/span.js +15 -0
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +35 -22
  55. package/packages/dd-trace/src/opentracing/span.js +14 -0
  56. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  57. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  58. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
  59. package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
  60. package/packages/dd-trace/src/plugins/util/web.js +39 -11
  61. package/packages/dd-trace/src/proxy.js +5 -0
  62. package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
  63. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
  64. package/packages/dd-trace/src/telemetry/metrics.js +6 -1
  65. package/packages/dd-trace/src/util.js +16 -1
  66. package/version.js +4 -2
  67. /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
@@ -88,6 +88,7 @@ afterEach(function () {
88
88
  const testInfo = {
89
89
  testName: currentTest.fullTitle(),
90
90
  testSuite: Cypress.mocha.getRootSuite().file,
91
+ testSuiteAbsolutePath: Cypress.spec && Cypress.spec.absolute,
91
92
  state: currentTest.state,
92
93
  error: currentTest.err,
93
94
  isNew: currentTest._ddIsNew,
@@ -22,7 +22,8 @@ class GoogleCloudPubsubConsumerPlugin extends ConsumerPlugin {
22
22
  },
23
23
  metrics: {
24
24
  'pubsub.ack': 0
25
- }
25
+ },
26
+ extractedLinks: childOf?._links
26
27
  })
27
28
  if (this.config.dsmEnabled && message?.attributes) {
28
29
  const payloadSize = getMessageSize(message)
@@ -48,7 +48,8 @@ class GrpcServerPlugin extends ServerPlugin {
48
48
  },
49
49
  metrics: {
50
50
  'grpc.status.code': 0
51
- }
51
+ },
52
+ extractedLinks: childOf?._links
52
53
  })
53
54
 
54
55
  addMetadataTags(span, metadata, metadataFilter, 'request')
@@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin {
58
58
  span._spanContext._trace.record = false
59
59
  }
60
60
 
61
- if (this.config.propagationFilter(uri)) {
61
+ if (this.shouldInjectTraceHeaders(options, uri)) {
62
62
  this.tracer.inject(span, HTTP_HEADERS, options.headers)
63
63
  }
64
64
 
@@ -71,6 +71,18 @@ class HttpClientPlugin extends ClientPlugin {
71
71
  return message.currentStore
72
72
  }
73
73
 
74
+ shouldInjectTraceHeaders (options, uri) {
75
+ if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
76
+ return false
77
+ }
78
+
79
+ if (!this.config.propagationFilter(uri)) {
80
+ return false
81
+ }
82
+
83
+ return true
84
+ }
85
+
74
86
  bindAsyncStart ({ parentStore }) {
75
87
  return parentStore
76
88
  }
@@ -200,6 +212,31 @@ function getHooks (config) {
200
212
  return { request }
201
213
  }
202
214
 
215
+ function hasAmazonSignature (options) {
216
+ if (!options) {
217
+ return false
218
+ }
219
+
220
+ if (options.headers) {
221
+ const headers = Object.keys(options.headers)
222
+ .reduce((prev, next) => Object.assign(prev, {
223
+ [next.toLowerCase()]: options.headers[next]
224
+ }), {})
225
+
226
+ if (headers['x-amz-signature']) {
227
+ return true
228
+ }
229
+
230
+ if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
231
+ return true
232
+ }
233
+ }
234
+
235
+ const search = options.search || options.path
236
+
237
+ return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1
238
+ }
239
+
203
240
  function extractSessionDetails (options) {
204
241
  if (typeof options === 'string') {
205
242
  return new URL(options).host
@@ -211,4 +248,8 @@ function extractSessionDetails (options) {
211
248
  return { host, port }
212
249
  }
213
250
 
251
+ function startsWith (searchString) {
252
+ return value => String(value).startsWith(searchString)
253
+ }
254
+
214
255
  module.exports = HttpClientPlugin
@@ -62,7 +62,9 @@ class Http2ClientPlugin extends ClientPlugin {
62
62
 
63
63
  addHeaderTags(span, headers, HTTP_REQUEST_HEADERS, this.config)
64
64
 
65
- this.tracer.inject(span, HTTP_HEADERS, headers)
65
+ if (!hasAmazonSignature(headers, path)) {
66
+ this.tracer.inject(span, HTTP_HEADERS, headers)
67
+ }
66
68
 
67
69
  message.parentStore = store
68
70
  message.currentStore = { ...store, span }
@@ -132,6 +134,29 @@ function extractSessionDetails (authority, options) {
132
134
  return { protocol, port, host }
133
135
  }
134
136
 
137
+ function hasAmazonSignature (headers, path) {
138
+ if (headers) {
139
+ headers = Object.keys(headers)
140
+ .reduce((prev, next) => Object.assign(prev, {
141
+ [next.toLowerCase()]: headers[next]
142
+ }), {})
143
+
144
+ if (headers['x-amz-signature']) {
145
+ return true
146
+ }
147
+
148
+ if ([].concat(headers.authorization).some(startsWith('AWS4-HMAC-SHA256'))) {
149
+ return true
150
+ }
151
+ }
152
+
153
+ return path && path.toLowerCase().indexOf('x-amz-signature=') !== -1
154
+ }
155
+
156
+ function startsWith (searchString) {
157
+ return value => String(value).startsWith(searchString)
158
+ }
159
+
135
160
  function getStatusValidator (config) {
136
161
  if (typeof config.validateStatus === 'function') {
137
162
  return config.validateStatus
@@ -219,7 +219,8 @@ class JestPlugin extends CiPlugin {
219
219
  [COMPONENT]: this.constructor.id,
220
220
  ...this.testEnvironmentMetadata,
221
221
  ...testSuiteMetadata
222
- }
222
+ },
223
+ extractedLinks: testSessionSpanContext?._links
223
224
  })
224
225
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
225
226
  if (_ddTestCodeCoverageEnabled) {
@@ -76,7 +76,8 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
76
76
  },
77
77
  metrics: {
78
78
  'kafka.partition': partition
79
- }
79
+ },
80
+ extractedLinks: childOf?._links
80
81
  })
81
82
  if (this.config.dsmEnabled && message?.headers) {
82
83
  const payloadSize = getMessageSize(message)
@@ -85,7 +85,7 @@ class MochaPlugin extends CiPlugin {
85
85
  }
86
86
 
87
87
  const relativeCoverageFiles = [...coverageFiles, suiteFile]
88
- .map(filename => getTestSuitePath(filename, this.sourceRoot))
88
+ .map(filename => getTestSuitePath(filename, this.repositoryRoot || this.sourceRoot))
89
89
 
90
90
  const { _traceId, _spanId } = testSuiteSpan.context()
91
91
 
@@ -9,7 +9,6 @@ class MoleculerServerPlugin extends ServerPlugin {
9
9
 
10
10
  start ({ action, ctx, broker }) {
11
11
  const followsFrom = this.tracer.extract('text_map', ctx.meta)
12
-
13
12
  this.startSpan(this.operationName(), {
14
13
  childOf: followsFrom || this.activeSpan,
15
14
  service: this.config.service || this.serviceName(),
@@ -19,7 +18,8 @@ class MoleculerServerPlugin extends ServerPlugin {
19
18
  meta: {
20
19
  'resource.name': action.name,
21
20
  ...moleculerTags(broker, ctx, this.config)
22
- }
21
+ },
22
+ extractedLinks: followsFrom?._links
23
23
  })
24
24
  }
25
25
  }
@@ -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()
@@ -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
  }
@@ -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()
@@ -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',
@@ -145,10 +145,6 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
145
145
  persistent[addresses.HTTP_CLIENT_IP] = clientIp
146
146
  }
147
147
 
148
- if (apiSecuritySampler.sampleRequest(req)) {
149
- persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
150
- }
151
-
152
148
  const actions = waf.run({ persistent }, req)
153
149
 
154
150
  handleResults(actions, req, res, rootSpan, abortController)
@@ -172,6 +168,10 @@ function incomingHttpEndTranslator ({ req, res }) {
172
168
  persistent[addresses.HTTP_INCOMING_QUERY] = req.query
173
169
  }
174
170
 
171
+ if (apiSecuritySampler.sampleRequest(req, res, true)) {
172
+ persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
173
+ }
174
+
175
175
  if (Object.keys(persistent).length) {
176
176
  waf.run({ persistent }, req)
177
177
  }
@@ -228,9 +228,9 @@ function onRequestProcessParams ({ req, res, abortController, params }) {
228
228
  handleResults(results, req, res, rootSpan, abortController)
229
229
  }
230
230
 
231
- function onResponseBody ({ req, body }) {
231
+ function onResponseBody ({ req, res, body }) {
232
232
  if (!body || typeof body !== 'object') return
233
- if (!apiSecuritySampler.isSampled(req)) return
233
+ if (!apiSecuritySampler.sampleRequest(req, res)) return
234
234
 
235
235
  // we don't support blocking at this point, so no results needed
236
236
  waf.run({