dd-trace 4.46.0 → 4.47.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 (89) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +20 -8
  3. package/package.json +9 -3
  4. package/packages/datadog-instrumentations/src/cucumber.js +290 -53
  5. package/packages/datadog-instrumentations/src/jest.js +3 -1
  6. package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
  7. package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
  8. package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
  9. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
  10. package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
  11. package/packages/datadog-instrumentations/src/openai.js +4 -2
  12. package/packages/datadog-instrumentations/src/pg.js +59 -4
  13. package/packages/datadog-instrumentations/src/vitest.js +184 -9
  14. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  17. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
  18. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  19. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  20. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
  21. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  22. package/packages/datadog-plugin-http/src/client.js +1 -42
  23. package/packages/datadog-plugin-http2/src/client.js +1 -26
  24. package/packages/datadog-plugin-jest/src/index.js +17 -1
  25. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
  26. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
  27. package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
  28. package/packages/datadog-plugin-mocha/src/index.js +18 -0
  29. package/packages/datadog-plugin-openai/src/index.js +27 -18
  30. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  31. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  32. package/packages/datadog-plugin-vitest/src/index.js +68 -3
  33. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  34. package/packages/dd-trace/src/appsec/channels.js +4 -2
  35. package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
  36. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
  37. package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
  38. package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
  39. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  40. package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
  41. package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
  42. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
  43. package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
  44. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
  46. package/packages/dd-trace/src/config.js +61 -10
  47. package/packages/dd-trace/src/constants.js +11 -1
  48. package/packages/dd-trace/src/data_streams_context.js +3 -0
  49. package/packages/dd-trace/src/datastreams/fnv.js +23 -0
  50. package/packages/dd-trace/src/datastreams/pathway.js +12 -5
  51. package/packages/dd-trace/src/datastreams/processor.js +35 -0
  52. package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
  53. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
  54. package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
  55. package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
  56. package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
  57. package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
  58. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
  59. package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
  60. package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
  61. package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
  62. package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
  63. package/packages/dd-trace/src/debugger/index.js +92 -0
  64. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
  65. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  66. package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
  67. package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
  68. package/packages/dd-trace/src/payload-tagging/index.js +93 -0
  69. package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
  70. package/packages/dd-trace/src/plugin_manager.js +11 -10
  71. package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
  72. package/packages/dd-trace/src/plugins/util/env.js +5 -2
  73. package/packages/dd-trace/src/plugins/util/test.js +26 -2
  74. package/packages/dd-trace/src/profiling/config.js +5 -0
  75. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  76. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
  77. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
  78. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
  79. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
  80. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
  81. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
  82. package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
  83. package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
  84. package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
  85. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  86. package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
  87. package/packages/dd-trace/src/proxy.js +10 -3
  88. package/packages/dd-trace/src/span_stats.js +4 -2
  89. package/packages/dd-trace/src/appsec/rasp.js +0 -176
