dd-trace 5.35.0 → 5.37.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.
- package/LICENSE-3rdparty.csv +2 -1
- package/index.d.ts +8 -7
- package/loader-hook.mjs +0 -4
- package/package.json +15 -14
- package/packages/datadog-core/index.js +1 -1
- package/packages/datadog-core/src/storage.js +76 -31
- package/packages/datadog-instrumentations/src/cucumber.js +54 -1
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +105 -11
- package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
- package/packages/datadog-instrumentations/src/mysql2.js +3 -3
- package/packages/datadog-instrumentations/src/openai.js +8 -0
- package/packages/datadog-instrumentations/src/playwright.js +70 -22
- package/packages/datadog-instrumentations/src/vitest.js +60 -6
- package/packages/datadog-plugin-aerospike/src/index.js +1 -1
- package/packages/datadog-plugin-apollo/src/gateway/fetch.js +1 -1
- package/packages/datadog-plugin-apollo/src/gateway/index.js +1 -1
- package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/base.js +3 -3
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -4
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -2
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-couchbase/src/index.js +2 -2
- package/packages/datadog-plugin-cucumber/src/index.js +31 -14
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +72 -7
- package/packages/datadog-plugin-cypress/src/support.js +36 -29
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
- package/packages/datadog-plugin-graphql/src/utils.js +8 -1
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-hapi/src/index.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-http/src/server.js +1 -1
- package/packages/datadog-plugin-http2/src/client.js +3 -3
- package/packages/datadog-plugin-http2/src/server.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +17 -12
- package/packages/datadog-plugin-langchain/src/tracing.js +1 -1
- package/packages/datadog-plugin-mariadb/src/index.js +3 -3
- package/packages/datadog-plugin-mocha/src/index.js +35 -16
- package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
- package/packages/datadog-plugin-next/src/index.js +4 -4
- package/packages/datadog-plugin-openai/src/tracing.js +2 -3
- package/packages/datadog-plugin-playwright/src/index.js +35 -9
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -1
- package/packages/datadog-plugin-router/src/index.js +2 -2
- package/packages/datadog-plugin-selenium/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +36 -12
- package/packages/dd-trace/src/appsec/graphql.js +6 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +3 -7
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +17 -30
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +2 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -11
- package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +24 -4
- package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/index.js +4 -2
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/constants.js +6 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +8 -8
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +65 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +14 -5
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +80 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
- package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +4 -4
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +5 -5
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +5 -5
- package/packages/dd-trace/src/appsec/rasp/lfi.js +3 -3
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +4 -4
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +3 -3
- package/packages/dd-trace/src/appsec/reporter.js +3 -3
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/index.js +1 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +31 -56
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
- package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +3 -3
- package/packages/dd-trace/src/config.js +18 -3
- package/packages/dd-trace/src/data_streams_context.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
- package/packages/dd-trace/src/exporters/common/agents.js +1 -1
- package/packages/dd-trace/src/exporters/common/request.js +3 -3
- package/packages/dd-trace/src/iitm.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +12 -2
- package/packages/dd-trace/src/log/writer.js +3 -3
- package/packages/dd-trace/src/noop/span.js +1 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +5 -4
- package/packages/dd-trace/src/opentracing/span.js +3 -3
- package/packages/dd-trace/src/plugin_manager.js +3 -1
- package/packages/dd-trace/src/plugins/apollo.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +51 -4
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/plugin.js +8 -8
- package/packages/dd-trace/src/plugins/tracing.js +3 -3
- package/packages/dd-trace/src/plugins/util/git.js +3 -3
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
- package/packages/dd-trace/src/plugins/util/test.js +10 -4
- package/packages/dd-trace/src/profiling/exporters/agent.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/ritm.js +2 -1
- package/packages/dd-trace/src/scope.js +5 -5
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/tracer.js +0 -14
- package/packages/memwatch/package.json +0 -9
|
@@ -10,11 +10,14 @@ const {
|
|
|
10
10
|
getVulnerabilityCallSiteFrames,
|
|
11
11
|
replaceCallSiteFromSourceMap
|
|
12
12
|
} = require('../vulnerability-reporter')
|
|
13
|
+
const { getMarkFromVulnerabilityType } = require('../taint-tracking/secure-marks')
|
|
14
|
+
const { SUPPRESSED_VULNERABILITIES } = require('../telemetry/iast-metric')
|
|
13
15
|
|
|
14
16
|
class Analyzer extends SinkIastPlugin {
|
|
15
17
|
constructor (type) {
|
|
16
18
|
super()
|
|
17
19
|
this._type = type
|
|
20
|
+
this._secureMark = getMarkFromVulnerabilityType(type)
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
_isVulnerable (value, context) {
|
|
@@ -75,11 +78,17 @@ class Analyzer extends SinkIastPlugin {
|
|
|
75
78
|
if (locationFromSourceMap?.path) {
|
|
76
79
|
originalLocation.path = locationFromSourceMap.path
|
|
77
80
|
}
|
|
81
|
+
|
|
78
82
|
if (locationFromSourceMap?.line) {
|
|
79
83
|
originalLocation.line = locationFromSourceMap.line
|
|
80
84
|
}
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
|
|
86
|
+
if (location?.class_name) {
|
|
87
|
+
originalLocation.class = location.class_name
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (location?.function) {
|
|
91
|
+
originalLocation.method = location.function
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
return originalLocation
|
|
@@ -91,7 +100,7 @@ class Analyzer extends SinkIastPlugin {
|
|
|
91
100
|
return store && !iastContext
|
|
92
101
|
}
|
|
93
102
|
|
|
94
|
-
analyze (value, store = storage.getStore(), meta) {
|
|
103
|
+
analyze (value, store = storage('legacy').getStore(), meta) {
|
|
95
104
|
const iastContext = getIastContext(store)
|
|
96
105
|
if (this._isInvalidContext(store, iastContext)) return
|
|
97
106
|
|
|
@@ -99,7 +108,7 @@ class Analyzer extends SinkIastPlugin {
|
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
analyzeAll (...values) {
|
|
102
|
-
const store = storage.getStore()
|
|
111
|
+
const store = storage('legacy').getStore()
|
|
103
112
|
const iastContext = getIastContext(store)
|
|
104
113
|
if (this._isInvalidContext(store, iastContext)) return
|
|
105
114
|
|
|
@@ -149,6 +158,17 @@ class Analyzer extends SinkIastPlugin {
|
|
|
149
158
|
return hash
|
|
150
159
|
}
|
|
151
160
|
|
|
161
|
+
_getSuppressedMetricTag () {
|
|
162
|
+
if (!this._suppressedMetricTag) {
|
|
163
|
+
this._suppressedMetricTag = SUPPRESSED_VULNERABILITIES.formatTags(this._type)[0]
|
|
164
|
+
}
|
|
165
|
+
return this._suppressedMetricTag
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_incrementSuppressedMetric (iastContext) {
|
|
169
|
+
SUPPRESSED_VULNERABILITIES.inc(iastContext, this._getSuppressedMetricTag())
|
|
170
|
+
}
|
|
171
|
+
|
|
152
172
|
addSub (iastSubOrChannelName, handler) {
|
|
153
173
|
const iastSub = typeof iastSubOrChannelName === 'string'
|
|
154
174
|
? { channelName: iastSubOrChannelName }
|
|
@@ -48,7 +48,7 @@ class IastContextPlugin extends IastPlugin {
|
|
|
48
48
|
let isRequestAcquired = false
|
|
49
49
|
let iastContext
|
|
50
50
|
|
|
51
|
-
const store = storage.getStore()
|
|
51
|
+
const store = storage('legacy').getStore()
|
|
52
52
|
if (store) {
|
|
53
53
|
const topContext = this.getTopContext()
|
|
54
54
|
const rootSpan = this.getRootSpan(store)
|
|
@@ -70,7 +70,7 @@ class IastContextPlugin extends IastPlugin {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
finishContext () {
|
|
73
|
-
const store = storage.getStore()
|
|
73
|
+
const store = storage('legacy').getStore()
|
|
74
74
|
if (store) {
|
|
75
75
|
const topContext = this.getTopContext()
|
|
76
76
|
const iastContext = iastContextFunctions.getIastContext(store, topContext)
|
|
@@ -62,12 +62,12 @@ class IastPlugin extends Plugin {
|
|
|
62
62
|
|
|
63
63
|
_getTelemetryHandler (iastSub) {
|
|
64
64
|
return () => {
|
|
65
|
-
const iastContext = getIastContext(storage.getStore())
|
|
65
|
+
const iastContext = getIastContext(storage('legacy').getStore())
|
|
66
66
|
iastSub.increaseExecuted(iastContext)
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
_execHandlerAndIncMetric ({ handler, metric, tags, iastContext = getIastContext(storage.getStore()) }) {
|
|
70
|
+
_execHandlerAndIncMetric ({ handler, metric, tags, iastContext = getIastContext(storage('legacy').getStore()) }) {
|
|
71
71
|
try {
|
|
72
72
|
const result = handler()
|
|
73
73
|
if (iastTelemetry.isEnabled()) {
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
const { IAST_ENABLED_TAG_KEY } = require('./tags')
|
|
16
16
|
const iastTelemetry = require('./telemetry')
|
|
17
17
|
const { enable: enableFsPlugin, disable: disableFsPlugin, IAST_MODULE } = require('../rasp/fs-plugin')
|
|
18
|
+
const securityControls = require('./security-controls')
|
|
18
19
|
|
|
19
20
|
// TODO Change to `apm:http:server:request:[start|close]` when the subscription
|
|
20
21
|
// order of the callbacks can be enforce
|
|
@@ -35,6 +36,7 @@ function enable (config, _tracer) {
|
|
|
35
36
|
requestClose.subscribe(onIncomingHttpRequestEnd)
|
|
36
37
|
overheadController.configure(config.iast)
|
|
37
38
|
overheadController.startGlobalContext()
|
|
39
|
+
securityControls.configure(config.iast)
|
|
38
40
|
vulnerabilityReporter.start(config, _tracer)
|
|
39
41
|
|
|
40
42
|
isEnabled = true
|
|
@@ -57,7 +59,7 @@ function disable () {
|
|
|
57
59
|
|
|
58
60
|
function onIncomingHttpRequestStart (data) {
|
|
59
61
|
if (data?.req) {
|
|
60
|
-
const store = storage.getStore()
|
|
62
|
+
const store = storage('legacy').getStore()
|
|
61
63
|
if (store) {
|
|
62
64
|
const topContext = web.getContext(data.req)
|
|
63
65
|
if (topContext) {
|
|
@@ -82,7 +84,7 @@ function onIncomingHttpRequestStart (data) {
|
|
|
82
84
|
|
|
83
85
|
function onIncomingHttpRequestEnd (data) {
|
|
84
86
|
if (data?.req) {
|
|
85
|
-
const store = storage.getStore()
|
|
87
|
+
const store = storage('legacy').getStore()
|
|
86
88
|
const topContext = web.getContext(data.req)
|
|
87
89
|
const iastContext = iastContextFunctions.getIastContext(store, topContext)
|
|
88
90
|
if (iastContext?.rootSpan) {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const dc = require('dc-polyfill')
|
|
5
|
+
const { storage } = require('../../../../../datadog-core')
|
|
6
|
+
const shimmer = require('../../../../../datadog-shimmer')
|
|
7
|
+
const log = require('../../../log')
|
|
8
|
+
const { parse, SANITIZER_TYPE } = require('./parser')
|
|
9
|
+
const TaintTrackingOperations = require('../taint-tracking/operations')
|
|
10
|
+
const { getIastContext } = require('../iast-context')
|
|
11
|
+
const { iterateObjectStrings } = require('../utils')
|
|
12
|
+
|
|
13
|
+
// esm
|
|
14
|
+
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
15
|
+
|
|
16
|
+
// cjs
|
|
17
|
+
const moduleLoadEndChannel = dc.channel('dd-trace:moduleLoadEnd')
|
|
18
|
+
|
|
19
|
+
let controls
|
|
20
|
+
let controlsKeys
|
|
21
|
+
let hooks
|
|
22
|
+
|
|
23
|
+
function configure (iastConfig) {
|
|
24
|
+
if (!iastConfig?.securityControlsConfiguration) return
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
controls = parse(iastConfig.securityControlsConfiguration)
|
|
28
|
+
if (controls?.size > 0) {
|
|
29
|
+
hooks = new WeakSet()
|
|
30
|
+
controlsKeys = [...controls.keys()]
|
|
31
|
+
|
|
32
|
+
moduleLoadStartChannel.subscribe(onModuleLoaded)
|
|
33
|
+
moduleLoadEndChannel.subscribe(onModuleLoaded)
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
log.error('[ASM] Error configuring IAST Security Controls', e)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function onModuleLoaded (payload) {
|
|
41
|
+
if (!payload?.module || hooks?.has(payload.module)) return
|
|
42
|
+
|
|
43
|
+
const { filename, module } = payload
|
|
44
|
+
|
|
45
|
+
const controlsByFile = getControls(filename)
|
|
46
|
+
if (controlsByFile) {
|
|
47
|
+
const hook = hookModule(filename, module, controlsByFile)
|
|
48
|
+
payload.module = hook
|
|
49
|
+
hooks.add(hook)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getControls (filename) {
|
|
54
|
+
if (filename.startsWith('file://')) {
|
|
55
|
+
filename = filename.substring(7)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let key = path.isAbsolute(filename) ? path.relative(process.cwd(), filename) : filename
|
|
59
|
+
key = key.replaceAll(path.sep, path.posix.sep)
|
|
60
|
+
|
|
61
|
+
if (key.includes('node_modules')) {
|
|
62
|
+
key = controlsKeys.find(file => key.endsWith(file))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return controls.get(key)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hookModule (filename, module, controlsByFile) {
|
|
69
|
+
try {
|
|
70
|
+
controlsByFile.forEach(({ type, method, parameters, secureMarks }) => {
|
|
71
|
+
const { target, parent, methodName } = resolve(method, module)
|
|
72
|
+
if (!target) {
|
|
73
|
+
log.error('[ASM] Unable to resolve IAST security control %s:%s', filename, method)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let wrapper
|
|
78
|
+
if (type === SANITIZER_TYPE) {
|
|
79
|
+
wrapper = wrapSanitizer(target, secureMarks)
|
|
80
|
+
} else {
|
|
81
|
+
wrapper = wrapInputValidator(target, parameters, secureMarks)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (methodName) {
|
|
85
|
+
parent[methodName] = wrapper
|
|
86
|
+
} else {
|
|
87
|
+
module = wrapper
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
} catch (e) {
|
|
91
|
+
log.error('[ASM] Error initializing IAST security control for %', filename, e)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return module
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolve (path, obj, separator = '.') {
|
|
98
|
+
if (!path) {
|
|
99
|
+
// esm module with default export
|
|
100
|
+
if (obj?.default) {
|
|
101
|
+
return { target: obj.default, parent: obj, methodName: 'default' }
|
|
102
|
+
} else {
|
|
103
|
+
return { target: obj, parent: obj }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const properties = path.split(separator)
|
|
108
|
+
|
|
109
|
+
let parent
|
|
110
|
+
let methodName
|
|
111
|
+
const target = properties.reduce((prev, curr) => {
|
|
112
|
+
parent = prev
|
|
113
|
+
methodName = curr
|
|
114
|
+
return prev?.[curr]
|
|
115
|
+
}, obj)
|
|
116
|
+
|
|
117
|
+
return { target, parent, methodName }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function wrapSanitizer (target, secureMarks) {
|
|
121
|
+
return shimmer.wrapFunction(target, orig => function () {
|
|
122
|
+
const result = orig.apply(this, arguments)
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
return addSecureMarks(result, secureMarks)
|
|
126
|
+
} catch (e) {
|
|
127
|
+
log.error('[ASM] Error adding Secure mark for sanitizer', e)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function wrapInputValidator (target, parameters, secureMarks) {
|
|
135
|
+
const allParameters = !parameters?.length
|
|
136
|
+
|
|
137
|
+
return shimmer.wrapFunction(target, orig => function () {
|
|
138
|
+
try {
|
|
139
|
+
[...arguments].forEach((arg, index) => {
|
|
140
|
+
if (allParameters || parameters.includes(index)) {
|
|
141
|
+
addSecureMarks(arg, secureMarks, false)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
} catch (e) {
|
|
145
|
+
log.error('[ASM] Error adding Secure mark for input validator', e)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return orig.apply(this, arguments)
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function addSecureMarks (value, secureMarks, createNewTainted = true) {
|
|
153
|
+
if (!value) return
|
|
154
|
+
|
|
155
|
+
const store = storage('legacy').getStore()
|
|
156
|
+
const iastContext = getIastContext(store)
|
|
157
|
+
|
|
158
|
+
if (typeof value === 'string') {
|
|
159
|
+
return TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted)
|
|
160
|
+
} else {
|
|
161
|
+
iterateObjectStrings(value, (value, levelKeys, parent, lastKey) => {
|
|
162
|
+
try {
|
|
163
|
+
const securedTainted = TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted)
|
|
164
|
+
if (createNewTainted) {
|
|
165
|
+
parent[lastKey] = securedTainted
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
// if it is a readonly property, do nothing
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
return value
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function disable () {
|
|
176
|
+
if (moduleLoadStartChannel.hasSubscribers) moduleLoadStartChannel.unsubscribe(onModuleLoaded)
|
|
177
|
+
if (moduleLoadEndChannel.hasSubscribers) moduleLoadEndChannel.unsubscribe(onModuleLoaded)
|
|
178
|
+
|
|
179
|
+
controls = undefined
|
|
180
|
+
controlsKeys = undefined
|
|
181
|
+
hooks = undefined
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
configure,
|
|
186
|
+
disable
|
|
187
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../../log')
|
|
4
|
+
const { getMarkFromVulnerabilityType, CUSTOM_SECURE_MARK } = require('../taint-tracking/secure-marks')
|
|
5
|
+
|
|
6
|
+
const SECURITY_CONTROL_DELIMITER = ';'
|
|
7
|
+
const SECURITY_CONTROL_FIELD_DELIMITER = ':'
|
|
8
|
+
const SECURITY_CONTROL_ELEMENT_DELIMITER = ','
|
|
9
|
+
|
|
10
|
+
const INPUT_VALIDATOR_TYPE = 'INPUT_VALIDATOR'
|
|
11
|
+
const SANITIZER_TYPE = 'SANITIZER'
|
|
12
|
+
|
|
13
|
+
const validTypes = [INPUT_VALIDATOR_TYPE, SANITIZER_TYPE]
|
|
14
|
+
|
|
15
|
+
function parse (securityControlsConfiguration) {
|
|
16
|
+
const controls = new Map()
|
|
17
|
+
|
|
18
|
+
securityControlsConfiguration?.replace(/[\r\n\t\v\f]*/g, '')
|
|
19
|
+
.split(SECURITY_CONTROL_DELIMITER)
|
|
20
|
+
.map(parseControl)
|
|
21
|
+
.filter(control => !!control)
|
|
22
|
+
.forEach(control => {
|
|
23
|
+
if (!controls.has(control.file)) {
|
|
24
|
+
controls.set(control.file, [])
|
|
25
|
+
}
|
|
26
|
+
controls.get(control.file).push(control)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return controls
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseControl (control) {
|
|
33
|
+
if (!control) return
|
|
34
|
+
|
|
35
|
+
const fields = control.split(SECURITY_CONTROL_FIELD_DELIMITER)
|
|
36
|
+
|
|
37
|
+
if (fields.length < 3 || fields.length > 5) {
|
|
38
|
+
log.warn('[ASM] Security control configuration is invalid: %s', control)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let [type, marks, file, method, parameters] = fields
|
|
43
|
+
|
|
44
|
+
type = type.trim().toUpperCase()
|
|
45
|
+
if (!validTypes.includes(type)) {
|
|
46
|
+
log.warn('[ASM] Invalid security control type: %s', type)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let secureMarks = CUSTOM_SECURE_MARK
|
|
51
|
+
getSecureMarks(marks).forEach(mark => { secureMarks |= mark })
|
|
52
|
+
if (secureMarks === CUSTOM_SECURE_MARK) {
|
|
53
|
+
log.warn('[ASM] Invalid security control mark: %s', marks)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
file = file?.trim()
|
|
58
|
+
|
|
59
|
+
method = method?.trim()
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
parameters = getParameters(parameters)
|
|
63
|
+
} catch (e) {
|
|
64
|
+
log.warn('[ASM] Invalid non-numeric security control parameter %s', parameters)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { type, secureMarks, file, method, parameters }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getSecureMarks (marks) {
|
|
72
|
+
return marks?.split(SECURITY_CONTROL_ELEMENT_DELIMITER)
|
|
73
|
+
.map(getMarkFromVulnerabilityType)
|
|
74
|
+
.filter(mark => !!mark)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getParameters (parameters) {
|
|
78
|
+
return parameters?.split(SECURITY_CONTROL_ELEMENT_DELIMITER)
|
|
79
|
+
.map(param => {
|
|
80
|
+
const parsedParam = parseInt(param, 10)
|
|
81
|
+
|
|
82
|
+
// discard the securityControl if there is an incorrect parameter
|
|
83
|
+
if (isNaN(parsedParam)) {
|
|
84
|
+
throw new Error('Invalid non-numeric security control parameter')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return parsedParam
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
parse,
|
|
93
|
+
|
|
94
|
+
INPUT_VALIDATOR_TYPE,
|
|
95
|
+
SANITIZER_TYPE
|
|
96
|
+
}
|
|
@@ -84,10 +84,10 @@ function getRanges (iastContext, string) {
|
|
|
84
84
|
return result
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
function addSecureMark (iastContext, string, mark) {
|
|
87
|
+
function addSecureMark (iastContext, string, mark, createNewTainted = true) {
|
|
88
88
|
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
89
89
|
if (transactionId) {
|
|
90
|
-
return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark)
|
|
90
|
+
return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark, createNewTainted)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
return string
|
|
@@ -39,7 +39,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
39
39
|
|
|
40
40
|
onConfigure () {
|
|
41
41
|
const onRequestBody = ({ req }) => {
|
|
42
|
-
const iastContext = getIastContext(storage.getStore())
|
|
42
|
+
const iastContext = getIastContext(storage('legacy').getStore())
|
|
43
43
|
if (iastContext && iastContext.body !== req.body) {
|
|
44
44
|
this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
|
|
45
45
|
iastContext.body = req.body
|
|
@@ -70,7 +70,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
70
70
|
{ channelName: 'apm:express:middleware:next', tag: HTTP_REQUEST_BODY },
|
|
71
71
|
({ req }) => {
|
|
72
72
|
if (req && req.body !== null && typeof req.body === 'object') {
|
|
73
|
-
const iastContext = getIastContext(storage.getStore())
|
|
73
|
+
const iastContext = getIastContext(storage('legacy').getStore())
|
|
74
74
|
if (iastContext && iastContext.body !== req.body) {
|
|
75
75
|
this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
|
|
76
76
|
iastContext.body = req.body
|
|
@@ -115,7 +115,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
115
115
|
this.addSub(
|
|
116
116
|
{ channelName: 'apm:graphql:resolve:start', tag: HTTP_REQUEST_BODY },
|
|
117
117
|
(data) => {
|
|
118
|
-
const iastContext = getIastContext(storage.getStore())
|
|
118
|
+
const iastContext = getIastContext(storage('legacy').getStore())
|
|
119
119
|
const source = data.context?.source
|
|
120
120
|
const ranges = source && getRanges(iastContext, source)
|
|
121
121
|
if (ranges?.length) {
|
|
@@ -128,7 +128,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
128
128
|
this.addSub(
|
|
129
129
|
{ channelName: 'datadog:url:parse:finish' },
|
|
130
130
|
({ input, base, parsed, isURL }) => {
|
|
131
|
-
const iastContext = getIastContext(storage.getStore())
|
|
131
|
+
const iastContext = getIastContext(storage('legacy').getStore())
|
|
132
132
|
let ranges
|
|
133
133
|
|
|
134
134
|
if (base) {
|
|
@@ -157,7 +157,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
157
157
|
const origRange = this._taintedURLs.get(context.urlObject)
|
|
158
158
|
if (!origRange) return
|
|
159
159
|
|
|
160
|
-
const iastContext = getIastContext(storage.getStore())
|
|
160
|
+
const iastContext = getIastContext(storage('legacy').getStore())
|
|
161
161
|
if (!iastContext) return
|
|
162
162
|
|
|
163
163
|
context.result =
|
|
@@ -168,7 +168,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
168
168
|
this.addInstrumentedSource('http', [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME])
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
_taintTrackingHandler (type, target, property, iastContext = getIastContext(storage.getStore())) {
|
|
171
|
+
_taintTrackingHandler (type, target, property, iastContext = getIastContext(storage('legacy').getStore())) {
|
|
172
172
|
if (!property) {
|
|
173
173
|
taintObject(iastContext, target, type)
|
|
174
174
|
} else if (target[property]) {
|
|
@@ -177,7 +177,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
_cookiesTaintTrackingHandler (target) {
|
|
180
|
-
const iastContext = getIastContext(storage.getStore())
|
|
180
|
+
const iastContext = getIastContext(storage('legacy').getStore())
|
|
181
181
|
// Prevent tainting cookie names since it leads to taint literal string with same value.
|
|
182
182
|
taintObject(iastContext, target, HTTP_REQUEST_COOKIE_VALUE)
|
|
183
183
|
}
|
|
@@ -206,7 +206,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
206
206
|
this.taintUrl(req, iastContext)
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
_taintDatabaseResult (result, dbOrigin, iastContext = getIastContext(storage.getStore()), name) {
|
|
209
|
+
_taintDatabaseResult (result, dbOrigin, iastContext = getIastContext(storage('legacy').getStore()), name) {
|
|
210
210
|
if (!iastContext) return result
|
|
211
211
|
|
|
212
212
|
if (this._rowsToTaint === 0) return result
|
|
@@ -22,7 +22,7 @@ class KafkaConsumerIastPlugin extends SourceIastPlugin {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
taintKafkaMessage (message) {
|
|
25
|
-
const iastContext = getIastContext(storage.getStore())
|
|
25
|
+
const iastContext = getIastContext(storage('legacy').getStore())
|
|
26
26
|
|
|
27
27
|
if (iastContext && message) {
|
|
28
28
|
const { key, value } = message
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { URL } from 'url'
|
|
5
|
+
import { getName } from '../telemetry/verbosity.js'
|
|
6
|
+
import { isNotLibraryFile, isPrivateModule } from './filter.js'
|
|
7
|
+
import constants from './constants.js'
|
|
8
|
+
|
|
9
|
+
const currentUrl = new URL(import.meta.url)
|
|
10
|
+
const ddTraceDir = path.join(currentUrl.pathname, '..', '..', '..', '..', '..', '..')
|
|
11
|
+
|
|
12
|
+
let port, rewriter
|
|
13
|
+
|
|
14
|
+
export async function initialize (data) {
|
|
15
|
+
if (rewriter) return Promise.reject(new Error('ALREADY INITIALIZED'))
|
|
16
|
+
|
|
17
|
+
const { csiMethods, telemetryVerbosity, chainSourceMap } = data
|
|
18
|
+
port = data.port
|
|
19
|
+
|
|
20
|
+
const iastRewriter = await import('@datadog/native-iast-rewriter')
|
|
21
|
+
|
|
22
|
+
const { NonCacheRewriter } = iastRewriter.default
|
|
23
|
+
|
|
24
|
+
rewriter = new NonCacheRewriter({
|
|
25
|
+
csiMethods,
|
|
26
|
+
telemetryVerbosity: getName(telemetryVerbosity),
|
|
27
|
+
chainSourceMap
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function load (url, context, nextLoad) {
|
|
32
|
+
const result = await nextLoad(url, context)
|
|
33
|
+
|
|
34
|
+
if (!port) return result
|
|
35
|
+
if (!result.source) return result
|
|
36
|
+
if (url.includes(ddTraceDir) || url.includes('iitm=true')) return result
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
if (isPrivateModule(url) && isNotLibraryFile(url)) {
|
|
40
|
+
const rewritten = rewriter.rewrite(result.source.toString(), url)
|
|
41
|
+
|
|
42
|
+
if (rewritten?.content) {
|
|
43
|
+
result.source = rewritten.content || result.source
|
|
44
|
+
const data = { url, rewritten }
|
|
45
|
+
port.postMessage({ type: constants.REWRITTEN_MESSAGE, data })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (e) {
|
|
49
|
+
const newErrObject = {
|
|
50
|
+
message: e.message,
|
|
51
|
+
stack: e.stack
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = {
|
|
55
|
+
level: 'error',
|
|
56
|
+
messages: ['[ASM] Error rewriting file %s', url, newErrObject]
|
|
57
|
+
}
|
|
58
|
+
port.postMessage({
|
|
59
|
+
type: constants.LOG_MESSAGE,
|
|
60
|
+
data
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
@@ -12,10 +12,7 @@ const telemetryRewriter = {
|
|
|
12
12
|
information (content, filename, rewriter) {
|
|
13
13
|
const response = this.off(content, filename, rewriter)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
if (metrics && metrics.instrumentedPropagation) {
|
|
17
|
-
INSTRUMENTED_PROPAGATION.inc(undefined, metrics.instrumentedPropagation)
|
|
18
|
-
}
|
|
15
|
+
incrementTelemetry(response.metrics)
|
|
19
16
|
|
|
20
17
|
return response
|
|
21
18
|
}
|
|
@@ -30,4 +27,16 @@ function getRewriteFunction (rewriter) {
|
|
|
30
27
|
}
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
function incrementTelemetry (metrics) {
|
|
31
|
+
if (metrics?.instrumentedPropagation) {
|
|
32
|
+
INSTRUMENTED_PROPAGATION.inc(undefined, metrics.instrumentedPropagation)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function incrementTelemetryIfNeeded (metrics) {
|
|
37
|
+
if (iastTelemetry.verbosity !== Verbosity.OFF) {
|
|
38
|
+
incrementTelemetry(metrics)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { getRewriteFunction, incrementTelemetryIfNeeded }
|