dd-trace 2.40.0 → 2.41.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/package.json +3 -3
- package/packages/datadog-instrumentations/src/cucumber.js +5 -2
- package/packages/datadog-instrumentations/src/grpc/client.js +44 -42
- package/packages/datadog-instrumentations/src/grpc/server.js +69 -60
- package/packages/datadog-instrumentations/src/http2/client.js +25 -26
- package/packages/datadog-instrumentations/src/jest.js +3 -1
- package/packages/datadog-instrumentations/src/mocha.js +5 -3
- package/packages/datadog-plugin-cypress/src/plugin.js +4 -2
- package/packages/datadog-plugin-grpc/src/client.js +29 -11
- package/packages/datadog-plugin-grpc/src/server.js +22 -6
- package/packages/datadog-plugin-http2/src/client.js +46 -29
- package/packages/datadog-plugin-router/src/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +7 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +19 -15
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +5 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +18 -19
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +3 -0
- package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +205 -0
- package/packages/dd-trace/src/appsec/iast/index.js +6 -5
- package/packages/dd-trace/src/appsec/iast/tags.js +2 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +6 -6
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +23 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +32 -16
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +33 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +23 -16
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +76 -37
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +101 -0
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +45 -0
- package/packages/dd-trace/src/appsec/iast/telemetry/{logs.js → log/index.js} +5 -5
- package/packages/dd-trace/src/appsec/iast/telemetry/{log_collector.js → log/log-collector.js} +1 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/namespaces.js +76 -0
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +53 -0
- package/packages/dd-trace/src/appsec/iast/telemetry/verbosity.js +42 -0
- package/packages/dd-trace/src/config.js +21 -2
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/opentracing/tracer.js +1 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/outbound.js +29 -12
- package/packages/dd-trace/src/plugins/plugin.js +28 -0
- package/packages/dd-trace/src/plugins/tracing.js +33 -16
- package/packages/dd-trace/src/plugins/util/ci.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +55 -11
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -22
- package/packages/dd-trace/src/profiling/config.js +0 -3
- package/packages/dd-trace/src/profiling/index.js +0 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +23 -11
- package/packages/diagnostics_channel/src/index.js +64 -0
- package/packages/dd-trace/src/profiling/profilers/cpu.js +0 -126
- /package/packages/dd-trace/src/appsec/iast/taint-tracking/{origin-types.js → source-types.js} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { storage } = require('../../datadog-core')
|
|
3
4
|
const ServerPlugin = require('../../dd-trace/src/plugins/server')
|
|
4
5
|
const { TEXT_MAP } = require('../../../ext/formats')
|
|
5
6
|
const { addMetadataTags, getFilter, getMethodMetadata } = require('./util')
|
|
@@ -7,6 +8,7 @@ const { addMetadataTags, getFilter, getMethodMetadata } = require('./util')
|
|
|
7
8
|
class GrpcServerPlugin extends ServerPlugin {
|
|
8
9
|
static get id () { return 'grpc' }
|
|
9
10
|
static get operation () { return 'server:request' }
|
|
11
|
+
static get prefix () { return `apm:grpc:server:request` }
|
|
10
12
|
|
|
11
13
|
constructor (...args) {
|
|
12
14
|
super(...args)
|
|
@@ -18,9 +20,15 @@ class GrpcServerPlugin extends ServerPlugin {
|
|
|
18
20
|
|
|
19
21
|
this.addCode(span, code)
|
|
20
22
|
})
|
|
23
|
+
|
|
24
|
+
this.addTraceBind('emit', ({ currentStore }) => {
|
|
25
|
+
return currentStore
|
|
26
|
+
})
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
bindStart (message) {
|
|
30
|
+
const store = storage.getStore()
|
|
31
|
+
const { name, metadata, type } = message
|
|
24
32
|
const metadataFilter = this.config.metadataFilter
|
|
25
33
|
const childOf = extract(this.tracer, metadata)
|
|
26
34
|
const method = getMethodMetadata(name, type)
|
|
@@ -44,9 +52,19 @@ class GrpcServerPlugin extends ServerPlugin {
|
|
|
44
52
|
})
|
|
45
53
|
|
|
46
54
|
addMetadataTags(span, metadata, metadataFilter, 'request')
|
|
55
|
+
|
|
56
|
+
message.span = span
|
|
57
|
+
message.parentStore = store
|
|
58
|
+
message.currentStore = { ...store, span }
|
|
59
|
+
|
|
60
|
+
return message.currentStore
|
|
47
61
|
}
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
bindAsyncStart ({ parentStore }) {
|
|
64
|
+
return parentStore
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
error ({ error }) {
|
|
50
68
|
const span = this.activeSpan
|
|
51
69
|
|
|
52
70
|
if (!span) return
|
|
@@ -55,9 +73,7 @@ class GrpcServerPlugin extends ServerPlugin {
|
|
|
55
73
|
this.addError(error)
|
|
56
74
|
}
|
|
57
75
|
|
|
58
|
-
finish ({ code, trailer }
|
|
59
|
-
const span = this.activeSpan
|
|
60
|
-
|
|
76
|
+
finish ({ span, code, trailer }) {
|
|
61
77
|
if (!span) return
|
|
62
78
|
|
|
63
79
|
const metadataFilter = this.config.metadataFilter
|
|
@@ -68,7 +84,7 @@ class GrpcServerPlugin extends ServerPlugin {
|
|
|
68
84
|
addMetadataTags(span, trailer, metadataFilter, 'response')
|
|
69
85
|
}
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
span.finish()
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
configure (config) {
|
|
@@ -8,7 +8,6 @@ const log = require('../../dd-trace/src/log')
|
|
|
8
8
|
const tags = require('../../../ext/tags')
|
|
9
9
|
const kinds = require('../../../ext/kinds')
|
|
10
10
|
const formats = require('../../../ext/formats')
|
|
11
|
-
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
12
11
|
const { COMPONENT, CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
|
|
13
12
|
const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
|
|
14
13
|
|
|
@@ -25,32 +24,11 @@ const HTTP2_HEADER_STATUS = ':status'
|
|
|
25
24
|
const HTTP2_METHOD_GET = 'GET'
|
|
26
25
|
|
|
27
26
|
class Http2ClientPlugin extends ClientPlugin {
|
|
28
|
-
static get id () {
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
constructor (...args) {
|
|
33
|
-
super(...args)
|
|
34
|
-
|
|
35
|
-
this.addSub('apm:http2:client:response', (headers) => {
|
|
36
|
-
const span = storage.getStore().span
|
|
37
|
-
const status = headers && headers[HTTP2_HEADER_STATUS]
|
|
38
|
-
|
|
39
|
-
span.setTag(HTTP_STATUS_CODE, status)
|
|
40
|
-
|
|
41
|
-
if (!this.config.validateStatus(status)) {
|
|
42
|
-
this.addError()
|
|
43
|
-
}
|
|
27
|
+
static get id () { return 'http2' }
|
|
28
|
+
static get prefix () { return `apm:http2:client:request` }
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
addTraceSub (eventName, handler) {
|
|
50
|
-
this.addSub(`apm:${this.constructor.id}:client:${this.operation}:${eventName}`, handler)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
start ({ authority, options, headers = {} }) {
|
|
30
|
+
bindStart (message) {
|
|
31
|
+
const { authority, options, headers = {} } = message
|
|
54
32
|
const sessionDetails = extractSessionDetails(authority, options)
|
|
55
33
|
const path = headers[HTTP2_HEADER_PATH] || '/'
|
|
56
34
|
const pathname = path.split(/[?#]/)[0]
|
|
@@ -75,7 +53,7 @@ class Http2ClientPlugin extends ClientPlugin {
|
|
|
75
53
|
metrics: {
|
|
76
54
|
[CLIENT_PORT_KEY]: parseInt(sessionDetails.port)
|
|
77
55
|
}
|
|
78
|
-
})
|
|
56
|
+
}, false)
|
|
79
57
|
|
|
80
58
|
// TODO: Figure out a better way to do this for any span.
|
|
81
59
|
if (!allowed) {
|
|
@@ -88,14 +66,53 @@ class Http2ClientPlugin extends ClientPlugin {
|
|
|
88
66
|
this.tracer.inject(span, HTTP_HEADERS, headers)
|
|
89
67
|
}
|
|
90
68
|
|
|
91
|
-
|
|
69
|
+
message.parentStore = store
|
|
70
|
+
message.currentStore = { ...store, span }
|
|
71
|
+
|
|
72
|
+
return message.currentStore
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
bindAsyncStart ({ eventName, eventData, currentStore, parentStore }) {
|
|
76
|
+
switch (eventName) {
|
|
77
|
+
case 'response':
|
|
78
|
+
this._onResponse(currentStore, eventData)
|
|
79
|
+
return parentStore
|
|
80
|
+
case 'error':
|
|
81
|
+
this._onError(currentStore, eventData)
|
|
82
|
+
return parentStore
|
|
83
|
+
case 'close':
|
|
84
|
+
this._onClose(currentStore, eventData)
|
|
85
|
+
return parentStore
|
|
86
|
+
}
|
|
92
87
|
|
|
93
|
-
|
|
88
|
+
return storage.getStore()
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
configure (config) {
|
|
97
92
|
return super.configure(normalizeConfig(config))
|
|
98
93
|
}
|
|
94
|
+
|
|
95
|
+
_onResponse (store, headers) {
|
|
96
|
+
const status = headers && headers[HTTP2_HEADER_STATUS]
|
|
97
|
+
|
|
98
|
+
store.span.setTag(HTTP_STATUS_CODE, status)
|
|
99
|
+
|
|
100
|
+
if (!this.config.validateStatus(status)) {
|
|
101
|
+
storage.run(store, () => this.addError())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
addHeaderTags(store.span, headers, HTTP_RESPONSE_HEADERS, this.config)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_onError ({ span }, error) {
|
|
108
|
+
span.setTag('error', error)
|
|
109
|
+
span.finish()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_onClose ({ span }) {
|
|
113
|
+
this.tagPeerService(span)
|
|
114
|
+
span.finish()
|
|
115
|
+
}
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
function extractSessionDetails (authority, options) {
|
|
@@ -5,6 +5,9 @@ const { COMMAND_INJECTION } = require('../vulnerabilities')
|
|
|
5
5
|
class CommandInjectionAnalyzer extends InjectionAnalyzer {
|
|
6
6
|
constructor () {
|
|
7
7
|
super(COMMAND_INJECTION)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onConfigure () {
|
|
8
11
|
this.addSub('datadog:child_process:execution:start', ({ command }) => this.analyze(command))
|
|
9
12
|
}
|
|
10
13
|
}
|
|
@@ -9,7 +9,13 @@ class CookieAnalyzer extends Analyzer {
|
|
|
9
9
|
constructor (type, propertyToBeSafe) {
|
|
10
10
|
super(type)
|
|
11
11
|
this.propertyToBeSafe = propertyToBeSafe.toLowerCase()
|
|
12
|
-
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
onConfigure () {
|
|
15
|
+
this.addSub(
|
|
16
|
+
{ channelName: 'datadog:iast:set-cookie', moduleName: 'http' },
|
|
17
|
+
(cookieInfo) => this.analyze(cookieInfo)
|
|
18
|
+
)
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
_isVulnerable ({ cookieProperties, cookieValue }) {
|
|
@@ -5,6 +5,9 @@ const { LDAP_INJECTION } = require('../vulnerabilities')
|
|
|
5
5
|
class LdapInjectionAnalyzer extends InjectionAnalyzer {
|
|
6
6
|
constructor () {
|
|
7
7
|
super(LDAP_INJECTION)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onConfigure () {
|
|
8
11
|
this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
|
|
9
12
|
}
|
|
10
13
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
6
|
const { getIastContext } = require('../iast-context')
|
|
5
7
|
const { storage } = require('../../../../../datadog-core')
|
|
6
|
-
const InjectionAnalyzer = require('./injection-analyzer')
|
|
7
8
|
const { PATH_TRAVERSAL } = require('../vulnerabilities')
|
|
8
9
|
|
|
9
10
|
const ignoredOperations = ['dir.close', 'close']
|
|
@@ -11,7 +12,23 @@ const ignoredOperations = ['dir.close', 'close']
|
|
|
11
12
|
class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
12
13
|
constructor () {
|
|
13
14
|
super(PATH_TRAVERSAL)
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
this.exclusionList = [
|
|
17
|
+
path.join('node_modules', 'send') + path.sep
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
this.internalExclusionList = [
|
|
21
|
+
'node:fs',
|
|
22
|
+
'node:internal/fs',
|
|
23
|
+
'node:internal\\fs',
|
|
24
|
+
'fs.js',
|
|
25
|
+
'internal/fs',
|
|
26
|
+
'internal\\fs'
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onConfigure () {
|
|
31
|
+
this.addSub('apm:fs:operation:start', (obj) => {
|
|
15
32
|
if (ignoredOperations.includes(obj.operation)) return
|
|
16
33
|
|
|
17
34
|
const pathArguments = []
|
|
@@ -44,19 +61,6 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
|
44
61
|
}
|
|
45
62
|
this.analyze(pathArguments)
|
|
46
63
|
})
|
|
47
|
-
|
|
48
|
-
this.exclusionList = [
|
|
49
|
-
path.join('node_modules', 'send') + path.sep
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
this.internalExclusionList = [
|
|
53
|
-
'node:fs',
|
|
54
|
-
'node:internal/fs',
|
|
55
|
-
'node:internal\\fs',
|
|
56
|
-
'fs.js',
|
|
57
|
-
'internal/fs',
|
|
58
|
-
'internal\\fs'
|
|
59
|
-
]
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
_isExcluded (location) {
|
|
@@ -13,6 +13,9 @@ const EXCLUDED_PATHS = getNodeModulesPaths('mysql2', 'sequelize')
|
|
|
13
13
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
14
14
|
constructor () {
|
|
15
15
|
super(SQL_INJECTION)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
onConfigure () {
|
|
16
19
|
this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
|
|
17
20
|
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
|
|
18
21
|
this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, 'POSTGRES'))
|
|
@@ -41,10 +44,9 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
41
44
|
|
|
42
45
|
analyze (value, dialect) {
|
|
43
46
|
const store = storage.getStore()
|
|
44
|
-
|
|
45
47
|
if (!(store && store.sqlAnalyzed)) {
|
|
46
48
|
const iastContext = getIastContext(store)
|
|
47
|
-
if (store
|
|
49
|
+
if (this._isInvalidContext(store, iastContext)) return
|
|
48
50
|
this._reportIfVulnerable(value, iastContext, dialect)
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -66,6 +68,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
66
68
|
addVulnerability(context, vulnerability)
|
|
67
69
|
}
|
|
68
70
|
}
|
|
71
|
+
|
|
69
72
|
_getExcludedPaths () {
|
|
70
73
|
return EXCLUDED_PATHS
|
|
71
74
|
}
|
|
@@ -6,7 +6,9 @@ const { SSRF } = require('../vulnerabilities')
|
|
|
6
6
|
class SSRFAnalyzer extends InjectionAnalyzer {
|
|
7
7
|
constructor () {
|
|
8
8
|
super(SSRF)
|
|
9
|
+
}
|
|
9
10
|
|
|
11
|
+
onConfigure () {
|
|
10
12
|
this.addSub('apm:http:client:request:start', ({ args }) => {
|
|
11
13
|
if (typeof args.originalUrl === 'string') {
|
|
12
14
|
this.analyze(args.originalUrl)
|
|
@@ -4,14 +4,16 @@ const InjectionAnalyzer = require('./injection-analyzer')
|
|
|
4
4
|
const { UNVALIDATED_REDIRECT } = require('../vulnerabilities')
|
|
5
5
|
const { getNodeModulesPaths } = require('../path-line')
|
|
6
6
|
const { getRanges } = require('../taint-tracking/operations')
|
|
7
|
-
const { HTTP_REQUEST_HEADER_VALUE } = require('../taint-tracking/
|
|
7
|
+
const { HTTP_REQUEST_HEADER_VALUE } = require('../taint-tracking/source-types')
|
|
8
8
|
|
|
9
9
|
const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
|
|
10
10
|
|
|
11
11
|
class UnvalidatedRedirectAnalyzer extends InjectionAnalyzer {
|
|
12
12
|
constructor () {
|
|
13
13
|
super(UNVALIDATED_REDIRECT)
|
|
14
|
+
}
|
|
14
15
|
|
|
16
|
+
onConfigure () {
|
|
15
17
|
this.addSub('datadog:http:server:response:set-header:finish', ({ name, value }) => this.analyze(name, value))
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -1,33 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const Plugin = require('../../../../src/plugins/plugin')
|
|
4
3
|
const { storage } = require('../../../../../datadog-core')
|
|
5
|
-
const iastLog = require('../iast-log')
|
|
6
4
|
const { getFirstNonDDPathAndLine } = require('../path-line')
|
|
7
5
|
const { addVulnerability } = require('../vulnerability-reporter')
|
|
8
6
|
const { getIastContext } = require('../iast-context')
|
|
9
7
|
const overheadController = require('../overhead-controller')
|
|
8
|
+
const { SinkIastPlugin } = require('../iast-plugin')
|
|
10
9
|
|
|
11
|
-
class Analyzer extends
|
|
10
|
+
class Analyzer extends SinkIastPlugin {
|
|
12
11
|
constructor (type) {
|
|
13
12
|
super()
|
|
14
13
|
this._type = type
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
_wrapHandler (handler) {
|
|
18
|
-
return (message, name) => {
|
|
19
|
-
try {
|
|
20
|
-
handler(message, name)
|
|
21
|
-
} catch (e) {
|
|
22
|
-
iastLog.errorAndPublish(e)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
addSub (channelName, handler) {
|
|
28
|
-
super.addSub(channelName, this._wrapHandler(handler))
|
|
29
|
-
}
|
|
30
|
-
|
|
31
16
|
_isVulnerable (value, context) {
|
|
32
17
|
return false
|
|
33
18
|
}
|
|
@@ -64,17 +49,23 @@ class Analyzer extends Plugin {
|
|
|
64
49
|
|
|
65
50
|
_getExcludedPaths () {}
|
|
66
51
|
|
|
52
|
+
_isInvalidContext (store, iastContext) {
|
|
53
|
+
return store && !iastContext
|
|
54
|
+
}
|
|
55
|
+
|
|
67
56
|
analyze (value) {
|
|
68
57
|
const store = storage.getStore()
|
|
69
58
|
const iastContext = getIastContext(store)
|
|
70
|
-
if (store
|
|
59
|
+
if (this._isInvalidContext(store, iastContext)) return
|
|
60
|
+
|
|
71
61
|
this._reportIfVulnerable(value, iastContext)
|
|
72
62
|
}
|
|
73
63
|
|
|
74
64
|
analyzeAll (...values) {
|
|
75
65
|
const store = storage.getStore()
|
|
76
66
|
const iastContext = getIastContext(store)
|
|
77
|
-
if (store
|
|
67
|
+
if (this._isInvalidContext(store, iastContext)) return
|
|
68
|
+
|
|
78
69
|
for (let i = 0; i < values.length; i++) {
|
|
79
70
|
const value = values[i]
|
|
80
71
|
if (this._isVulnerable(value, iastContext)) {
|
|
@@ -119,6 +110,14 @@ class Analyzer extends Plugin {
|
|
|
119
110
|
}
|
|
120
111
|
return hash
|
|
121
112
|
}
|
|
113
|
+
|
|
114
|
+
addSub (iastSubOrChannelName, handler) {
|
|
115
|
+
const iastSub = typeof iastSubOrChannelName === 'string'
|
|
116
|
+
? { channelName: iastSubOrChannelName }
|
|
117
|
+
: iastSubOrChannelName
|
|
118
|
+
|
|
119
|
+
super.addSub({ tag: this._type, ...iastSub }, handler)
|
|
120
|
+
}
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
module.exports = Analyzer
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { channel } = require('../../../../diagnostics_channel')
|
|
4
|
+
|
|
5
|
+
const iastLog = require('./iast-log')
|
|
6
|
+
const Plugin = require('../../plugins/plugin')
|
|
7
|
+
const iastTelemetry = require('./telemetry')
|
|
8
|
+
const { getInstrumentedMetric, getExecutedMetric, TagKey, EXECUTED_SOURCE } = require('./telemetry/iast-metric')
|
|
9
|
+
const { storage } = require('../../../../datadog-core')
|
|
10
|
+
const { getIastContext } = require('./iast-context')
|
|
11
|
+
const instrumentations = require('../../../../datadog-instrumentations/src/helpers/instrumentations')
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Used by vulnerability sources and sinks to subscribe diagnostic channel events
|
|
15
|
+
* and indicate what kind of metrics the subscription provides
|
|
16
|
+
* - moduleName is used identify when a module is loaded and
|
|
17
|
+
* to increment the INSTRUMENTED_[SINK|SOURCE] metric when it occurs
|
|
18
|
+
* - channelName is the channel used by the hook to publish execution events
|
|
19
|
+
* - tag indicates the name of the metric: taint-tracking/source-types for Sources and analyzers type for Sinks
|
|
20
|
+
* - tagKey can be only SOURCE_TYPE (Source) or VULNERABILITY_TYPE (Sink)
|
|
21
|
+
*/
|
|
22
|
+
class IastPluginSubscription {
|
|
23
|
+
constructor (moduleName, channelName, tag, tagKey = TagKey.VULNERABILITY_TYPE) {
|
|
24
|
+
this.moduleName = moduleName
|
|
25
|
+
this.channelName = channelName
|
|
26
|
+
this.tag = tag
|
|
27
|
+
this.tagKey = tagKey
|
|
28
|
+
this.executedMetric = getExecutedMetric(this.tagKey)
|
|
29
|
+
this.instrumentedMetric = getInstrumentedMetric(this.tagKey)
|
|
30
|
+
this.moduleInstrumented = false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
increaseInstrumented () {
|
|
34
|
+
if (this.moduleInstrumented) return
|
|
35
|
+
|
|
36
|
+
this.moduleInstrumented = true
|
|
37
|
+
this.instrumentedMetric.inc(this.tag)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
increaseExecuted (iastContext) {
|
|
41
|
+
this.executedMetric.inc(this.tag, iastContext)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
matchesModuleInstrumented (name) {
|
|
45
|
+
// https module is a special case because it's events are published as http
|
|
46
|
+
name = name === 'https' ? 'http' : name
|
|
47
|
+
return this.moduleName === name
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class IastPlugin extends Plugin {
|
|
52
|
+
constructor () {
|
|
53
|
+
super()
|
|
54
|
+
this.configured = false
|
|
55
|
+
this.pluginSubs = []
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_wrapHandler (handler) {
|
|
59
|
+
return (message, name) => {
|
|
60
|
+
try {
|
|
61
|
+
handler(message, name)
|
|
62
|
+
} catch (e) {
|
|
63
|
+
iastLog.errorAndPublish(e)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_getTelemetryHandler (iastSub) {
|
|
69
|
+
return () => {
|
|
70
|
+
try {
|
|
71
|
+
const iastContext = getIastContext(storage.getStore())
|
|
72
|
+
iastSub.increaseExecuted(iastContext)
|
|
73
|
+
} catch (e) {
|
|
74
|
+
iastLog.errorAndPublish(e)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_execHandlerAndIncMetric ({ handler, metric, tag, iastContext = getIastContext(storage.getStore()) }) {
|
|
80
|
+
try {
|
|
81
|
+
const result = handler()
|
|
82
|
+
iastTelemetry.isEnabled() && metric.inc(tag, iastContext)
|
|
83
|
+
return result
|
|
84
|
+
} catch (e) {
|
|
85
|
+
iastLog.errorAndPublish(e)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
addSub (iastSub, handler) {
|
|
90
|
+
if (typeof iastSub === 'string') {
|
|
91
|
+
super.addSub(iastSub, this._wrapHandler(handler))
|
|
92
|
+
} else {
|
|
93
|
+
iastSub = this._getAndRegisterSubscription(iastSub)
|
|
94
|
+
if (iastSub) {
|
|
95
|
+
super.addSub(iastSub.channelName, this._wrapHandler(handler))
|
|
96
|
+
|
|
97
|
+
if (iastTelemetry.isEnabled()) {
|
|
98
|
+
super.addSub(iastSub.channelName, this._getTelemetryHandler(iastSub))
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onConfigure () {}
|
|
105
|
+
|
|
106
|
+
configure (config) {
|
|
107
|
+
if (typeof config !== 'object') {
|
|
108
|
+
config = { enabled: config }
|
|
109
|
+
}
|
|
110
|
+
if (config.enabled && !this.configured) {
|
|
111
|
+
this.onConfigure()
|
|
112
|
+
this.configured = true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (iastTelemetry.isEnabled()) {
|
|
116
|
+
if (config.enabled) {
|
|
117
|
+
this.enableTelemetry()
|
|
118
|
+
} else {
|
|
119
|
+
this.disableTelemetry()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
super.configure(config)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_getAndRegisterSubscription ({ moduleName, channelName, tag, tagKey }) {
|
|
127
|
+
if (!channelName && !moduleName) return
|
|
128
|
+
|
|
129
|
+
if (!moduleName) {
|
|
130
|
+
const firstSep = channelName.indexOf(':')
|
|
131
|
+
if (firstSep === -1) {
|
|
132
|
+
moduleName = channelName
|
|
133
|
+
} else {
|
|
134
|
+
const lastSep = channelName.indexOf(':', firstSep + 1)
|
|
135
|
+
moduleName = channelName.substring(firstSep + 1, lastSep !== -1 ? lastSep : channelName.length)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const iastSub = new IastPluginSubscription(moduleName, channelName, tag, tagKey)
|
|
140
|
+
this.pluginSubs.push(iastSub)
|
|
141
|
+
return iastSub
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
enableTelemetry () {
|
|
145
|
+
if (this.onInstrumentationLoadedListener) return
|
|
146
|
+
|
|
147
|
+
this.onInstrumentationLoadedListener = ({ name }) => this._onInstrumentationLoaded(name)
|
|
148
|
+
const loadChannel = channel('dd-trace:instrumentation:load')
|
|
149
|
+
loadChannel.subscribe(this.onInstrumentationLoadedListener)
|
|
150
|
+
|
|
151
|
+
// check for already instrumented modules
|
|
152
|
+
for (const name in instrumentations) {
|
|
153
|
+
this._onInstrumentationLoaded(name)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
disableTelemetry () {
|
|
158
|
+
if (!this.onInstrumentationLoadedListener) return
|
|
159
|
+
|
|
160
|
+
const loadChannel = channel('dd-trace:instrumentation:load')
|
|
161
|
+
if (loadChannel.hasSubscribers) {
|
|
162
|
+
loadChannel.unsubscribe(this.onInstrumentationLoadedListener)
|
|
163
|
+
}
|
|
164
|
+
this.onInstrumentationLoadedListener = null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_onInstrumentationLoaded (name) {
|
|
168
|
+
this.pluginSubs
|
|
169
|
+
.filter(sub => sub.matchesModuleInstrumented(name))
|
|
170
|
+
.forEach(sub => sub.increaseInstrumented())
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
class SourceIastPlugin extends IastPlugin {
|
|
175
|
+
addSub (iastPluginSub, handler) {
|
|
176
|
+
return super.addSub({ tagKey: TagKey.SOURCE_TYPE, ...iastPluginSub }, handler)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
addInstrumentedSource (moduleName, tag) {
|
|
180
|
+
this._getAndRegisterSubscription({
|
|
181
|
+
moduleName,
|
|
182
|
+
tag,
|
|
183
|
+
tagKey: TagKey.SOURCE_TYPE
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
execSource (sourceHandlerInfo) {
|
|
188
|
+
this._execHandlerAndIncMetric({
|
|
189
|
+
metric: EXECUTED_SOURCE,
|
|
190
|
+
...sourceHandlerInfo
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
class SinkIastPlugin extends IastPlugin {
|
|
196
|
+
addSub (iastPluginSub, handler) {
|
|
197
|
+
return super.addSub({ tagKey: TagKey.VULNERABILITY_TYPE, ...iastPluginSub }, handler)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
SourceIastPlugin,
|
|
203
|
+
SinkIastPlugin,
|
|
204
|
+
IastPlugin
|
|
205
|
+
}
|