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.
- package/LICENSE-3rdparty.csv +2 -0
- package/index.d.ts +20 -8
- package/package.json +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +290 -53
- package/packages/datadog-instrumentations/src/jest.js +3 -1
- package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
- package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
- package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
- package/packages/datadog-instrumentations/src/openai.js +4 -2
- package/packages/datadog-instrumentations/src/pg.js +59 -4
- package/packages/datadog-instrumentations/src/vitest.js +184 -9
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
- package/packages/datadog-plugin-cypress/src/support.js +4 -1
- package/packages/datadog-plugin-http/src/client.js +1 -42
- package/packages/datadog-plugin-http2/src/client.js +1 -26
- package/packages/datadog-plugin-jest/src/index.js +17 -1
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
- package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
- package/packages/datadog-plugin-mocha/src/index.js +18 -0
- package/packages/datadog-plugin-openai/src/index.js +27 -18
- package/packages/datadog-plugin-playwright/src/index.js +9 -0
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
- package/packages/datadog-plugin-vitest/src/index.js +68 -3
- package/packages/dd-trace/src/appsec/addresses.js +3 -1
- package/packages/dd-trace/src/appsec/channels.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
- package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
- package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
- package/packages/dd-trace/src/config.js +61 -10
- package/packages/dd-trace/src/constants.js +11 -1
- package/packages/dd-trace/src/data_streams_context.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +23 -0
- package/packages/dd-trace/src/datastreams/pathway.js +12 -5
- package/packages/dd-trace/src/datastreams/processor.js +35 -0
- package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
- package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
- package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
- package/packages/dd-trace/src/debugger/index.js +92 -0
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
- package/packages/dd-trace/src/exporters/common/request.js +1 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
- package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
- package/packages/dd-trace/src/payload-tagging/index.js +93 -0
- package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
- package/packages/dd-trace/src/plugin_manager.js +11 -10
- package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
- package/packages/dd-trace/src/plugins/util/env.js +5 -2
- package/packages/dd-trace/src/plugins/util/test.js +26 -2
- package/packages/dd-trace/src/profiling/config.js +5 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
- package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
- package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
- package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
- package/packages/dd-trace/src/proxy.js +10 -3
- package/packages/dd-trace/src/span_stats.js +4 -2
- 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
|
+
}
|
|
@@ -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.
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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.
|
|
102
|
-
rc.
|
|
103
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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 =
|
|
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.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
+
const persistentInputs = {}
|
|
36
|
+
|
|
30
37
|
for (const key of Object.keys(persistent)) {
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
if (Object.keys(persistentInputs).length) {
|
|
47
|
+
payload.persistent = persistentInputs
|
|
48
|
+
payloadHasData = true
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
if (ephemeral &&
|
|
48
|
-
|
|
49
|
-
|
|
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')
|
|
@@ -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
|