dd-trace 4.16.0 → 4.18.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 -0
- package/index.d.ts +9 -1
- package/package.json +5 -4
- package/packages/datadog-instrumentations/src/body-parser.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +29 -4
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
- package/packages/datadog-instrumentations/src/express.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/jest.js +58 -20
- package/packages/datadog-instrumentations/src/knex.js +69 -1
- package/packages/datadog-instrumentations/src/mocha.js +34 -4
- package/packages/datadog-instrumentations/src/mongodb.js +63 -0
- package/packages/datadog-instrumentations/src/mongoose.js +140 -1
- package/packages/datadog-instrumentations/src/next.js +98 -23
- package/packages/datadog-instrumentations/src/playwright.js +22 -8
- package/packages/datadog-plugin-cucumber/src/index.js +17 -5
- package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
- package/packages/datadog-plugin-http/src/client.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +29 -6
- package/packages/datadog-plugin-jest/src/util.js +45 -2
- package/packages/datadog-plugin-memcached/src/index.js +10 -5
- package/packages/datadog-plugin-mocha/src/index.js +25 -6
- package/packages/datadog-plugin-next/src/index.js +4 -3
- package/packages/datadog-plugin-playwright/src/index.js +4 -1
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +173 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
- package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +22 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +15 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
- package/packages/dd-trace/src/appsec/index.js +31 -13
- package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +14 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
- package/packages/dd-trace/src/config.js +37 -13
- package/packages/dd-trace/src/format.js +3 -0
- package/packages/dd-trace/src/git_properties.js +16 -15
- package/packages/dd-trace/src/plugin_manager.js +3 -1
- package/packages/dd-trace/src/plugins/util/ci.js +17 -0
- package/packages/dd-trace/src/plugins/util/git.js +26 -4
- package/packages/dd-trace/src/plugins/util/test.js +45 -2
- package/packages/dd-trace/src/profiling/config.js +20 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +51 -40
- package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
- package/packages/dd-trace/src/telemetry/index.js +4 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
- package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
- package/packages/dd-trace/src/telemetry/metrics.js +0 -5
- package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { NOSQL_MONGODB_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
const { getRanges, addSecureMark } = require('../taint-tracking/operations')
|
|
6
|
+
const { getNodeModulesPaths } = require('../path-line')
|
|
7
|
+
const { getNextSecureMark } = require('../taint-tracking/secure-marks-generator')
|
|
8
|
+
const { storage } = require('../../../../../datadog-core')
|
|
9
|
+
const { getIastContext } = require('../iast-context')
|
|
10
|
+
const { HTTP_REQUEST_PARAMETER, HTTP_REQUEST_BODY } = require('../taint-tracking/source-types')
|
|
11
|
+
|
|
12
|
+
const EXCLUDED_PATHS_FROM_STACK = getNodeModulesPaths('mongodb', 'mongoose')
|
|
13
|
+
const MONGODB_NOSQL_SECURE_MARK = getNextSecureMark()
|
|
14
|
+
|
|
15
|
+
function iterateObjectStrings (target, fn, levelKeys = [], depth = 50, visited = new Set()) {
|
|
16
|
+
if (target && typeof target === 'object') {
|
|
17
|
+
Object.keys(target).forEach((key) => {
|
|
18
|
+
const nextLevelKeys = [...levelKeys, key]
|
|
19
|
+
const val = target[key]
|
|
20
|
+
|
|
21
|
+
if (typeof val === 'string') {
|
|
22
|
+
fn(val, nextLevelKeys, target, key)
|
|
23
|
+
} else if (depth > 0 && !visited.has(val)) {
|
|
24
|
+
iterateObjectStrings(val, fn, nextLevelKeys, depth - 1, visited)
|
|
25
|
+
visited.add(val)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class NosqlInjectionMongodbAnalyzer extends InjectionAnalyzer {
|
|
32
|
+
constructor () {
|
|
33
|
+
super(NOSQL_MONGODB_INJECTION)
|
|
34
|
+
this.sanitizedObjects = new WeakSet()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onConfigure () {
|
|
38
|
+
this.configureSanitizers()
|
|
39
|
+
|
|
40
|
+
this.addSub('datadog:mongodb:collection:filter:start', ({ filters }) => {
|
|
41
|
+
const store = storage.getStore()
|
|
42
|
+
if (store && !store.nosqlAnalyzed && filters?.length) {
|
|
43
|
+
filters.forEach(filter => {
|
|
44
|
+
this.analyze({ filter }, store)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
this.addSub('datadog:mongoose:model:filter:start', ({ filters }) => {
|
|
50
|
+
const store = storage.getStore()
|
|
51
|
+
if (!store) return
|
|
52
|
+
|
|
53
|
+
if (filters?.length) {
|
|
54
|
+
filters.forEach(filter => {
|
|
55
|
+
this.analyze({ filter }, store)
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
storage.enterWith({ ...store, nosqlAnalyzed: true, mongooseParentStore: store })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
this.addSub('datadog:mongoose:model:filter:finish', () => {
|
|
63
|
+
const store = storage.getStore()
|
|
64
|
+
if (store?.mongooseParentStore) {
|
|
65
|
+
storage.enterWith(store.mongooseParentStore)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
configureSanitizers () {
|
|
71
|
+
this.addNotSinkSub('datadog:express-mongo-sanitize:filter:finish', ({ sanitizedProperties, req }) => {
|
|
72
|
+
const store = storage.getStore()
|
|
73
|
+
const iastContext = getIastContext(store)
|
|
74
|
+
|
|
75
|
+
if (iastContext) { // do nothing if we are not in an iast request
|
|
76
|
+
sanitizedProperties.forEach(key => {
|
|
77
|
+
iterateObjectStrings(req[key], function (value, levelKeys) {
|
|
78
|
+
if (typeof value === 'string') {
|
|
79
|
+
let parentObj = req[key]
|
|
80
|
+
const levelsLength = levelKeys.length
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < levelsLength; i++) {
|
|
83
|
+
const currentLevelKey = levelKeys[i]
|
|
84
|
+
|
|
85
|
+
if (i === levelsLength - 1) {
|
|
86
|
+
parentObj[currentLevelKey] = addSecureMark(iastContext, value, MONGODB_NOSQL_SECURE_MARK)
|
|
87
|
+
} else {
|
|
88
|
+
parentObj = parentObj[currentLevelKey]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
this.addNotSinkSub('datadog:express-mongo-sanitize:sanitize:finish', ({ sanitizedObject }) => {
|
|
98
|
+
const store = storage.getStore()
|
|
99
|
+
const iastContext = getIastContext(store)
|
|
100
|
+
|
|
101
|
+
if (iastContext) { // do nothing if we are not in an iast request
|
|
102
|
+
iterateObjectStrings(sanitizedObject, function (value, levelKeys, parent, lastKey) {
|
|
103
|
+
try {
|
|
104
|
+
parent[lastKey] = addSecureMark(iastContext, value, MONGODB_NOSQL_SECURE_MARK)
|
|
105
|
+
} catch {
|
|
106
|
+
// if it is a readonly property, do nothing
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
this.addNotSinkSub('datadog:mongoose:sanitize-filter:finish', ({ sanitizedObject }) => {
|
|
113
|
+
this.sanitizedObjects.add(sanitizedObject)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_isVulnerableRange (range) {
|
|
118
|
+
const rangeType = range?.iinfo?.type
|
|
119
|
+
const isVulnerableType = rangeType === HTTP_REQUEST_PARAMETER || rangeType === HTTP_REQUEST_BODY
|
|
120
|
+
return isVulnerableType && (range.secureMarks & MONGODB_NOSQL_SECURE_MARK) !== MONGODB_NOSQL_SECURE_MARK
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_isVulnerable (value, iastContext) {
|
|
124
|
+
if (value?.filter && iastContext) {
|
|
125
|
+
let isVulnerable = false
|
|
126
|
+
|
|
127
|
+
if (this.sanitizedObjects.has(value.filter)) {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const rangesByKey = {}
|
|
132
|
+
const allRanges = []
|
|
133
|
+
|
|
134
|
+
iterateObjectStrings(value.filter, (val, nextLevelKeys) => {
|
|
135
|
+
const ranges = getRanges(iastContext, val)
|
|
136
|
+
if (ranges?.length) {
|
|
137
|
+
const filteredRanges = []
|
|
138
|
+
|
|
139
|
+
for (const range of ranges) {
|
|
140
|
+
if (this._isVulnerableRange(range)) {
|
|
141
|
+
isVulnerable = true
|
|
142
|
+
filteredRanges.push(range)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (filteredRanges.length > 0) {
|
|
147
|
+
rangesByKey[nextLevelKeys.join('.')] = filteredRanges
|
|
148
|
+
allRanges.push(...filteredRanges)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [], 4)
|
|
152
|
+
|
|
153
|
+
if (isVulnerable) {
|
|
154
|
+
value.rangesToApply = rangesByKey
|
|
155
|
+
value.ranges = allRanges
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return isVulnerable
|
|
159
|
+
}
|
|
160
|
+
return false
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_getEvidence (value, iastContext) {
|
|
164
|
+
return { value: value.filter, rangesToApply: value.rangesToApply, ranges: value.ranges }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_getExcludedPaths () {
|
|
168
|
+
return EXCLUDED_PATHS_FROM_STACK
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = new NosqlInjectionMongodbAnalyzer()
|
|
173
|
+
module.exports.MONGODB_NOSQL_SECURE_MARK = MONGODB_NOSQL_SECURE_MARK
|
|
@@ -8,7 +8,7 @@ const { getIastContext } = require('../iast-context')
|
|
|
8
8
|
const { addVulnerability } = require('../vulnerability-reporter')
|
|
9
9
|
const { getNodeModulesPaths } = require('../path-line')
|
|
10
10
|
|
|
11
|
-
const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool')
|
|
11
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('mysql', 'mysql2', 'sequelize', 'pg-pool', 'knex')
|
|
12
12
|
|
|
13
13
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
14
14
|
constructor () {
|
|
@@ -31,6 +31,12 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
31
31
|
|
|
32
32
|
this.addSub('datadog:mysql:pool:query:start', ({ sql }) => this.getStoreAndAnalyze(sql, 'MYSQL'))
|
|
33
33
|
this.addSub('datadog:mysql:pool:query:finish', () => this.returnToParentStore())
|
|
34
|
+
|
|
35
|
+
this.addSub('datadog:knex:raw:start', ({ sql, dialect: knexDialect }) => {
|
|
36
|
+
const dialect = this.normalizeKnexDialect(knexDialect)
|
|
37
|
+
this.getStoreAndAnalyze(sql, dialect)
|
|
38
|
+
})
|
|
39
|
+
this.addSub('datadog:knex:raw:finish', () => this.returnToParentStore())
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
getStoreAndAnalyze (query, dialect) {
|
|
@@ -83,6 +89,20 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
83
89
|
_getExcludedPaths () {
|
|
84
90
|
return EXCLUDED_PATHS
|
|
85
91
|
}
|
|
92
|
+
|
|
93
|
+
normalizeKnexDialect (knexDialect) {
|
|
94
|
+
if (knexDialect === 'postgresql') {
|
|
95
|
+
return 'POSTGRES'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (knexDialect === 'sqlite3') {
|
|
99
|
+
return 'SQLITE'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (typeof knexDialect === 'string') {
|
|
103
|
+
return knexDialect.toUpperCase()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
module.exports = new SqlInjectionAnalyzer()
|
|
@@ -6,8 +6,8 @@ const { getNodeModulesPaths } = require('../path-line')
|
|
|
6
6
|
const { getRanges } = require('../taint-tracking/operations')
|
|
7
7
|
const {
|
|
8
8
|
HTTP_REQUEST_HEADER_VALUE,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
HTTP_REQUEST_PATH_PARAM,
|
|
10
|
+
HTTP_REQUEST_URI
|
|
11
11
|
} = require('../taint-tracking/source-types')
|
|
12
12
|
|
|
13
13
|
const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
|
|
@@ -56,7 +56,7 @@ class UnvalidatedRedirectAnalyzer extends InjectionAnalyzer {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
_isUrl (range) {
|
|
59
|
-
return range.iinfo.type ===
|
|
59
|
+
return range.iinfo.type === HTTP_REQUEST_URI
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
_getExcludedPaths () {
|
|
@@ -71,8 +71,7 @@ class Analyzer extends SinkIastPlugin {
|
|
|
71
71
|
return store && !iastContext
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
analyze (value) {
|
|
75
|
-
const store = storage.getStore()
|
|
74
|
+
analyze (value, store = storage.getStore()) {
|
|
76
75
|
const iastContext = getIastContext(store)
|
|
77
76
|
if (this._isInvalidContext(store, iastContext)) return
|
|
78
77
|
|
|
@@ -11,8 +11,8 @@ class XcontenttypeHeaderMissingAnalyzer extends MissingHeaderAnalyzer {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
_isVulnerableFromRequestAndResponse (req, res) {
|
|
14
|
-
const
|
|
15
|
-
return
|
|
14
|
+
const headerValues = this._getHeaderValues(res, XCONTENTTYPEOPTIONS_HEADER_NAME)
|
|
15
|
+
return headerValues.length === 0 || headerValues.some(headerValue => headerValue.trim().toLowerCase() !== 'nosniff')
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const dc = require('../../../../diagnostics_channel')
|
|
3
4
|
const log = require('../../log')
|
|
4
|
-
const telemetryLogs = require('./telemetry/log')
|
|
5
5
|
const { calculateDDBasePath } = require('../../util')
|
|
6
6
|
|
|
7
|
+
const telemetryLog = dc.channel('datadog:telemetry:log')
|
|
8
|
+
|
|
7
9
|
const ddBasePath = calculateDDBasePath(__dirname)
|
|
8
10
|
const EOL = '\n'
|
|
9
11
|
const STACK_FRAME_LINE_REGEX = /^\s*at\s/gm
|
|
@@ -80,9 +82,8 @@ const iastLog = {
|
|
|
80
82
|
},
|
|
81
83
|
|
|
82
84
|
publish (data, level) {
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
telemetryLogs.publish(telemetryLog)
|
|
85
|
+
if (telemetryLog.hasSubscribers) {
|
|
86
|
+
telemetryLog.publish(getTelemetryLog(data, level))
|
|
86
87
|
}
|
|
87
88
|
return this
|
|
88
89
|
},
|
|
@@ -92,6 +93,10 @@ const iastLog = {
|
|
|
92
93
|
return this.publish(data, 'DEBUG')
|
|
93
94
|
},
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* forward 'INFO' log level to 'DEBUG' telemetry log level
|
|
98
|
+
* see also {@link ../../telemetry/logs#isLevelEnabled } method
|
|
99
|
+
*/
|
|
95
100
|
infoAndPublish (data) {
|
|
96
101
|
this.info(data)
|
|
97
102
|
return this.publish(data, 'DEBUG')
|
|
@@ -196,6 +196,10 @@ class SinkIastPlugin extends IastPlugin {
|
|
|
196
196
|
addSub (iastPluginSub, handler) {
|
|
197
197
|
return super.addSub({ tagKey: TagKey.VULNERABILITY_TYPE, ...iastPluginSub }, handler)
|
|
198
198
|
}
|
|
199
|
+
|
|
200
|
+
addNotSinkSub (iastPluginSub, handler) {
|
|
201
|
+
return super.addSub(iastPluginSub, handler)
|
|
202
|
+
}
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
module.exports = {
|
|
@@ -6,6 +6,7 @@ const { calculateDDBasePath } = require('../../util')
|
|
|
6
6
|
const pathLine = {
|
|
7
7
|
getFirstNonDDPathAndLine,
|
|
8
8
|
getNodeModulesPaths,
|
|
9
|
+
getRelativePath,
|
|
9
10
|
getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
|
|
10
11
|
calculateDDBasePath, // Exported only for test purposes
|
|
11
12
|
ddBasePath: calculateDDBasePath(__dirname) // Only for test purposes
|
|
@@ -45,7 +46,7 @@ function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPat
|
|
|
45
46
|
const filepath = callsite.getFileName()
|
|
46
47
|
if (!isExcluded(callsite, externallyExcludedPaths) && filepath.indexOf(pathLine.ddBasePath) === -1) {
|
|
47
48
|
return {
|
|
48
|
-
path:
|
|
49
|
+
path: getRelativePath(filepath),
|
|
49
50
|
line: callsite.getLineNumber(),
|
|
50
51
|
column: callsite.getColumnNumber(),
|
|
51
52
|
isInternal: !path.isAbsolute(filepath)
|
|
@@ -56,6 +57,10 @@ function getFirstNonDDPathAndLineFromCallsites (callsites, externallyExcludedPat
|
|
|
56
57
|
return null
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
function getRelativePath (filepath) {
|
|
61
|
+
return path.relative(process.cwd(), filepath)
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
function isExcluded (callsite, externallyExcludedPaths) {
|
|
60
65
|
if (callsite.isNative()) return true
|
|
61
66
|
const filename = callsite.getFileName()
|
|
@@ -18,15 +18,14 @@ let onRemoveTransaction = (transactionId, iastContext) => {}
|
|
|
18
18
|
|
|
19
19
|
function onRemoveTransactionInformationTelemetry (transactionId, iastContext) {
|
|
20
20
|
const metrics = TaintedUtils.getMetrics(transactionId, iastTelemetry.verbosity)
|
|
21
|
-
if (metrics
|
|
21
|
+
if (metrics?.requestCount) {
|
|
22
22
|
REQUEST_TAINTED.add(metrics.requestCount, null, iastContext)
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function removeTransaction (iastContext) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
28
|
+
if (transactionId) {
|
|
30
29
|
onRemoveTransaction(transactionId, iastContext)
|
|
31
30
|
|
|
32
31
|
TaintedUtils.removeTransaction(transactionId)
|
|
@@ -36,8 +35,8 @@ function removeTransaction (iastContext) {
|
|
|
36
35
|
|
|
37
36
|
function newTaintedString (iastContext, string, name, type) {
|
|
38
37
|
let result = string
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
39
|
+
if (transactionId) {
|
|
41
40
|
result = TaintedUtils.newTaintedString(transactionId, string, name, type)
|
|
42
41
|
} else {
|
|
43
42
|
result = string
|
|
@@ -47,15 +46,17 @@ function newTaintedString (iastContext, string, name, type) {
|
|
|
47
46
|
|
|
48
47
|
function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
49
48
|
let result = object
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
50
|
+
if (transactionId) {
|
|
52
51
|
const queue = [{ parent: null, property: null, value: object }]
|
|
53
52
|
const visited = new WeakSet()
|
|
53
|
+
|
|
54
54
|
while (queue.length > 0) {
|
|
55
55
|
const { parent, property, value, key } = queue.pop()
|
|
56
56
|
if (value === null) {
|
|
57
57
|
continue
|
|
58
58
|
}
|
|
59
|
+
|
|
59
60
|
try {
|
|
60
61
|
if (typeof value === 'string') {
|
|
61
62
|
const tainted = TaintedUtils.newTaintedString(transactionId, value, property, type)
|
|
@@ -71,11 +72,13 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
|
71
72
|
}
|
|
72
73
|
} else if (typeof value === 'object' && !visited.has(value)) {
|
|
73
74
|
visited.add(value)
|
|
75
|
+
|
|
74
76
|
const keys = Object.keys(value)
|
|
75
77
|
for (let i = 0; i < keys.length; i++) {
|
|
76
78
|
const key = keys[i]
|
|
77
79
|
queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key], key })
|
|
78
80
|
}
|
|
81
|
+
|
|
79
82
|
if (parent && keyTainting && key) {
|
|
80
83
|
const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
|
|
81
84
|
parent[taintedProperty] = value
|
|
@@ -91,8 +94,8 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
|
91
94
|
|
|
92
95
|
function isTainted (iastContext, string) {
|
|
93
96
|
let result = false
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
98
|
+
if (transactionId) {
|
|
96
99
|
result = TaintedUtils.isTainted(transactionId, string)
|
|
97
100
|
} else {
|
|
98
101
|
result = false
|
|
@@ -102,8 +105,8 @@ function isTainted (iastContext, string) {
|
|
|
102
105
|
|
|
103
106
|
function getRanges (iastContext, string) {
|
|
104
107
|
let result = []
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
109
|
+
if (transactionId) {
|
|
107
110
|
result = TaintedUtils.getRanges(transactionId, string)
|
|
108
111
|
} else {
|
|
109
112
|
result = []
|
|
@@ -111,6 +114,15 @@ function getRanges (iastContext, string) {
|
|
|
111
114
|
return result
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
function addSecureMark (iastContext, string, mark) {
|
|
118
|
+
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
119
|
+
if (transactionId) {
|
|
120
|
+
return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return string
|
|
124
|
+
}
|
|
125
|
+
|
|
114
126
|
function enableTaintOperations (telemetryVerbosity) {
|
|
115
127
|
if (isInfoAllowed(telemetryVerbosity)) {
|
|
116
128
|
onRemoveTransaction = onRemoveTransactionInformationTelemetry
|
|
@@ -132,6 +144,7 @@ function setMaxTransactions (transactions) {
|
|
|
132
144
|
}
|
|
133
145
|
|
|
134
146
|
module.exports = {
|
|
147
|
+
addSecureMark,
|
|
135
148
|
createTransaction,
|
|
136
149
|
removeTransaction,
|
|
137
150
|
newTaintedString,
|
|
@@ -11,8 +11,8 @@ const {
|
|
|
11
11
|
HTTP_REQUEST_HEADER_VALUE,
|
|
12
12
|
HTTP_REQUEST_HEADER_NAME,
|
|
13
13
|
HTTP_REQUEST_PARAMETER,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
HTTP_REQUEST_PATH_PARAM,
|
|
15
|
+
HTTP_REQUEST_URI
|
|
16
16
|
} = require('./source-types')
|
|
17
17
|
|
|
18
18
|
class TaintTrackingPlugin extends SourceIastPlugin {
|
|
@@ -93,9 +93,9 @@ class TaintTrackingPlugin extends SourceIastPlugin {
|
|
|
93
93
|
taintUrl (req, iastContext) {
|
|
94
94
|
this.execSource({
|
|
95
95
|
handler: function () {
|
|
96
|
-
req.url = newTaintedString(iastContext, req.url,
|
|
96
|
+
req.url = newTaintedString(iastContext, req.url, HTTP_REQUEST_URI, HTTP_REQUEST_URI)
|
|
97
97
|
},
|
|
98
|
-
tag: [
|
|
98
|
+
tag: [HTTP_REQUEST_URI],
|
|
99
99
|
iastContext
|
|
100
100
|
})
|
|
101
101
|
}
|
|
@@ -7,7 +7,9 @@ const { isPrivateModule, isNotLibraryFile } = require('./filter')
|
|
|
7
7
|
const { csiMethods } = require('./csi-methods')
|
|
8
8
|
const { getName } = require('../telemetry/verbosity')
|
|
9
9
|
const { getRewriteFunction } = require('./rewriter-telemetry')
|
|
10
|
+
const dc = require('../../../../../diagnostics_channel')
|
|
10
11
|
|
|
12
|
+
const hardcodedSecretCh = dc.channel('datadog:secrets:result')
|
|
11
13
|
let rewriter
|
|
12
14
|
let getPrepareStackTrace
|
|
13
15
|
|
|
@@ -50,7 +52,11 @@ function getRewriter (telemetryVerbosity) {
|
|
|
50
52
|
getGetOriginalPathAndLineFromSourceMapFunction(chainSourceMap, getOriginalPathAndLineFromSourceMap)
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
rewriter = new Rewriter({
|
|
55
|
+
rewriter = new Rewriter({
|
|
56
|
+
csiMethods,
|
|
57
|
+
telemetryVerbosity: getName(telemetryVerbosity),
|
|
58
|
+
chainSourceMap
|
|
59
|
+
})
|
|
54
60
|
} catch (e) {
|
|
55
61
|
iastLog.error('Unable to initialize TaintTracking Rewriter')
|
|
56
62
|
.errorAndPublish(e)
|
|
@@ -80,7 +86,12 @@ function getCompileMethodFn (compileMethod) {
|
|
|
80
86
|
try {
|
|
81
87
|
if (isPrivateModule(filename) && isNotLibraryFile(filename)) {
|
|
82
88
|
const rewritten = rewriteFn(content, filename)
|
|
83
|
-
|
|
89
|
+
|
|
90
|
+
if (rewritten?.literalsResult && hardcodedSecretCh.hasSubscribers) {
|
|
91
|
+
hardcodedSecretCh.publish(rewritten.literalsResult)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (rewritten?.content) {
|
|
84
95
|
return compileMethod.apply(this, [rewritten.content, filename])
|
|
85
96
|
}
|
|
86
97
|
}
|
|
@@ -8,5 +8,6 @@ module.exports = {
|
|
|
8
8
|
HTTP_REQUEST_HEADER_VALUE: 'http.request.header',
|
|
9
9
|
HTTP_REQUEST_PARAMETER: 'http.request.parameter',
|
|
10
10
|
HTTP_REQUEST_PATH: 'http.request.path',
|
|
11
|
-
HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter'
|
|
11
|
+
HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter',
|
|
12
|
+
HTTP_REQUEST_URI: 'http.request.uri'
|
|
12
13
|
}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const telemetryMetrics = require('../../../telemetry/metrics')
|
|
4
|
-
const telemetryLogs = require('./log')
|
|
5
4
|
const { Verbosity, getVerbosity } = require('./verbosity')
|
|
6
5
|
const { initRequestNamespace, finalizeRequestNamespace, globalNamespace } = require('./namespaces')
|
|
7
6
|
|
|
8
|
-
function isIastMetricsEnabled (metrics) {
|
|
9
|
-
// TODO: let DD_TELEMETRY_METRICS_ENABLED as undefined in config.js to avoid read here the env property
|
|
10
|
-
return process.env.DD_TELEMETRY_METRICS_ENABLED !== undefined ? metrics : true
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
class Telemetry {
|
|
14
8
|
configure (config, verbosity) {
|
|
15
|
-
const telemetryAndMetricsEnabled = config &&
|
|
16
|
-
config.telemetry &&
|
|
17
|
-
config.telemetry.enabled &&
|
|
18
|
-
isIastMetricsEnabled(config.telemetry.metrics)
|
|
9
|
+
const telemetryAndMetricsEnabled = config?.telemetry?.enabled && config.telemetry.metrics
|
|
19
10
|
|
|
20
11
|
this.verbosity = telemetryAndMetricsEnabled ? getVerbosity(verbosity) : Verbosity.OFF
|
|
21
12
|
this.enabled = this.verbosity !== Verbosity.OFF
|
|
@@ -23,15 +14,11 @@ class Telemetry {
|
|
|
23
14
|
if (this.enabled) {
|
|
24
15
|
telemetryMetrics.manager.set('iast', globalNamespace)
|
|
25
16
|
}
|
|
26
|
-
|
|
27
|
-
telemetryLogs.start()
|
|
28
17
|
}
|
|
29
18
|
|
|
30
19
|
stop () {
|
|
31
20
|
this.enabled = false
|
|
32
21
|
telemetryMetrics.manager.delete('iast')
|
|
33
|
-
|
|
34
|
-
telemetryLogs.stop()
|
|
35
22
|
}
|
|
36
23
|
|
|
37
24
|
isEnabled () {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { stringifyWithRanges } = require('../../utils')
|
|
4
|
+
|
|
5
|
+
class JsonSensitiveAnalyzer {
|
|
6
|
+
extractSensitiveRanges (evidence) {
|
|
7
|
+
// expect object evidence
|
|
8
|
+
const { value, ranges, sensitiveRanges } = stringifyWithRanges(evidence.value, evidence.rangesToApply, true)
|
|
9
|
+
evidence.value = value
|
|
10
|
+
evidence.ranges = ranges
|
|
11
|
+
|
|
12
|
+
return sensitiveRanges
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = JsonSensitiveAnalyzer
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const iastLog = require('../../iast-log')
|
|
3
4
|
const vulnerabilities = require('../../vulnerabilities')
|
|
4
5
|
|
|
5
6
|
const { contains, intersects, remove } = require('./range-utils')
|
|
6
7
|
|
|
7
8
|
const CommandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
|
|
9
|
+
const JsonSensitiveAnalyzer = require('./sensitive-analyzers/json-sensitive-analyzer')
|
|
8
10
|
const LdapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
|
|
9
11
|
const SqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
|
|
10
12
|
const UrlSensitiveAnalyzer = require('./sensitive-analyzers/url-sensitive-analyzer')
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)'
|
|
14
|
-
// eslint-disable-next-line max-len
|
|
15
|
-
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
|
|
14
|
+
const { DEFAULT_IAST_REDACTION_NAME_PATTERN, DEFAULT_IAST_REDACTION_VALUE_PATTERN } = require('./sensitive-regex')
|
|
16
15
|
|
|
17
16
|
const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
18
17
|
|
|
@@ -23,6 +22,7 @@ class SensitiveHandler {
|
|
|
23
22
|
|
|
24
23
|
this._sensitiveAnalyzers = new Map()
|
|
25
24
|
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
|
|
25
|
+
this._sensitiveAnalyzers.set(vulnerabilities.NOSQL_MONGODB_INJECTION, new JsonSensitiveAnalyzer())
|
|
26
26
|
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
|
|
27
27
|
this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
|
|
28
28
|
const urlSensitiveAnalyzer = new UrlSensitiveAnalyzer()
|
|
@@ -264,6 +264,24 @@ class SensitiveHandler {
|
|
|
264
264
|
valueParts.push({ redacted: true })
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
|
+
|
|
268
|
+
setRedactionPatterns (redactionNamePattern, redactionValuePattern) {
|
|
269
|
+
if (redactionNamePattern) {
|
|
270
|
+
try {
|
|
271
|
+
this._namePattern = new RegExp(redactionNamePattern, 'gmi')
|
|
272
|
+
} catch (e) {
|
|
273
|
+
iastLog.warn('Redaction name pattern is not valid')
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (redactionValuePattern) {
|
|
278
|
+
try {
|
|
279
|
+
this._valuePattern = new RegExp(redactionValuePattern, 'gmi')
|
|
280
|
+
} catch (e) {
|
|
281
|
+
iastLog.warn('Redaction value pattern is not valid')
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
267
285
|
}
|
|
268
286
|
|
|
269
287
|
module.exports = new SensitiveHandler()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// eslint-disable-next-line max-len
|
|
2
|
+
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)'
|
|
3
|
+
// eslint-disable-next-line max-len
|
|
4
|
+
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
DEFAULT_IAST_REDACTION_NAME_PATTERN,
|
|
8
|
+
DEFAULT_IAST_REDACTION_VALUE_PATTERN
|
|
9
|
+
}
|