@@ -0,0 +1,86 @@
1
+ 'use strict'
2
+
3
+ const { pgQueryStart, pgPoolQueryStart, wafRunFinished } = require('../channels')
4
+ const { storage } = require('../../../../datadog-core')
5
+ const addresses = require('../addresses')
6
+ const waf = require('../waf')
7
+ const { RULE_TYPES, handleResult } = require('./utils')
8
+
9
+ const DB_SYSTEM_POSTGRES = 'postgresql'
10
+ const reqQueryMap = new WeakMap() // WeakMap<Request, Set<querytext>>
11
+
12
+ let config
13
+
14
+ function enable (_config) {
15
+ config = _config
16
+
17
+ pgQueryStart.subscribe(analyzePgSqlInjection)
18
+ pgPoolQueryStart.subscribe(analyzePgSqlInjection)
19
+ wafRunFinished.subscribe(clearQuerySet)
20
+ }
21
+
22
+ function disable () {
23
+ if (pgQueryStart.hasSubscribers) pgQueryStart.unsubscribe(analyzePgSqlInjection)
24
+ if (pgPoolQueryStart.hasSubscribers) pgPoolQueryStart.unsubscribe(analyzePgSqlInjection)
25
+ if (wafRunFinished.hasSubscribers) wafRunFinished.unsubscribe(clearQuerySet)
26
+ }
27
+
28
+ function analyzePgSqlInjection (ctx) {
29
+ const query = ctx.query?.text
30
+ if (!query) return
31
+
32
+ const store = storage.getStore()
33
+ if (!store) return
34
+
35
+ const { req, res } = store
36
+
37
+ if (!req) return
38
+
39
+ let executedQueries = reqQueryMap.get(req)
40
+ if (executedQueries?.has(query)) return
41
+
42
+ // Do not waste time executing same query twice
43
+ // This also will prevent double calls in pg.Pool internal queries
44
+ if (!executedQueries) {
45
+ executedQueries = new Set()
46
+ reqQueryMap.set(req, executedQueries)
47
+ }
48
+ executedQueries.add(query)
49
+
50
+ const persistent = {
51
+ [addresses.DB_STATEMENT]: query,
52
+ [addresses.DB_SYSTEM]: DB_SYSTEM_POSTGRES
53
+ }
54
+
55
+ const result = waf.run({ persistent }, req, RULE_TYPES.SQL_INJECTION)
56
+
57
+ handleResult(result, req, res, ctx.abortController, config)
58
+ }
59
+
60
+ function hasInputAddress (payload) {
61
+ return hasAddressesObjectInputAddress(payload.ephemeral) || hasAddressesObjectInputAddress(payload.persistent)
62
+ }
63
+
64
+ function hasAddressesObjectInputAddress (addressesObject) {
65
+ return addressesObject && Object.keys(addressesObject)
66
+ .some(address => address.startsWith('server.request') || address.startsWith('graphql.server'))
67
+ }
68
+
69
+ function clearQuerySet ({ payload }) {
70
+ if (!payload) return
71
+
72
+ const store = storage.getStore()
73
+ if (!store) return
74
+
75
+ const { req } = store
76
+ if (!req) return
77
+
78
+ const executedQueries = reqQueryMap.get(req)
79
+ if (!executedQueries) return
80
+
81
+ if (hasInputAddress(payload)) {
82
+ executedQueries.clear()
83
+ }
84
+ }
85
+
86
+ module.exports = { enable, disable }
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ const { httpClientRequestStart } = require('../channels')
4
+ const { storage } = require('../../../../datadog-core')
5
+ const addresses = require('../addresses')
6
+ const waf = require('../waf')
7
+ const { RULE_TYPES, handleResult } = require('./utils')
8
+
9
+ let config
10
+
11
+ function enable (_config) {
12
+ config = _config
13
+ httpClientRequestStart.subscribe(analyzeSsrf)
14
+ }
15
+
16
+ function disable () {
17
+ if (httpClientRequestStart.hasSubscribers) httpClientRequestStart.unsubscribe(analyzeSsrf)
18
+ }
19
+
20
+ function analyzeSsrf (ctx) {
21
+ const store = storage.getStore()
22
+ const req = store?.req
23
+ const url = ctx.args.uri
24
+
25
+ if (!req || !url) return
26
+
27
+ const persistent = {
28
+ [addresses.HTTP_OUTGOING_URL]: url
29
+ }
30
+
31
+ const result = waf.run({ persistent }, req, RULE_TYPES.SSRF)
32
+
33
+ const res = store?.res
34
+ handleResult(result, req, res, ctx.abortController, config)
35
+ }
36
+
37
+ module.exports = { enable, disable }
@@ -0,0 +1,63 @@
1
+ 'use strict'
2
+
3
+ const web = require('../../plugins/util/web')
4
+ const { reportStackTrace } = require('../stack_trace')
5
+ const { getBlockingAction } = require('../blocking')
6
+ const log = require('../../log')
7
+
8
+ const abortOnUncaughtException = process.execArgv?.includes('--abort-on-uncaught-exception')
9
+
10
+ if (abortOnUncaughtException) {
11
+ log.warn('The --abort-on-uncaught-exception flag is enabled. The RASP module will not block operations.')
12
+ }
13
+
14
+ const RULE_TYPES = {
15
+ SSRF: 'ssrf',
16
+ SQL_INJECTION: 'sql_injection'
17
+ }
18
+
19
+ class DatadogRaspAbortError extends Error {
20
+ constructor (req, res, blockingAction) {
21
+ super('DatadogRaspAbortError')
22
+ this.name = 'DatadogRaspAbortError'
23
+ this.req = req
24
+ this.res = res
25
+ this.blockingAction = blockingAction
26
+ }
27
+ }
28
+
29
+ function handleResult (actions, req, res, abortController, config) {
30
+ const generateStackTraceAction = actions?.generate_stack
31
+ if (generateStackTraceAction && config.appsec.stackTrace.enabled) {
32
+ const rootSpan = web.root(req)
33
+ reportStackTrace(
34
+ rootSpan,
35
+ generateStackTraceAction.stack_id,
36
+ config.appsec.stackTrace.maxDepth,
37
+ config.appsec.stackTrace.maxStackTraces
38
+ )
39
+ }
40
+
41
+ if (!abortController || abortOnUncaughtException) return
42
+
43
+ const blockingAction = getBlockingAction(actions)
44
+ if (blockingAction) {
45
+ const rootSpan = web.root(req)
46
+ // Should block only in express
47
+ if (rootSpan?.context()._name === 'express.request') {
48
+ const abortError = new DatadogRaspAbortError(req, res, blockingAction)
49
+ abortController.abort(abortError)
50
+
51
+ // TODO Delete this when support for node 16 is removed
52
+ if (!abortController.signal.reason) {
53
+ abortController.signal.reason = abortError
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ module.exports = {
60
+ handleResult,
61
+ RULE_TYPES,
62
+ DatadogRaspAbortError
63
+ }
@@ -17,5 +17,7 @@ module.exports = {
17
17
  APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n,
18
18
  APM_TRACING_CUSTOM_TAGS: 1n << 15n,
19
19
  APM_TRACING_ENABLED: 1n << 19n,
20
+ ASM_RASP_SQLI: 1n << 21n,
21
+ ASM_RASP_SSRF: 1n << 23n,
20
22
  APM_TRACING_SAMPLE_RULES: 1n << 29n
21
23
  }
@@ -28,7 +28,7 @@ function enable (config, appsec) {
28
28
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_API_SECURITY_SAMPLE_RATE, true)
29
29
  }
30
30
 
31
- rc.on('ASM_FEATURES', (action, rcConfig) => {
31
+ rc.setProductHandler('ASM_FEATURES', (action, rcConfig) => {
32
32
  if (!rcConfig) return
33
33
 
34
34
  if (activation === Activation.ONECLICK) {
@@ -76,9 +76,15 @@ function enableWafUpdate (appsecConfig) {
76
76
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, true)
77
77
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, true)
78
78
 
79
- rc.on('ASM_DATA', noop)
80
- rc.on('ASM_DD', noop)
81
- rc.on('ASM', noop)
79
+ if (appsecConfig.rasp?.enabled) {
80
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SQLI, true)
81
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SSRF, true)
82
+ }
83
+
84
+ // TODO: delete noop handlers and kPreUpdate and replace with batched handlers
85
+ rc.setProductHandler('ASM_DATA', noop)
86
+ rc.setProductHandler('ASM_DD', noop)
87
+ rc.setProductHandler('ASM', noop)
82
88
 
83
89
  rc.on(RemoteConfigManager.kPreUpdate, RuleManager.updateWafFromRC)
84
90
  }
@@ -98,9 +104,12 @@ function disableWafUpdate () {
98
104
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, false)
99
105
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, false)
100
106
 
101
- rc.off('ASM_DATA', noop)
102
- rc.off('ASM_DD', noop)
103
- rc.off('ASM', noop)
107
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SQLI, false)
108
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SSRF, false)
109
+
110
+ rc.removeProductHandler('ASM_DATA')
111
+ rc.removeProductHandler('ASM_DD')
112
+ rc.removeProductHandler('ASM')
104
113
 
105
114
  rc.off(RemoteConfigManager.kPreUpdate, RuleManager.updateWafFromRC)
106
115
  }
