dd-trace 5.24.0 → 5.26.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 +3 -0
- package/index.d.ts +345 -8
- package/init.js +60 -47
- package/package.json +16 -7
- package/packages/datadog-code-origin/index.js +4 -4
- package/packages/datadog-core/index.js +1 -3
- package/packages/datadog-core/src/storage.js +21 -0
- package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-instrumentations/src/amqplib.js +65 -5
- package/packages/datadog-instrumentations/src/child_process.js +135 -27
- package/packages/datadog-instrumentations/src/express.js +1 -1
- package/packages/datadog-instrumentations/src/handlebars.js +40 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
- package/packages/datadog-instrumentations/src/jest.js +6 -2
- package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
- package/packages/datadog-instrumentations/src/multer.js +37 -0
- package/packages/datadog-instrumentations/src/openai.js +2 -2
- package/packages/datadog-instrumentations/src/pug.js +23 -0
- package/packages/datadog-instrumentations/src/router.js +2 -3
- package/packages/datadog-instrumentations/src/url.js +84 -0
- package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
- package/packages/datadog-plugin-amqplib/src/consumer.js +6 -5
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +10 -7
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +35 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +11 -9
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
- package/packages/datadog-plugin-cypress/src/support.js +1 -0
- package/packages/datadog-plugin-fastify/src/code_origin.js +2 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +10 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
- package/packages/datadog-plugin-grpc/src/client.js +3 -0
- package/packages/datadog-plugin-grpc/src/server.js +5 -1
- package/packages/datadog-plugin-http/src/client.js +42 -1
- package/packages/datadog-plugin-http2/src/client.js +26 -1
- package/packages/datadog-plugin-jest/src/index.js +2 -1
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +10 -5
- package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
- package/packages/datadog-plugin-mocha/src/index.js +5 -2
- package/packages/datadog-plugin-moleculer/src/server.js +2 -2
- package/packages/datadog-plugin-openai/src/index.js +9 -1015
- package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
- package/packages/datadog-plugin-rhea/src/consumer.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +2 -1
- package/packages/dd-trace/src/appsec/addresses.js +2 -0
- package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
- package/packages/dd-trace/src/appsec/index.js +9 -6
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
- package/packages/dd-trace/src/appsec/rasp/index.js +3 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
- package/packages/dd-trace/src/appsec/rasp/utils.js +3 -2
- package/packages/dd-trace/src/appsec/recommended.json +354 -158
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -7
- package/packages/dd-trace/src/appsec/reporter.js +6 -4
- package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -3
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
- package/packages/dd-trace/src/azure_metadata.js +120 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
- package/packages/dd-trace/src/config.js +88 -10
- package/packages/dd-trace/src/constants.js +8 -1
- package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
- package/packages/dd-trace/src/crashtracking/index.js +15 -0
- package/packages/dd-trace/src/crashtracking/noop.js +8 -0
- package/packages/dd-trace/src/datastreams/pathway.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +9 -13
- package/packages/dd-trace/src/debugger/devtools_client/send.js +15 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +57 -23
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +12 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +31 -20
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +11 -2
- package/packages/dd-trace/src/debugger/index.js +10 -3
- package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
- package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
- package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
- package/packages/dd-trace/src/llmobs/index.js +103 -0
- package/packages/dd-trace/src/llmobs/noop.js +82 -0
- package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
- package/packages/dd-trace/src/llmobs/sdk.js +377 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
- package/packages/dd-trace/src/llmobs/storage.js +7 -0
- package/packages/dd-trace/src/llmobs/tagger.js +322 -0
- package/packages/dd-trace/src/llmobs/util.js +176 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
- package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
- package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +52 -0
- package/packages/dd-trace/src/log/index.js +10 -13
- package/packages/dd-trace/src/log/log.js +52 -0
- package/packages/dd-trace/src/log/writer.js +50 -19
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/noop/span.js +4 -0
- package/packages/dd-trace/src/opentelemetry/span.js +16 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +106 -32
- package/packages/dd-trace/src/opentracing/span.js +26 -0
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/opentracing/tracer.js +8 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
- package/packages/dd-trace/src/plugins/outbound.js +9 -0
- package/packages/dd-trace/src/plugins/tracing.js +3 -3
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
- package/packages/dd-trace/src/plugins/util/web.js +39 -11
- package/packages/dd-trace/src/priority_sampler.js +16 -0
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
- package/packages/dd-trace/src/profiling/profilers/wall.js +2 -1
- package/packages/dd-trace/src/proxy.js +13 -1
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/telemetry/index.js +11 -1
- package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
- package/packages/dd-trace/src/telemetry/metrics.js +6 -1
- package/packages/dd-trace/src/util.js +16 -1
- package/version.js +4 -2
- /package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/{code-injection-sensitive-analyzer.js → tainted-range-based-sensitive-analyzer.js} +0 -0
|
@@ -191,7 +191,8 @@ class VitestPlugin extends CiPlugin {
|
|
|
191
191
|
[COMPONENT]: this.constructor.id,
|
|
192
192
|
...this.testEnvironmentMetadata,
|
|
193
193
|
...testSuiteMetadata
|
|
194
|
-
}
|
|
194
|
+
},
|
|
195
|
+
extractedLinks: testSessionSpanContext?._links
|
|
195
196
|
})
|
|
196
197
|
this.telemetry.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
197
198
|
const store = storage.getStore()
|
|
@@ -28,6 +28,8 @@ module.exports = {
|
|
|
28
28
|
DB_STATEMENT: 'server.db.statement',
|
|
29
29
|
DB_SYSTEM: 'server.db.system',
|
|
30
30
|
|
|
31
|
+
SHELL_COMMAND: 'server.sys.shell.cmd',
|
|
32
|
+
|
|
31
33
|
LOGIN_SUCCESS: 'server.business_logic.users.login.success',
|
|
32
34
|
LOGIN_FAILURE: 'server.business_logic.users.login.failure'
|
|
33
35
|
}
|
|
@@ -1,61 +1,84 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const TTLCache = require('@isaacs/ttlcache')
|
|
4
|
+
const web = require('../plugins/util/web')
|
|
3
5
|
const log = require('../log')
|
|
6
|
+
const { AUTO_REJECT, USER_REJECT } = require('../../../../ext/priority')
|
|
7
|
+
|
|
8
|
+
const MAX_SIZE = 4096
|
|
4
9
|
|
|
5
10
|
let enabled
|
|
6
|
-
let
|
|
11
|
+
let sampledRequests
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
class NoopTTLCache {
|
|
14
|
+
clear () { }
|
|
15
|
+
set (key) { return undefined }
|
|
16
|
+
has (key) { return false }
|
|
17
|
+
}
|
|
9
18
|
|
|
10
19
|
function configure ({ apiSecurity }) {
|
|
11
20
|
enabled = apiSecurity.enabled
|
|
12
|
-
|
|
21
|
+
sampledRequests = apiSecurity.sampleDelay === 0
|
|
22
|
+
? new NoopTTLCache()
|
|
23
|
+
: new TTLCache({ max: MAX_SIZE, ttl: apiSecurity.sampleDelay * 1000 })
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
function disable () {
|
|
16
27
|
enabled = false
|
|
28
|
+
sampledRequests?.clear()
|
|
17
29
|
}
|
|
18
30
|
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
}
|
|
31
|
+
function sampleRequest (req, res, force = false) {
|
|
32
|
+
if (!enabled) return false
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
const key = computeKey(req, res)
|
|
35
|
+
if (!key || isSampled(key)) return false
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
const rootSpan = web.root(req)
|
|
38
|
+
if (!rootSpan) return false
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
let priority = getSpanPriority(rootSpan)
|
|
41
|
+
if (!priority) {
|
|
42
|
+
rootSpan._prioritySampler?.sample(rootSpan)
|
|
43
|
+
priority = getSpanPriority(rootSpan)
|
|
32
44
|
}
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function sampleRequest (req) {
|
|
38
|
-
if (!enabled || !requestSampling) {
|
|
46
|
+
if (priority === AUTO_REJECT || priority === USER_REJECT) {
|
|
39
47
|
return false
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (shouldSample) {
|
|
45
|
-
sampledRequests.add(req)
|
|
50
|
+
if (force) {
|
|
51
|
+
sampledRequests.set(key)
|
|
46
52
|
}
|
|
47
53
|
|
|
48
|
-
return
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isSampled (key) {
|
|
58
|
+
return sampledRequests.has(key)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function computeKey (req, res) {
|
|
62
|
+
const route = web.getContext(req)?.paths?.join('') || ''
|
|
63
|
+
const method = req.method
|
|
64
|
+
const status = res.statusCode
|
|
65
|
+
|
|
66
|
+
if (!method || !status) {
|
|
67
|
+
log.warn('Unsupported groupkey for API security')
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
return method + route + status
|
|
49
71
|
}
|
|
50
72
|
|
|
51
|
-
function
|
|
52
|
-
|
|
73
|
+
function getSpanPriority (span) {
|
|
74
|
+
const spanContext = span.context?.()
|
|
75
|
+
return spanContext._sampling?.priority
|
|
53
76
|
}
|
|
54
77
|
|
|
55
78
|
module.exports = {
|
|
56
79
|
configure,
|
|
57
80
|
disable,
|
|
58
|
-
setRequestSampling,
|
|
59
81
|
sampleRequest,
|
|
60
|
-
isSampled
|
|
82
|
+
isSampled,
|
|
83
|
+
computeKey
|
|
61
84
|
}
|
|
@@ -6,6 +6,7 @@ const dc = require('dc-polyfill')
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
bodyParser: dc.channel('datadog:body-parser:read:finish'),
|
|
8
8
|
cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
|
|
9
|
+
multerParser: dc.channel('datadog:multer:read:finish'),
|
|
9
10
|
startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
|
|
10
11
|
graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
|
|
11
12
|
apolloChannel: dc.tracingChannel('datadog:apollo:request'),
|
|
@@ -28,5 +29,6 @@ module.exports = {
|
|
|
28
29
|
mysql2OuterQueryStart: dc.channel('datadog:mysql2:outerquery:start'),
|
|
29
30
|
wafRunFinished: dc.channel('datadog:waf:run:finish'),
|
|
30
31
|
fsOperationStart: dc.channel('apm:fs:operation:start'),
|
|
31
|
-
expressMiddlewareError: dc.channel('apm:express:middleware:error')
|
|
32
|
+
expressMiddlewareError: dc.channel('apm:express:middleware:error'),
|
|
33
|
+
childProcessExecutionTracingChannel: dc.tracingChannel('datadog:child_process:execution')
|
|
32
34
|
}
|
|
@@ -15,6 +15,7 @@ module.exports = {
|
|
|
15
15
|
PATH_TRAVERSAL_ANALYZER: require('./path-traversal-analyzer'),
|
|
16
16
|
SQL_INJECTION_ANALYZER: require('./sql-injection-analyzer'),
|
|
17
17
|
SSRF: require('./ssrf-analyzer'),
|
|
18
|
+
TEMPLATE_INJECTION_ANALYZER: require('./template-injection-analyzer'),
|
|
18
19
|
UNVALIDATED_REDIRECT_ANALYZER: require('./unvalidated-redirect-analyzer'),
|
|
19
20
|
WEAK_CIPHER_ANALYZER: require('./weak-cipher-analyzer'),
|
|
20
21
|
WEAK_HASH_ANALYZER: require('./weak-hash-analyzer'),
|
|
@@ -6,7 +6,6 @@ const { getNodeModulesPaths } = require('../path-line')
|
|
|
6
6
|
const { HEADER_NAME_VALUE_SEPARATOR } = require('../vulnerabilities-formatter/constants')
|
|
7
7
|
const { getRanges } = require('../taint-tracking/operations')
|
|
8
8
|
const {
|
|
9
|
-
HTTP_REQUEST_COOKIE_NAME,
|
|
10
9
|
HTTP_REQUEST_COOKIE_VALUE,
|
|
11
10
|
HTTP_REQUEST_HEADER_VALUE
|
|
12
11
|
} = require('../taint-tracking/source-types')
|
|
@@ -45,13 +44,7 @@ class HeaderInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
45
44
|
if (this.isExcludedHeaderName(lowerCasedHeaderName) || typeof value !== 'string') return
|
|
46
45
|
|
|
47
46
|
const ranges = getRanges(iastContext, value)
|
|
48
|
-
|
|
49
|
-
return !(this.isCookieExclusion(lowerCasedHeaderName, ranges) ||
|
|
50
|
-
this.isSameHeaderExclusion(lowerCasedHeaderName, ranges) ||
|
|
51
|
-
this.isAccessControlAllowExclusion(lowerCasedHeaderName, ranges))
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return false
|
|
47
|
+
return ranges?.length > 0 && !this.shouldIgnoreHeader(lowerCasedHeaderName, ranges)
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
_getEvidence (headerInfo, iastContext) {
|
|
@@ -75,28 +68,52 @@ class HeaderInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
75
68
|
return EXCLUDED_HEADER_NAMES.includes(name)
|
|
76
69
|
}
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
isAllRangesFromHeader (ranges, headerName) {
|
|
72
|
+
return ranges
|
|
73
|
+
.every(range =>
|
|
74
|
+
range.iinfo.type === HTTP_REQUEST_HEADER_VALUE && range.iinfo.parameterName?.toLowerCase() === headerName
|
|
75
|
+
)
|
|
76
|
+
}
|
|
83
77
|
|
|
84
|
-
|
|
78
|
+
isAllRangesFromSource (ranges, source) {
|
|
79
|
+
return ranges
|
|
80
|
+
.every(range => range.iinfo.type === source)
|
|
85
81
|
}
|
|
86
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Exclude access-control-allow-*: when the header starts with access-control-allow- and the
|
|
85
|
+
* source of the tainted range is a request header
|
|
86
|
+
*/
|
|
87
87
|
isAccessControlAllowExclusion (name, ranges) {
|
|
88
88
|
if (name?.startsWith('access-control-allow-')) {
|
|
89
|
-
return ranges
|
|
90
|
-
.every(range => range.iinfo.type === HTTP_REQUEST_HEADER_VALUE)
|
|
89
|
+
return this.isAllRangesFromSource(ranges, HTTP_REQUEST_HEADER_VALUE)
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
return false
|
|
94
93
|
}
|
|
95
94
|
|
|
95
|
+
/** Exclude when the header is reflected from the request */
|
|
96
96
|
isSameHeaderExclusion (name, ranges) {
|
|
97
97
|
return ranges.length === 1 && name === ranges[0].iinfo.parameterName?.toLowerCase()
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
shouldIgnoreHeader (headerName, ranges) {
|
|
101
|
+
switch (headerName) {
|
|
102
|
+
case 'set-cookie':
|
|
103
|
+
/** Exclude set-cookie header if the source of all the tainted ranges are cookies */
|
|
104
|
+
return this.isAllRangesFromSource(ranges, HTTP_REQUEST_COOKIE_VALUE)
|
|
105
|
+
case 'pragma':
|
|
106
|
+
/** Ignore pragma headers when the source is the cache control header. */
|
|
107
|
+
return this.isAllRangesFromHeader(ranges, 'cache-control')
|
|
108
|
+
case 'transfer-encoding':
|
|
109
|
+
case 'content-encoding':
|
|
110
|
+
/** Ignore transfer and content encoding headers when the source is the accept encoding header. */
|
|
111
|
+
return this.isAllRangesFromHeader(ranges, 'accept-encoding')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return this.isAccessControlAllowExclusion(headerName, ranges) || this.isSameHeaderExclusion(headerName, ranges)
|
|
115
|
+
}
|
|
116
|
+
|
|
100
117
|
_getExcludedPaths () {
|
|
101
118
|
return EXCLUDED_PATHS
|
|
102
119
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { TEMPLATE_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
|
|
6
|
+
class TemplateInjectionAnalyzer extends InjectionAnalyzer {
|
|
7
|
+
constructor () {
|
|
8
|
+
super(TEMPLATE_INJECTION)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
onConfigure () {
|
|
12
|
+
this.addSub('datadog:handlebars:compile:start', ({ source }) => this.analyze(source))
|
|
13
|
+
this.addSub('datadog:handlebars:register-partial:start', ({ partial }) => this.analyze(partial))
|
|
14
|
+
this.addSub('datadog:pug:compile:start', ({ source }) => this.analyze(source))
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = new TemplateInjectionAnalyzer()
|
|
@@ -23,18 +23,26 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
23
23
|
constructor () {
|
|
24
24
|
super()
|
|
25
25
|
this._type = 'taint-tracking'
|
|
26
|
+
this._taintedURLs = new WeakMap()
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
onConfigure () {
|
|
30
|
+
const onRequestBody = ({ req }) => {
|
|
31
|
+
const iastContext = getIastContext(storage.getStore())
|
|
32
|
+
if (iastContext && iastContext.body !== req.body) {
|
|
33
|
+
this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
|
|
34
|
+
iastContext.body = req.body
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
this.addSub(
|
|
30
39
|
{ channelName: 'datadog:body-parser:read:finish', tag: HTTP_REQUEST_BODY },
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
40
|
+
onRequestBody
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
this.addSub(
|
|
44
|
+
{ channelName: 'datadog:multer:read:finish', tag: HTTP_REQUEST_BODY },
|
|
45
|
+
onRequestBody
|
|
38
46
|
)
|
|
39
47
|
|
|
40
48
|
this.addSub(
|
|
@@ -81,6 +89,46 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
81
89
|
}
|
|
82
90
|
)
|
|
83
91
|
|
|
92
|
+
const urlResultTaintedProperties = ['host', 'origin', 'hostname']
|
|
93
|
+
this.addSub(
|
|
94
|
+
{ channelName: 'datadog:url:parse:finish' },
|
|
95
|
+
({ input, base, parsed, isURL }) => {
|
|
96
|
+
const iastContext = getIastContext(storage.getStore())
|
|
97
|
+
let ranges
|
|
98
|
+
|
|
99
|
+
if (base) {
|
|
100
|
+
ranges = getRanges(iastContext, base)
|
|
101
|
+
} else {
|
|
102
|
+
ranges = getRanges(iastContext, input)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (ranges?.length) {
|
|
106
|
+
if (isURL) {
|
|
107
|
+
this._taintedURLs.set(parsed, ranges[0])
|
|
108
|
+
} else {
|
|
109
|
+
urlResultTaintedProperties.forEach(param => {
|
|
110
|
+
this._taintTrackingHandler(ranges[0].iinfo.type, parsed, param, iastContext)
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
this.addSub(
|
|
118
|
+
{ channelName: 'datadog:url:getter:finish' },
|
|
119
|
+
(context) => {
|
|
120
|
+
if (!urlResultTaintedProperties.includes(context.property)) return
|
|
121
|
+
|
|
122
|
+
const origRange = this._taintedURLs.get(context.urlObject)
|
|
123
|
+
if (!origRange) return
|
|
124
|
+
|
|
125
|
+
const iastContext = getIastContext(storage.getStore())
|
|
126
|
+
if (!iastContext) return
|
|
127
|
+
|
|
128
|
+
context.result =
|
|
129
|
+
newTaintedString(iastContext, context.result, origRange.iinfo.parameterName, origRange.iinfo.type)
|
|
130
|
+
})
|
|
131
|
+
|
|
84
132
|
// this is a special case to increment INSTRUMENTED_SOURCE metric for header
|
|
85
133
|
this.addInstrumentedSource('http', [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME])
|
|
86
134
|
}
|
|
@@ -5,13 +5,13 @@ const vulnerabilities = require('../../vulnerabilities')
|
|
|
5
5
|
|
|
6
6
|
const { contains, intersects, remove } = require('./range-utils')
|
|
7
7
|
|
|
8
|
-
const codeInjectionSensitiveAnalyzer = require('./sensitive-analyzers/code-injection-sensitive-analyzer')
|
|
9
8
|
const commandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
|
|
10
9
|
const hardcodedPasswordAnalyzer = require('./sensitive-analyzers/hardcoded-password-analyzer')
|
|
11
10
|
const headerSensitiveAnalyzer = require('./sensitive-analyzers/header-sensitive-analyzer')
|
|
12
11
|
const jsonSensitiveAnalyzer = require('./sensitive-analyzers/json-sensitive-analyzer')
|
|
13
12
|
const ldapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
|
|
14
13
|
const sqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
|
|
14
|
+
const taintedRangeBasedSensitiveAnalyzer = require('./sensitive-analyzers/tainted-range-based-sensitive-analyzer')
|
|
15
15
|
const urlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
|
|
16
16
|
|
|
17
17
|
const { DEFAULT_IAST_REDACTION_NAME_PATTERN, DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./sensitive-regex')
|
|
@@ -24,7 +24,8 @@ class SensitiveHandler {
|
|
|
24
24
|
this._valuePattern = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
|
|
25
25
|
|
|
26
26
|
this._sensitiveAnalyzers = new Map()
|
|
27
|
-
this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION,
|
|
27
|
+
this._sensitiveAnalyzers.set(vulnerabilities.CODE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|
|
28
|
+
this._sensitiveAnalyzers.set(vulnerabilities.TEMPLATE_INJECTION, taintedRangeBasedSensitiveAnalyzer)
|
|
28
29
|
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, commandSensitiveAnalyzer)
|
|
29
30
|
this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, jsonSensitiveAnalyzer)
|
|
30
31
|
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, ldapSensitiveAnalyzer)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { MANUAL_KEEP } = require('../../../../../ext/tags')
|
|
4
3
|
const LRU = require('lru-cache')
|
|
5
4
|
const vulnerabilitiesFormatter = require('./vulnerabilities-formatter')
|
|
6
5
|
const { IAST_ENABLED_TAG_KEY, IAST_JSON_TAG_KEY } = require('./tags')
|
|
7
6
|
const standalone = require('../standalone')
|
|
7
|
+
const { SAMPLING_MECHANISM_APPSEC } = require('../../constants')
|
|
8
|
+
const { keepTrace } = require('../../priority_sampler')
|
|
8
9
|
|
|
9
10
|
const VULNERABILITIES_KEY = 'vulnerabilities'
|
|
10
11
|
const VULNERABILITY_HASHES_MAX_SIZE = 1000
|
|
@@ -56,9 +57,10 @@ function sendVulnerabilities (vulnerabilities, rootSpan) {
|
|
|
56
57
|
const tags = {}
|
|
57
58
|
// TODO: Store this outside of the span and set the tag in the exporter.
|
|
58
59
|
tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
|
|
59
|
-
tags[MANUAL_KEEP] = 'true'
|
|
60
60
|
span.addTags(tags)
|
|
61
61
|
|
|
62
|
+
keepTrace(span, SAMPLING_MECHANISM_APPSEC)
|
|
63
|
+
|
|
62
64
|
standalone.sample(span)
|
|
63
65
|
|
|
64
66
|
if (!rootSpan) span.finish()
|
|
@@ -6,6 +6,7 @@ const remoteConfig = require('./remote_config')
|
|
|
6
6
|
const {
|
|
7
7
|
bodyParser,
|
|
8
8
|
cookieParser,
|
|
9
|
+
multerParser,
|
|
9
10
|
incomingHttpRequestStart,
|
|
10
11
|
incomingHttpRequestEnd,
|
|
11
12
|
passportVerify,
|
|
@@ -58,6 +59,7 @@ function enable (_config) {
|
|
|
58
59
|
apiSecuritySampler.configure(_config.appsec)
|
|
59
60
|
|
|
60
61
|
bodyParser.subscribe(onRequestBodyParsed)
|
|
62
|
+
multerParser.subscribe(onRequestBodyParsed)
|
|
61
63
|
cookieParser.subscribe(onRequestCookieParser)
|
|
62
64
|
incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
|
|
63
65
|
incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
|
|
@@ -143,10 +145,6 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
|
|
|
143
145
|
persistent[addresses.HTTP_CLIENT_IP] = clientIp
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
if (apiSecuritySampler.sampleRequest(req)) {
|
|
147
|
-
persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
|
|
148
|
-
}
|
|
149
|
-
|
|
150
148
|
const actions = waf.run({ persistent }, req)
|
|
151
149
|
|
|
152
150
|
handleResults(actions, req, res, rootSpan, abortController)
|
|
@@ -170,6 +168,10 @@ function incomingHttpEndTranslator ({ req, res }) {
|
|
|
170
168
|
persistent[addresses.HTTP_INCOMING_QUERY] = req.query
|
|
171
169
|
}
|
|
172
170
|
|
|
171
|
+
if (apiSecuritySampler.sampleRequest(req, res, true)) {
|
|
172
|
+
persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
|
|
173
|
+
}
|
|
174
|
+
|
|
173
175
|
if (Object.keys(persistent).length) {
|
|
174
176
|
waf.run({ persistent }, req)
|
|
175
177
|
}
|
|
@@ -226,9 +228,9 @@ function onRequestProcessParams ({ req, res, abortController, params }) {
|
|
|
226
228
|
handleResults(results, req, res, rootSpan, abortController)
|
|
227
229
|
}
|
|
228
230
|
|
|
229
|
-
function onResponseBody ({ req, body }) {
|
|
231
|
+
function onResponseBody ({ req, res, body }) {
|
|
230
232
|
if (!body || typeof body !== 'object') return
|
|
231
|
-
if (!apiSecuritySampler.
|
|
233
|
+
if (!apiSecuritySampler.sampleRequest(req, res)) return
|
|
232
234
|
|
|
233
235
|
// we don't support blocking at this point, so no results needed
|
|
234
236
|
waf.run({
|
|
@@ -299,6 +301,7 @@ function disable () {
|
|
|
299
301
|
|
|
300
302
|
// Channel#unsubscribe() is undefined for non active channels
|
|
301
303
|
if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
|
|
304
|
+
if (multerParser.hasSubscribers) multerParser.unsubscribe(onRequestBodyParsed)
|
|
302
305
|
if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
|
|
303
306
|
if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
|
|
304
307
|
if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { childProcessExecutionTracingChannel } = require('../channels')
|
|
4
|
+
const { RULE_TYPES, handleResult } = require('./utils')
|
|
5
|
+
const { storage } = require('../../../../datadog-core')
|
|
6
|
+
const addresses = require('../addresses')
|
|
7
|
+
const waf = require('../waf')
|
|
8
|
+
|
|
9
|
+
let config
|
|
10
|
+
|
|
11
|
+
function enable (_config) {
|
|
12
|
+
config = _config
|
|
13
|
+
|
|
14
|
+
childProcessExecutionTracingChannel.subscribe({
|
|
15
|
+
start: analyzeCommandInjection
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function disable () {
|
|
20
|
+
if (childProcessExecutionTracingChannel.start.hasSubscribers) {
|
|
21
|
+
childProcessExecutionTracingChannel.unsubscribe({
|
|
22
|
+
start: analyzeCommandInjection
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function analyzeCommandInjection ({ file, fileArgs, shell, abortController }) {
|
|
28
|
+
if (!file || !shell) return
|
|
29
|
+
|
|
30
|
+
const store = storage.getStore()
|
|
31
|
+
const req = store?.req
|
|
32
|
+
if (!req) return
|
|
33
|
+
|
|
34
|
+
const commandParams = fileArgs ? [file, ...fileArgs] : file
|
|
35
|
+
|
|
36
|
+
const persistent = {
|
|
37
|
+
[addresses.SHELL_COMMAND]: commandParams
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = waf.run({ persistent }, req, RULE_TYPES.COMMAND_INJECTION)
|
|
41
|
+
|
|
42
|
+
const res = store?.res
|
|
43
|
+
handleResult(result, req, res, abortController, config)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
enable,
|
|
48
|
+
disable
|
|
49
|
+
}
|
|
@@ -6,6 +6,7 @@ const { block, isBlocked } = require('../blocking')
|
|
|
6
6
|
const ssrf = require('./ssrf')
|
|
7
7
|
const sqli = require('./sql_injection')
|
|
8
8
|
const lfi = require('./lfi')
|
|
9
|
+
const cmdi = require('./command_injection')
|
|
9
10
|
|
|
10
11
|
const { DatadogRaspAbortError } = require('./utils')
|
|
11
12
|
|
|
@@ -95,6 +96,7 @@ function enable (config) {
|
|
|
95
96
|
ssrf.enable(config)
|
|
96
97
|
sqli.enable(config)
|
|
97
98
|
lfi.enable(config)
|
|
99
|
+
cmdi.enable(config)
|
|
98
100
|
|
|
99
101
|
process.on('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
|
|
100
102
|
expressMiddlewareError.subscribe(blockOnDatadogRaspAbortError)
|
|
@@ -104,6 +106,7 @@ function disable () {
|
|
|
104
106
|
ssrf.disable()
|
|
105
107
|
sqli.disable()
|
|
106
108
|
lfi.disable()
|
|
109
|
+
cmdi.disable()
|
|
107
110
|
|
|
108
111
|
process.off('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
|
|
109
112
|
if (expressMiddlewareError.hasSubscribers) expressMiddlewareError.unsubscribe(blockOnDatadogRaspAbortError)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { format } = require('url')
|
|
3
4
|
const { httpClientRequestStart } = require('../channels')
|
|
4
5
|
const { storage } = require('../../../../datadog-core')
|
|
5
6
|
const addresses = require('../addresses')
|
|
@@ -20,12 +21,12 @@ function disable () {
|
|
|
20
21
|
function analyzeSsrf (ctx) {
|
|
21
22
|
const store = storage.getStore()
|
|
22
23
|
const req = store?.req
|
|
23
|
-
const
|
|
24
|
+
const outgoingUrl = (ctx.args.options?.uri && format(ctx.args.options.uri)) ?? ctx.args.uri
|
|
24
25
|
|
|
25
|
-
if (!req || !
|
|
26
|
+
if (!req || !outgoingUrl) return
|
|
26
27
|
|
|
27
28
|
const persistent = {
|
|
28
|
-
[addresses.HTTP_OUTGOING_URL]:
|
|
29
|
+
[addresses.HTTP_OUTGOING_URL]: outgoingUrl
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
const result = waf.run({ persistent }, req, RULE_TYPES.SSRF)
|
|
@@ -12,9 +12,10 @@ if (abortOnUncaughtException) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
const RULE_TYPES = {
|
|
15
|
-
|
|
15
|
+
COMMAND_INJECTION: 'command_injection',
|
|
16
|
+
LFI: 'lfi',
|
|
16
17
|
SQL_INJECTION: 'sql_injection',
|
|
17
|
-
|
|
18
|
+
SSRF: 'ssrf'
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
class DatadogRaspAbortError extends Error {
|