dd-trace 5.45.0 → 5.47.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 +1 -2
- package/ci/init.js +8 -0
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/package.json +8 -9
- package/packages/datadog-instrumentations/orchestrion.yml +52 -0
- package/packages/datadog-instrumentations/src/cucumber.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +41 -1
- package/packages/datadog-instrumentations/src/jest.js +11 -2
- package/packages/datadog-instrumentations/src/langchain.js +49 -53
- package/packages/datadog-instrumentations/src/mariadb.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +11 -3
- package/packages/datadog-instrumentations/src/orchestrion-config/index.js +5 -0
- package/packages/datadog-instrumentations/src/playwright.js +333 -46
- package/packages/datadog-instrumentations/src/router.js +1 -7
- package/packages/datadog-instrumentations/src/vitest.js +11 -3
- package/packages/datadog-plugin-cucumber/src/index.js +11 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -5
- package/packages/datadog-plugin-jest/src/index.js +11 -4
- package/packages/datadog-plugin-langchain/src/index.js +18 -12
- package/packages/datadog-plugin-langchain/src/tracing.js +66 -6
- package/packages/datadog-plugin-mocha/src/index.js +17 -5
- package/packages/datadog-plugin-mongodb-core/src/index.js +24 -0
- package/packages/datadog-plugin-playwright/src/index.js +124 -10
- package/packages/datadog-plugin-vitest/src/index.js +13 -8
- package/packages/datadog-shimmer/src/shimmer.js +3 -42
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +39 -15
- package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +0 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +25 -12
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +3 -32
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +99 -57
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/utils.js +12 -7
- package/packages/dd-trace/src/appsec/recommended.json +256 -84
- package/packages/dd-trace/src/appsec/reporter.js +6 -4
- package/packages/dd-trace/src/appsec/telemetry/index.js +27 -3
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +70 -6
- package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -30
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +8 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +6 -4
- package/packages/dd-trace/src/config.js +9 -0
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +102 -22
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +263 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +69 -36
- package/packages/dd-trace/src/debugger/devtools_client/lock.js +8 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -7
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +15 -10
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +3 -3
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +69 -62
- package/packages/dd-trace/src/debugger/devtools_client/state.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +3 -0
- package/packages/dd-trace/src/encode/0.4.js +24 -17
- package/packages/dd-trace/src/exporter.js +1 -0
- package/packages/dd-trace/src/exporters/common/docker.js +37 -7
- package/packages/dd-trace/src/exporters/common/request.js +1 -4
- package/packages/dd-trace/src/format.js +58 -60
- package/packages/dd-trace/src/llmobs/plugins/base.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +62 -3
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -0
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +2 -1
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -3
- package/packages/dd-trace/src/log/index.js +2 -0
- package/packages/dd-trace/src/log/writer.js +19 -2
- package/packages/dd-trace/src/opentelemetry/span.js +4 -4
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -3
- package/packages/dd-trace/src/opentracing/span.js +10 -0
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +11 -0
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/config.js +6 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -5
- package/packages/dd-trace/src/profiling/profiler.js +4 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +12 -8
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/tagger.js +38 -26
- package/packages/dd-trace/src/util.js +1 -7
|
@@ -12,6 +12,9 @@ const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose', 'mq
|
|
|
12
12
|
const { NOSQL_MONGODB_INJECTION_MARK } = require('../taint-tracking/secure-marks')
|
|
13
13
|
const { iterateObjectStrings } = require('../utils')
|
|
14
14
|
|
|
15
|
+
const SAFE_OPERATORS = new Set(['$eq', '$gt', '$gte', '$in', '$lt', '$lte', '$ne', '$nin',
|
|
16
|
+
'$exists', '$type', '$mod', '$bitsAllClear', '$bitsAllSet', '$bitsAnyClear', '$bitsAnySet'])
|
|
17
|
+
|
|
15
18
|
class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
16
19
|
constructor () {
|
|
17
20
|
super(NOSQL_MONGODB_INJECTION)
|
|
@@ -103,7 +106,11 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
|
103
106
|
})
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
_isVulnerableRange (range) {
|
|
109
|
+
_isVulnerableRange (range, value) {
|
|
110
|
+
const rangeIsWholeValue = range.start === 0 && range.end === value?.length
|
|
111
|
+
|
|
112
|
+
if (!rangeIsWholeValue) return false
|
|
113
|
+
|
|
107
114
|
const rangeType = range?.iinfo?.type
|
|
108
115
|
return rangeType === HTTP_REQUEST_PARAMETER || rangeType === HTTP_REQUEST_BODY
|
|
109
116
|
}
|
|
@@ -119,29 +126,25 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
|
119
126
|
const rangesByKey = {}
|
|
120
127
|
const allRanges = []
|
|
121
128
|
|
|
122
|
-
|
|
129
|
+
iterateMongodbQueryStrings(value.filter, (val, nextLevelKeys) => {
|
|
123
130
|
let ranges = getRanges(iastContext, val)
|
|
124
|
-
if (ranges?.length) {
|
|
125
|
-
const filteredRanges = []
|
|
126
|
-
|
|
131
|
+
if (ranges?.length === 1) {
|
|
127
132
|
ranges = this._filterSecureRanges(ranges)
|
|
128
133
|
if (!ranges.length) {
|
|
129
134
|
this._incrementSuppressedMetric(iastContext)
|
|
135
|
+
return
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
filteredRanges.push(range)
|
|
136
|
-
}
|
|
138
|
+
const range = ranges[0]
|
|
139
|
+
if (!this._isVulnerableRange(range, val)) {
|
|
140
|
+
return
|
|
137
141
|
}
|
|
142
|
+
isVulnerable = true
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
allRanges.push(...filteredRanges)
|
|
142
|
-
}
|
|
144
|
+
rangesByKey[nextLevelKeys.join('.')] = ranges
|
|
145
|
+
allRanges.push(range)
|
|
143
146
|
}
|
|
144
|
-
}
|
|
147
|
+
})
|
|
145
148
|
|
|
146
149
|
if (isVulnerable) {
|
|
147
150
|
value.rangesToApply = rangesByKey
|
|
@@ -162,4 +165,25 @@ class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
|
162
165
|
}
|
|
163
166
|
}
|
|
164
167
|
|
|
168
|
+
function iterateMongodbQueryStrings (target, fn, levelKeys = [], depth = 10, visited = new Set()) {
|
|
169
|
+
if (target !== null && typeof target === 'object') {
|
|
170
|
+
if (visited.has(target)) return
|
|
171
|
+
|
|
172
|
+
visited.add(target)
|
|
173
|
+
|
|
174
|
+
Object.keys(target).forEach((key) => {
|
|
175
|
+
if (SAFE_OPERATORS.has(key)) return
|
|
176
|
+
|
|
177
|
+
const nextLevelKeys = [...levelKeys, key]
|
|
178
|
+
const val = target[key]
|
|
179
|
+
|
|
180
|
+
if (typeof val === 'string') {
|
|
181
|
+
fn(val, nextLevelKeys, target, key)
|
|
182
|
+
} else if (depth > 0) {
|
|
183
|
+
iterateMongodbQueryStrings(val, fn, nextLevelKeys, depth - 1, visited)
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
165
189
|
module.exports = new NosqlInjectionMongodbAnalyzer()
|
|
@@ -6,11 +6,11 @@ const isPrivateModule = function (file) {
|
|
|
6
6
|
return file && file.indexOf(NODE_MODULES) === -1
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
return file && file.indexOf('dd-trace-js')
|
|
9
|
+
const isDdTrace = function (file) {
|
|
10
|
+
return file && (file.indexOf('dd-trace-js') !== -1 || file.indexOf('dd-trace') !== -1)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
module.exports = {
|
|
14
14
|
isPrivateModule,
|
|
15
|
-
|
|
15
|
+
isDdTrace
|
|
16
16
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { enableRewriter, disableRewriter } = require('./rewriter')
|
|
4
3
|
const {
|
|
5
4
|
createTransaction,
|
|
6
5
|
removeTransaction,
|
|
@@ -16,7 +15,6 @@ const kafkaContextPlugin = require('../context/kafka-ctx-plugin')
|
|
|
16
15
|
|
|
17
16
|
module.exports = {
|
|
18
17
|
enableTaintTracking (config, telemetryVerbosity) {
|
|
19
|
-
enableRewriter(telemetryVerbosity)
|
|
20
18
|
enableTaintOperations(telemetryVerbosity)
|
|
21
19
|
taintTrackingPlugin.enable(config)
|
|
22
20
|
|
|
@@ -26,7 +24,6 @@ module.exports = {
|
|
|
26
24
|
setMaxTransactions(config.maxConcurrentRequests)
|
|
27
25
|
},
|
|
28
26
|
disableTaintTracking () {
|
|
29
|
-
disableRewriter()
|
|
30
27
|
disableTaintOperations()
|
|
31
28
|
taintTrackingPlugin.disable()
|
|
32
29
|
|
|
@@ -3,28 +3,30 @@
|
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import { URL } from 'url'
|
|
5
5
|
import { getName } from '../telemetry/verbosity.js'
|
|
6
|
-
import {
|
|
6
|
+
import { isDdTrace, isPrivateModule } from './filter.js'
|
|
7
7
|
import constants from './constants.js'
|
|
8
8
|
|
|
9
9
|
const currentUrl = new URL(import.meta.url)
|
|
10
10
|
const ddTraceDir = path.join(currentUrl.pathname, '..', '..', '..', '..', '..', '..')
|
|
11
11
|
|
|
12
|
-
let port, rewriter
|
|
12
|
+
let port, rewriter, iastEnabled
|
|
13
13
|
|
|
14
14
|
export async function initialize (data) {
|
|
15
15
|
if (rewriter) return Promise.reject(new Error('ALREADY INITIALIZED'))
|
|
16
16
|
|
|
17
|
-
const { csiMethods, telemetryVerbosity, chainSourceMap } = data
|
|
17
|
+
const { csiMethods, telemetryVerbosity, chainSourceMap, orchestrionConfig } = data
|
|
18
18
|
port = data.port
|
|
19
|
+
iastEnabled = data.iastEnabled
|
|
19
20
|
|
|
20
|
-
const iastRewriter = await import('@datadog/
|
|
21
|
+
const iastRewriter = await import('@datadog/wasm-js-rewriter')
|
|
21
22
|
|
|
22
23
|
const { NonCacheRewriter } = iastRewriter.default
|
|
23
24
|
|
|
24
25
|
rewriter = new NonCacheRewriter({
|
|
25
26
|
csiMethods,
|
|
26
27
|
telemetryVerbosity: getName(telemetryVerbosity),
|
|
27
|
-
chainSourceMap
|
|
28
|
+
chainSourceMap,
|
|
29
|
+
orchestrion: orchestrionConfig
|
|
28
30
|
})
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -35,15 +37,26 @@ export async function load (url, context, nextLoad) {
|
|
|
35
37
|
if (!result.source) return result
|
|
36
38
|
if (url.includes(ddTraceDir) || url.includes('iitm=true')) return result
|
|
37
39
|
|
|
40
|
+
let passes
|
|
38
41
|
try {
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if (isDdTrace(url)) {
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
45
|
+
if (isPrivateModule(url)) {
|
|
46
|
+
// TODO error tracking needs to be added based on config
|
|
47
|
+
passes = ['error_tracking']
|
|
48
|
+
if (iastEnabled) {
|
|
49
|
+
passes.push('iast')
|
|
46
50
|
}
|
|
51
|
+
} else {
|
|
52
|
+
passes = ['orchestrion']
|
|
53
|
+
}
|
|
54
|
+
const rewritten = rewriter.rewrite(result.source.toString(), url, passes)
|
|
55
|
+
|
|
56
|
+
if (rewritten?.content) {
|
|
57
|
+
result.source = rewritten.content || result.source
|
|
58
|
+
const data = { url, rewritten }
|
|
59
|
+
port.postMessage({ type: constants.REWRITTEN_MESSAGE, data })
|
|
47
60
|
}
|
|
48
61
|
} catch (e) {
|
|
49
62
|
const newErrObject = {
|
|
@@ -4,39 +4,10 @@ const iastTelemetry = require('../telemetry')
|
|
|
4
4
|
const { Verbosity } = require('../telemetry/verbosity')
|
|
5
5
|
const { INSTRUMENTED_PROPAGATION } = require('../telemetry/iast-metric')
|
|
6
6
|
|
|
7
|
-
const telemetryRewriter = {
|
|
8
|
-
off (content, filename, rewriter) {
|
|
9
|
-
return rewriter.rewrite(content, filename)
|
|
10
|
-
},
|
|
11
|
-
|
|
12
|
-
information (content, filename, rewriter) {
|
|
13
|
-
const response = this.off(content, filename, rewriter)
|
|
14
|
-
|
|
15
|
-
incrementTelemetry(response.metrics)
|
|
16
|
-
|
|
17
|
-
return response
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getRewriteFunction (rewriter) {
|
|
22
|
-
switch (iastTelemetry.verbosity) {
|
|
23
|
-
case Verbosity.OFF:
|
|
24
|
-
return (content, filename) => telemetryRewriter.off(content, filename, rewriter)
|
|
25
|
-
default:
|
|
26
|
-
return (content, filename) => telemetryRewriter.information(content, filename, rewriter)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function incrementTelemetry (metrics) {
|
|
31
|
-
if (metrics?.instrumentedPropagation) {
|
|
32
|
-
INSTRUMENTED_PROPAGATION.inc(undefined, metrics.instrumentedPropagation)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
7
|
function incrementTelemetryIfNeeded (metrics) {
|
|
37
|
-
if (iastTelemetry.verbosity !== Verbosity.OFF) {
|
|
38
|
-
|
|
8
|
+
if (iastTelemetry.verbosity !== Verbosity.OFF && metrics?.instrumentedPropagation) {
|
|
9
|
+
INSTRUMENTED_PROPAGATION.inc(undefined, metrics.instrumentedPropagation)
|
|
39
10
|
}
|
|
40
11
|
}
|
|
41
12
|
|
|
42
|
-
module.exports = {
|
|
13
|
+
module.exports = { incrementTelemetryIfNeeded }
|
|
@@ -4,69 +4,70 @@ const Module = require('module')
|
|
|
4
4
|
const { pathToFileURL } = require('url')
|
|
5
5
|
const { MessageChannel } = require('worker_threads')
|
|
6
6
|
const shimmer = require('../../../../../datadog-shimmer')
|
|
7
|
-
const { isPrivateModule,
|
|
7
|
+
const { isPrivateModule, isDdTrace } = require('./filter')
|
|
8
8
|
const { csiMethods } = require('./csi-methods')
|
|
9
9
|
const { getName } = require('../telemetry/verbosity')
|
|
10
|
-
const
|
|
10
|
+
const telemetry = require('../telemetry')
|
|
11
|
+
const { incrementTelemetryIfNeeded } = require('./rewriter-telemetry')
|
|
11
12
|
const dc = require('dc-polyfill')
|
|
12
13
|
const log = require('../../../log')
|
|
13
14
|
const { isMainThread } = require('worker_threads')
|
|
14
15
|
const { LOG_MESSAGE, REWRITTEN_MESSAGE } = require('./constants')
|
|
16
|
+
const orchestrionConfig = require('../../../../../datadog-instrumentations/src/orchestrion-config')
|
|
15
17
|
|
|
18
|
+
let config
|
|
16
19
|
const hardcodedSecretCh = dc.channel('datadog:secrets:result')
|
|
17
20
|
let rewriter
|
|
21
|
+
let unwrapCompile = () => {}
|
|
18
22
|
let getPrepareStackTrace, cacheRewrittenSourceMap
|
|
19
23
|
let kSymbolPrepareStackTrace
|
|
20
24
|
let esmRewriterEnabled = false
|
|
21
25
|
|
|
22
|
-
let getRewriterOriginalPathAndLineFromSourceMap = function (path, line, column) {
|
|
23
|
-
return { path, line, column }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
26
|
function isFlagPresent (flag) {
|
|
27
27
|
return process.env.NODE_OPTIONS?.includes(flag) ||
|
|
28
28
|
process.execArgv?.some(arg => arg.includes(flag))
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
let getRewriterOriginalPathAndLineFromSourceMap = function (path, line, column) {
|
|
32
|
+
return { path, line, column }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function setGetOriginalPathAndLineFromSourceMapFunction (chainSourceMap, { getOriginalPathAndLineFromSourceMap }) {
|
|
36
|
+
if (!getOriginalPathAndLineFromSourceMap) return
|
|
37
|
+
|
|
38
|
+
getRewriterOriginalPathAndLineFromSourceMap = chainSourceMap
|
|
39
|
+
? (path, line, column) => {
|
|
34
40
|
// if --enable-source-maps is present stacktraces of the rewritten files contain the original path, file and
|
|
35
41
|
// column because the sourcemap chaining is done during the rewriting process so we can skip it
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
if (isPrivateModule(path) && !isDdTrace(path)) {
|
|
43
|
+
return { path, line, column }
|
|
44
|
+
} else {
|
|
45
|
+
return getOriginalPathAndLineFromSourceMap(path, line, column)
|
|
46
|
+
}
|
|
40
47
|
}
|
|
41
|
-
|
|
42
|
-
} else {
|
|
43
|
-
return getOriginalPathAndLineFromSourceMap
|
|
44
|
-
}
|
|
48
|
+
: getOriginalPathAndLineFromSourceMap
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
function getRewriter (telemetryVerbosity) {
|
|
48
52
|
if (!rewriter) {
|
|
49
53
|
try {
|
|
50
|
-
const iastRewriter = require('@datadog/
|
|
54
|
+
const iastRewriter = require('@datadog/wasm-js-rewriter')
|
|
51
55
|
const Rewriter = iastRewriter.Rewriter
|
|
52
56
|
getPrepareStackTrace = iastRewriter.getPrepareStackTrace
|
|
53
57
|
kSymbolPrepareStackTrace = iastRewriter.kSymbolPrepareStackTrace
|
|
54
58
|
cacheRewrittenSourceMap = iastRewriter.cacheRewrittenSourceMap
|
|
55
59
|
|
|
56
60
|
const chainSourceMap = isFlagPresent('--enable-source-maps')
|
|
57
|
-
|
|
58
|
-
if (getOriginalPathAndLineFromSourceMap) {
|
|
59
|
-
getRewriterOriginalPathAndLineFromSourceMap =
|
|
60
|
-
getGetOriginalPathAndLineFromSourceMapFunction(chainSourceMap, getOriginalPathAndLineFromSourceMap)
|
|
61
|
-
}
|
|
61
|
+
setGetOriginalPathAndLineFromSourceMapFunction(chainSourceMap, iastRewriter)
|
|
62
62
|
|
|
63
63
|
rewriter = new Rewriter({
|
|
64
64
|
csiMethods,
|
|
65
65
|
telemetryVerbosity: getName(telemetryVerbosity),
|
|
66
|
-
chainSourceMap
|
|
66
|
+
chainSourceMap,
|
|
67
|
+
orchestrion: orchestrionConfig
|
|
67
68
|
})
|
|
68
69
|
} catch (e) {
|
|
69
|
-
log.error('
|
|
70
|
+
log.error('Unable to initialize Rewriter', e)
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
return rewriter
|
|
@@ -74,6 +75,9 @@ function getRewriter (telemetryVerbosity) {
|
|
|
74
75
|
|
|
75
76
|
let originalPrepareStackTrace
|
|
76
77
|
function getPrepareStackTraceAccessor () {
|
|
78
|
+
if (!getPrepareStackTrace) {
|
|
79
|
+
getPrepareStackTrace = require('@datadog/wasm-js-rewriter/js/stack-trace').getPrepareStackTrace
|
|
80
|
+
}
|
|
77
81
|
originalPrepareStackTrace = Error.prepareStackTrace
|
|
78
82
|
let actual = getPrepareStackTrace(originalPrepareStackTrace)
|
|
79
83
|
return {
|
|
@@ -89,25 +93,42 @@ function getPrepareStackTraceAccessor () {
|
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
function getCompileMethodFn (compileMethod) {
|
|
92
|
-
|
|
93
|
-
return function (content, filename) {
|
|
96
|
+
let delegate = function (content, filename) {
|
|
94
97
|
try {
|
|
95
|
-
if (
|
|
96
|
-
|
|
98
|
+
if (isDdTrace(filename)) {
|
|
99
|
+
return compileMethod.apply(this, [content, filename])
|
|
100
|
+
}
|
|
101
|
+
if (!isPrivateModule(filename) || !config.iast?.enabled) {
|
|
102
|
+
return compileMethod.apply(this, [content, filename])
|
|
103
|
+
}
|
|
104
|
+
// TODO when we have CJS support for orchestrion and taint-tracking, add
|
|
105
|
+
// them here as appropriate
|
|
106
|
+
const rewritten = rewriter.rewrite(content, filename, ['iast'])
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
hardcodedSecretCh.publish(rewritten.literalsResult)
|
|
100
|
-
}
|
|
108
|
+
incrementTelemetryIfNeeded(rewritten.metrics)
|
|
101
109
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
if (rewritten?.literalsResult && hardcodedSecretCh.hasSubscribers) {
|
|
111
|
+
hardcodedSecretCh.publish(rewritten.literalsResult)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (rewritten?.content) {
|
|
115
|
+
return compileMethod.apply(this, [rewritten.content, filename])
|
|
105
116
|
}
|
|
106
117
|
} catch (e) {
|
|
107
|
-
log.error('
|
|
118
|
+
log.error('Error rewriting file %s', filename, e)
|
|
108
119
|
}
|
|
109
120
|
return compileMethod.apply(this, [content, filename])
|
|
110
121
|
}
|
|
122
|
+
|
|
123
|
+
const shim = function () {
|
|
124
|
+
return delegate.apply(this, arguments)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
unwrapCompile = function () {
|
|
128
|
+
delegate = compileMethod
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return shim
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
function esmRewritePostProcess (rewritten, filename) {
|
|
@@ -128,20 +149,31 @@ function esmRewritePostProcess (rewritten, filename) {
|
|
|
128
149
|
}
|
|
129
150
|
}
|
|
130
151
|
|
|
152
|
+
let shimmedPrepareStackTrace = false
|
|
153
|
+
function shimPrepareStackTrace () {
|
|
154
|
+
if (shimmedPrepareStackTrace) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
const pstDescriptor = Object.getOwnPropertyDescriptor(global.Error, 'prepareStackTrace')
|
|
158
|
+
if (!pstDescriptor || pstDescriptor.configurable) {
|
|
159
|
+
Object.defineProperty(global.Error, 'prepareStackTrace', getPrepareStackTraceAccessor())
|
|
160
|
+
}
|
|
161
|
+
shimmedPrepareStackTrace = true
|
|
162
|
+
}
|
|
163
|
+
|
|
131
164
|
function enableRewriter (telemetryVerbosity) {
|
|
132
165
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
166
|
+
if (config.iast?.enabled) {
|
|
167
|
+
const rewriter = getRewriter(telemetryVerbosity)
|
|
168
|
+
if (rewriter) {
|
|
169
|
+
shimPrepareStackTrace()
|
|
170
|
+
shimmer.wrap(Module.prototype, '_compile', compileMethod => getCompileMethodFn(compileMethod))
|
|
138
171
|
}
|
|
139
|
-
shimmer.wrap(Module.prototype, '_compile', compileMethod => getCompileMethodFn(compileMethod))
|
|
140
172
|
}
|
|
141
173
|
|
|
142
174
|
enableEsmRewriter(telemetryVerbosity)
|
|
143
175
|
} catch (e) {
|
|
144
|
-
log.error('
|
|
176
|
+
log.error('Error enabling Rewriter', e)
|
|
145
177
|
}
|
|
146
178
|
}
|
|
147
179
|
|
|
@@ -155,6 +187,8 @@ function isEsmConfigured () {
|
|
|
155
187
|
|
|
156
188
|
function enableEsmRewriter (telemetryVerbosity) {
|
|
157
189
|
if (isMainThread && Module.register && !esmRewriterEnabled && isEsmConfigured()) {
|
|
190
|
+
shimPrepareStackTrace()
|
|
191
|
+
|
|
158
192
|
esmRewriterEnabled = true
|
|
159
193
|
|
|
160
194
|
const { port1, port2 } = new MessageChannel()
|
|
@@ -175,30 +209,31 @@ function enableEsmRewriter (telemetryVerbosity) {
|
|
|
175
209
|
port1.unref()
|
|
176
210
|
port2.unref()
|
|
177
211
|
|
|
178
|
-
const chainSourceMap = isFlagPresent('--enable-source-maps')
|
|
179
|
-
const data = {
|
|
180
|
-
port: port2,
|
|
181
|
-
csiMethods,
|
|
182
|
-
telemetryVerbosity,
|
|
183
|
-
chainSourceMap
|
|
184
|
-
}
|
|
185
|
-
|
|
186
212
|
try {
|
|
187
213
|
Module.register('./rewriter-esm.mjs', {
|
|
188
214
|
parentURL: pathToFileURL(__filename),
|
|
189
215
|
transferList: [port2],
|
|
190
|
-
data
|
|
216
|
+
data: {
|
|
217
|
+
port: port2,
|
|
218
|
+
csiMethods,
|
|
219
|
+
telemetryVerbosity,
|
|
220
|
+
chainSourceMap: isFlagPresent('--enable-source-maps'),
|
|
221
|
+
orchestrionConfig,
|
|
222
|
+
iastEnabled: config?.iast?.enabled
|
|
223
|
+
}
|
|
191
224
|
})
|
|
192
225
|
} catch (e) {
|
|
193
|
-
log.error('
|
|
226
|
+
log.error('Error enabling ESM Rewriter', e)
|
|
194
227
|
port1.close()
|
|
195
228
|
port2.close()
|
|
196
229
|
}
|
|
230
|
+
|
|
231
|
+
cacheRewrittenSourceMap = require('@datadog/wasm-js-rewriter/js/source-map').cacheRewrittenSourceMap
|
|
197
232
|
}
|
|
198
233
|
}
|
|
199
234
|
|
|
200
|
-
function
|
|
201
|
-
|
|
235
|
+
function disable () {
|
|
236
|
+
unwrapCompile()
|
|
202
237
|
|
|
203
238
|
if (!Error.prepareStackTrace?.[kSymbolPrepareStackTrace]) return
|
|
204
239
|
|
|
@@ -206,8 +241,10 @@ function disableRewriter () {
|
|
|
206
241
|
delete Error.prepareStackTrace
|
|
207
242
|
|
|
208
243
|
Error.prepareStackTrace = originalPrepareStackTrace
|
|
244
|
+
|
|
245
|
+
shimmedPrepareStackTrace = false
|
|
209
246
|
} catch (e) {
|
|
210
|
-
log.warn('
|
|
247
|
+
log.warn('Error disabling Rewriter', e)
|
|
211
248
|
}
|
|
212
249
|
}
|
|
213
250
|
|
|
@@ -215,6 +252,11 @@ function getOriginalPathAndLineFromSourceMap ({ path, line, column }) {
|
|
|
215
252
|
return getRewriterOriginalPathAndLineFromSourceMap(path, line, column)
|
|
216
253
|
}
|
|
217
254
|
|
|
255
|
+
function enable (configArg) {
|
|
256
|
+
config = configArg
|
|
257
|
+
enableRewriter(telemetry.verbosity || 'OFF')
|
|
258
|
+
}
|
|
259
|
+
|
|
218
260
|
module.exports = {
|
|
219
|
-
|
|
261
|
+
enable, disable, getOriginalPathAndLineFromSourceMap
|
|
220
262
|
}
|
|
@@ -47,7 +47,7 @@ function analyzeCommandInjection ({ file, fileArgs, shell, abortController }) {
|
|
|
47
47
|
const result = waf.run({ ephemeral }, req, raspRule)
|
|
48
48
|
|
|
49
49
|
const res = store?.res
|
|
50
|
-
handleResult(result, req, res, abortController, config)
|
|
50
|
+
handleResult(result, req, res, abortController, config, raspRule)
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
module.exports = {
|
|
@@ -7,6 +7,7 @@ const ssrf = require('./ssrf')
|
|
|
7
7
|
const sqli = require('./sql_injection')
|
|
8
8
|
const lfi = require('./lfi')
|
|
9
9
|
const cmdi = require('./command_injection')
|
|
10
|
+
const { updateRaspRuleMatchMetricTags } = require('../telemetry')
|
|
10
11
|
|
|
11
12
|
const { DatadogRaspAbortError } = require('./utils')
|
|
12
13
|
|
|
@@ -84,9 +85,10 @@ function blockOnDatadogRaspAbortError ({ error }) {
|
|
|
84
85
|
const abortError = findDatadogRaspAbortError(error)
|
|
85
86
|
if (!abortError) return false
|
|
86
87
|
|
|
87
|
-
const { req, res, blockingAction } = abortError
|
|
88
|
+
const { req, res, blockingAction, raspRule } = abortError
|
|
88
89
|
if (!isBlocked(res)) {
|
|
89
|
-
block(req, res, web.root(req), null, blockingAction)
|
|
90
|
+
const blocked = block(req, res, web.root(req), null, blockingAction)
|
|
91
|
+
updateRaspRuleMatchMetricTags(req, raspRule, true, blocked)
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
return true
|
|
@@ -61,7 +61,7 @@ function analyzeLfi (ctx) {
|
|
|
61
61
|
const raspRule = { type: RULE_TYPES.LFI }
|
|
62
62
|
|
|
63
63
|
const result = waf.run({ ephemeral }, req, raspRule)
|
|
64
|
-
handleResult(result, req, res, ctx.abortController, config)
|
|
64
|
+
handleResult(result, req, res, ctx.abortController, config, raspRule)
|
|
65
65
|
})
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -76,7 +76,7 @@ function analyzeSqlInjection (query, dbSystem, abortController) {
|
|
|
76
76
|
|
|
77
77
|
const result = waf.run({ ephemeral }, req, raspRule)
|
|
78
78
|
|
|
79
|
-
handleResult(result, req, res, abortController, config)
|
|
79
|
+
handleResult(result, req, res, abortController, config, raspRule)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
function hasInputAddress (payload) {
|
|
@@ -34,7 +34,7 @@ function analyzeSsrf (ctx) {
|
|
|
34
34
|
const result = waf.run({ ephemeral }, req, raspRule)
|
|
35
35
|
|
|
36
36
|
const res = store?.res
|
|
37
|
-
handleResult(result, req, res, ctx.abortController, config)
|
|
37
|
+
handleResult(result, req, res, ctx.abortController, config, raspRule)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
module.exports = { enable, disable }
|
|
@@ -4,6 +4,7 @@ const web = require('../../plugins/util/web')
|
|
|
4
4
|
const { getCallsiteFrames, reportStackTrace, canReportStackTrace } = require('../stack_trace')
|
|
5
5
|
const { getBlockingAction } = require('../blocking')
|
|
6
6
|
const log = require('../../log')
|
|
7
|
+
const { updateRaspRuleMatchMetricTags } = require('../telemetry')
|
|
7
8
|
|
|
8
9
|
const abortOnUncaughtException = process.execArgv?.includes('--abort-on-uncaught-exception')
|
|
9
10
|
|
|
@@ -19,16 +20,17 @@ const RULE_TYPES = {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
class DatadogRaspAbortError extends Error {
|
|
22
|
-
constructor (req, res, blockingAction) {
|
|
23
|
+
constructor (req, res, blockingAction, raspRule) {
|
|
23
24
|
super('DatadogRaspAbortError')
|
|
24
25
|
this.name = 'DatadogRaspAbortError'
|
|
25
26
|
this.req = req
|
|
26
27
|
this.res = res
|
|
27
28
|
this.blockingAction = blockingAction
|
|
29
|
+
this.raspRule = raspRule
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
function handleResult (actions, req, res, abortController, config) {
|
|
33
|
+
function handleResult (actions, req, res, abortController, config, raspRule) {
|
|
32
34
|
const generateStackTraceAction = actions?.generate_stack
|
|
33
35
|
|
|
34
36
|
const { enabled, maxDepth, maxStackTraces } = config.appsec.stackTrace
|
|
@@ -45,21 +47,24 @@ function handleResult (actions, req, res, abortController, config) {
|
|
|
45
47
|
)
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
if (
|
|
50
|
+
if (abortController && !abortOnUncaughtException) {
|
|
51
|
+
const blockingAction = getBlockingAction(actions)
|
|
49
52
|
|
|
50
|
-
const blockingAction = getBlockingAction(actions)
|
|
51
|
-
if (blockingAction) {
|
|
52
53
|
// Should block only in express
|
|
53
|
-
if (rootSpan?.context()._name === 'express.request') {
|
|
54
|
-
const abortError = new DatadogRaspAbortError(req, res, blockingAction)
|
|
54
|
+
if (blockingAction && rootSpan?.context()._name === 'express.request') {
|
|
55
|
+
const abortError = new DatadogRaspAbortError(req, res, blockingAction, raspRule)
|
|
55
56
|
abortController.abort(abortError)
|
|
56
57
|
|
|
57
58
|
// TODO Delete this when support for node 16 is removed
|
|
58
59
|
if (!abortController.signal.reason) {
|
|
59
60
|
abortController.signal.reason = abortError
|
|
60
61
|
}
|
|
62
|
+
|
|
63
|
+
return
|
|
61
64
|
}
|
|
62
65
|
}
|
|
66
|
+
|
|
67
|
+
updateRaspRuleMatchMetricTags(req, raspRule, false, false)
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
module.exports = {
|