@@ -15,6 +15,7 @@ const clientId = uuid()
15
15
  const DEFAULT_CAPABILITY = Buffer.alloc(1).toString('base64') // 0x00
16
16
 
17
17
  const kPreUpdate = Symbol('kPreUpdate')
18
+ const kSupportsAckCallback = Symbol('kSupportsAckCallback')
18
19
 
19
20
  // There MUST NOT exist separate instances of RC clients in a tracer making separate ClientGetConfigsRequest
20
21
  // with their own separated Client.ClientState.
@@ -32,14 +33,26 @@ class RemoteConfigManager extends EventEmitter {
32
33
  port: config.port
33
34
  }))
34
35
 
36
+ this._handlers = new Map()
37
+ const appliedConfigs = this.appliedConfigs = new Map()
38
+
35
39
  this.scheduler = new Scheduler((cb) => this.poll(cb), pollInterval)
36
40
 
37
41
  this.state = {
38
42
  client: {
39
- state: { // updated by `parseConfig()`
43
+ state: { // updated by `parseConfig()` and `poll()`
40
44
  root_version: 1,
41
45
  targets_version: 0,
42
- config_states: [],
46
+ // Use getter so `apply_*` can be updated async and still affect the content of `config_states`
47
+ get config_states () {
48
+ return Array.from(appliedConfigs.values()).map((conf) => ({
49
+ id: conf.id,
50
+ version: conf.version,
51
+ product: conf.product,
52
+ apply_state: conf.apply_state,
53
+ apply_error: conf.apply_error
54
+ }))
55
+ },
43
56
  has_error: false,
44
57
  error: '',
45
58
  backend_client_state: ''
@@ -60,8 +73,6 @@ class RemoteConfigManager extends EventEmitter {
60
73
  },
61
74
  cached_target_files: [] // updated by `parseConfig()`
62
75
  }
63
-
64
- this.appliedConfigs = new Map()
65
76
  }
66
77
 
67
78
  updateCapabilities (mask, value) {
@@ -82,32 +93,24 @@ class RemoteConfigManager extends EventEmitter {
82
93
  this.state.client.capabilities = Buffer.from(str, 'hex').toString('base64')
83
94
  }
84
95
 
85
- on (event, listener) {
86
- super.on(event, listener)
87
-
96
+ setProductHandler (product, handler) {
97
+ this._handlers.set(product, handler)
88
98
  this.updateProducts()
89
-
90
- if (this.state.client.products.length) {
99
+ if (this.state.client.products.length === 1) {
91
100
  this.scheduler.start()
92
101
  }
93
-
94
- return this
95
102
  }
96
103
 
97
- off (event, listener) {
98
- super.off(event, listener)
99
-
104
+ removeProductHandler (product) {
105
+ this._handlers.delete(product)
100
106
  this.updateProducts()
101
-
102
- if (!this.state.client.products.length) {
107
+ if (this.state.client.products.length === 0) {
103
108
  this.scheduler.stop()
104
109
  }
105
-
106
- return this
107
110
  }
108
111
 
109
112
  updateProducts () {
110
- this.state.client.products = this.eventNames().filter(e => typeof e === 'string')
113
+ this.state.client.products = Array.from(this._handlers.keys())
111
114
  }
112
115
 
113
116
  getPayload () {
@@ -228,24 +231,11 @@ class RemoteConfigManager extends EventEmitter {
228
231
  this.dispatch(toApply, 'apply')
229
232
  this.dispatch(toModify, 'modify')
230
233
 
231
- this.state.client.state.config_states = []
232
- this.state.cached_target_files = []
233
-
234
- for (const conf of this.appliedConfigs.values()) {
235
- this.state.client.state.config_states.push({
236
- id: conf.id,
237
- version: conf.version,
238
- product: conf.product,
239
- apply_state: conf.apply_state,
240
- apply_error: conf.apply_error
241
- })
242
-
243
- this.state.cached_target_files.push({
244
- path: conf.path,
245
- length: conf.length,
246
- hashes: Object.entries(conf.hashes).map((entry) => ({ algorithm: entry[0], hash: entry[1] }))
247
- })
248
- }
234
+ this.state.cached_target_files = Array.from(this.appliedConfigs.values()).map((conf) => ({
235
+ path: conf.path,
236
+ length: conf.length,
237
+ hashes: Object.entries(conf.hashes).map((entry) => ({ algorithm: entry[0], hash: entry[1] }))
238
+ }))
249
239
  }
250
240
  }
251
241
 
@@ -254,20 +244,7 @@ class RemoteConfigManager extends EventEmitter {
254
244
  // TODO: we need a way to tell if unapply configs were handled by kPreUpdate or not, because they're always
255
245
  // emitted unlike the apply and modify configs
256
246
 
257
- // in case the item was already handled by kPreUpdate
258
- if (item.apply_state === UNACKNOWLEDGED || action === 'unapply') {
259
- try {
260
- // TODO: do we want to pass old and new config ?
261
- const hadListeners = this.emit(item.product, action, item.file, item.id)
262
-
263
- if (hadListeners) {
264
- item.apply_state = ACKNOWLEDGED
265
- }
266
- } catch (err) {
267
- item.apply_state = ERROR
268
- item.apply_error = err.toString()
269
- }
270
- }
247
+ this._callHandlerFor(action, item)
271
248
 
272
249
  if (action === 'unapply') {
273
250
  this.appliedConfigs.delete(item.path)
@@ -276,6 +253,49 @@ class RemoteConfigManager extends EventEmitter {
276
253
  }
277
254
  }
278
255
  }
256
+
257
+ _callHandlerFor (action, item) {
258
+ // in case the item was already handled by kPreUpdate
259
+ if (item.apply_state !== UNACKNOWLEDGED && action !== 'unapply') return
260
+
261
+ const handler = this._handlers.get(item.product)
262
+
263
+ if (!handler) return
264
+
265
+ try {
266
+ if (supportsAckCallback(handler)) {
267
+ // If the handler accepts an `ack` callback, expect that to be called and set `apply_state` accordinly
268
+ // TODO: do we want to pass old and new config ?
269
+ handler(action, item.file, item.id, (err) => {
270
+ if (err) {
271
+ item.apply_state = ERROR
272
+ item.apply_error = err.toString()
273
+ } else if (item.apply_state !== ERROR) {
274
+ item.apply_state = ACKNOWLEDGED
275
+ }
276
+ })
277
+ } else {
278
+ // If the handler doesn't accept an `ack` callback, assume `apply_state` is `ACKNOWLEDGED`,
279
+ // unless it returns a promise, in which case we wait for the promise to be resolved or rejected.
280
+ // TODO: do we want to pass old and new config ?
281
+ const result = handler(action, item.file, item.id)
282
+ if (result instanceof Promise) {
283
+ result.then(
284
+ () => { item.apply_state = ACKNOWLEDGED },
285
+ (err) => {
286
+ item.apply_state = ERROR
287
+ item.apply_error = err.toString()
288
+ }
289
+ )
290
+ } else {
291
+ item.apply_state = ACKNOWLEDGED
292
+ }
293
+ }
294
+ } catch (err) {
295
+ item.apply_state = ERROR
296
+ item.apply_error = err.toString()
297
+ }
298
+ }
279
299
  }
280
300
 
281
301
  function fromBase64JSON (str) {
@@ -299,4 +319,22 @@ function parseConfigPath (configPath) {
299
319
  }
300
320
  }
301
321
 
322
+ function supportsAckCallback (handler) {
323
+ if (kSupportsAckCallback in handler) return handler[kSupportsAckCallback]
324
+
325
+ const numOfArgs = handler.length
326
+ let result = false
327
+
328
+ if (numOfArgs >= 4) {
329
+ result = true
330
+ } else if (numOfArgs !== 0) {
331
+ const source = handler.toString()
332
+ result = source.slice(0, source.indexOf(')')).includes('...')
333
+ }
334
+
335
+ handler[kSupportsAckCallback] = result
336
+
337
+ return result
338
+ }
339
+
302
340
  module.exports = RemoteConfigManager
@@ -4,6 +4,7 @@ const log = require('../../log')
4
4
  const Reporter = require('../reporter')
5
5
  const addresses = require('../addresses')
6
6
  const { getBlockingAction } = require('../blocking')
7
+ const { wafRunFinished } = require('../channels')
7
8
 
8
9
  // TODO: remove once ephemeral addresses are implemented
9
10
  const preventDuplicateAddresses = new Set([
@@ -11,42 +12,56 @@ const preventDuplicateAddresses = new Set([
11
12
  ])
12
13
 
13
14
  class WAFContextWrapper {
14
- constructor (ddwafContext, wafTimeout, wafVersion, rulesVersion) {
15
+ constructor (ddwafContext, wafTimeout, wafVersion, rulesVersion, knownAddresses) {
15
16
  this.ddwafContext = ddwafContext
16
17
  this.wafTimeout = wafTimeout
17
18
  this.wafVersion = wafVersion
18
19
  this.rulesVersion = rulesVersion
19
20
  this.addressesToSkip = new Set()
21
+ this.knownAddresses = knownAddresses
20
22
  }
21
23
 
22
24
  run ({ persistent, ephemeral }, raspRuleType) {
25
+ if (this.ddwafContext.disposed) {
26
+ log.warn('Calling run on a disposed context')
27
+ return
28
+ }
29
+
23
30
  const payload = {}
24
31
  let payloadHasData = false
25
- const inputs = {}
26
32
  const newAddressesToSkip = new Set(this.addressesToSkip)
27
33
 
28
34
  if (persistent !== null && typeof persistent === 'object') {
29
- // TODO: possible optimization: only send params that haven't already been sent with same value to this wafContext
35
+ const persistentInputs = {}
36
+
30
37
  for (const key of Object.keys(persistent)) {
31
- // TODO: requiredAddresses is no longer used due to processor addresses are not included in the list. Check on
32
- // future versions when the actual addresses are included in the 'loaded' section inside diagnostics.
33
- if (!this.addressesToSkip.has(key)) {
34
- inputs[key] = persistent[key]
38
+ if (!this.addressesToSkip.has(key) && this.knownAddresses.has(key)) {
39
+ persistentInputs[key] = persistent[key]
35
40
  if (preventDuplicateAddresses.has(key)) {
36
41
  newAddressesToSkip.add(key)
37
42
  }
38
43
  }
39
44
  }
40
- }
41
45
 
42
- if (Object.keys(inputs).length) {
43
- payload.persistent = inputs
44
- payloadHasData = true
46
+ if (Object.keys(persistentInputs).length) {
47
+ payload.persistent = persistentInputs
48
+ payloadHasData = true
49
+ }
45
50
  }
46
51
 
47
- if (ephemeral && Object.keys(ephemeral).length) {
48
- payload.ephemeral = ephemeral
49
- payloadHasData = true
52
+ if (ephemeral !== null && typeof ephemeral === 'object') {
53
+ const ephemeralInputs = {}
54
+
55
+ for (const key of Object.keys(ephemeral)) {
56
+ if (this.knownAddresses.has(key)) {
57
+ ephemeralInputs[key] = ephemeral[key]
58
+ }
59
+ }
60
+
61
+ if (Object.keys(ephemeralInputs).length) {
62
+ payload.ephemeral = ephemeralInputs
63
+ payloadHasData = true
64
+ }
50
65
  }
51
66
 
52
67
  if (!payloadHasData) return
@@ -80,6 +95,10 @@ class WAFContextWrapper {
80
95
 
81
96
  Reporter.reportSchemas(result.derivatives)
82
97
 
98
+ if (wafRunFinished.hasSubscribers) {
99
+ wafRunFinished.publish({ payload })
100
+ }
101
+
83
102
  return result.actions
84
103
  } catch (err) {
85
104
  log.error('Error while running the AppSec WAF')
@@ -39,7 +39,8 @@ class WAFManager {
39
39
  this.ddwaf.createContext(),
40
40
  this.wafTimeout,
41
41
  this.ddwafVersion,
42
- this.rulesVersion
42
+ this.rulesVersion,
43
+ this.ddwaf.knownAddresses
43
44
  )
44
45
  contexts.set(req, wafContext)
45
46
  }
@@ -72,6 +72,10 @@ class Writer extends BaseWriter {
72
72
  done()
73
73
  })
74
74
  }
75
+
76
+ setMetadataTags (tags) {
77
+ this._encoder.setMetadataTags(tags)
78
+ }
75
79
  }
76
80
 
77
81
  module.exports = Writer
@@ -291,6 +291,19 @@ class CiVisibilityExporter extends AgentInfoExporter {
291
291
  _getApiUrl () {
292
292
  return this._url
293
293
  }
294
+
295
+ // By the time setMetadataTags is called, the agent info request might not have finished
296
+ setMetadataTags (tags) {
297
+ if (this._writer?.setMetadataTags) {
298
+ this._writer.setMetadataTags(tags)
299
+ } else {
300
+ this._canUseCiVisProtocolPromise.then(() => {
301
+ if (this._writer?.setMetadataTags) {
302
+ this._writer.setMetadataTags(tags)
303
+ }
304
+ })
305
+ }
306
+ }
294
307
  }
295
308
 
296
309
  module.exports = CiVisibilityExporter