dd-trace 5.36.0 → 5.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +2 -1
- package/index.d.ts +5 -0
- package/loader-hook.mjs +0 -4
- package/package.json +14 -13
- package/packages/datadog-instrumentations/src/cucumber.js +54 -1
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +103 -5
- package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
- package/packages/datadog-instrumentations/src/mysql2.js +3 -3
- package/packages/datadog-instrumentations/src/openai.js +8 -0
- package/packages/datadog-instrumentations/src/playwright.js +70 -22
- package/packages/datadog-instrumentations/src/vitest.js +60 -6
- package/packages/datadog-plugin-cucumber/src/index.js +20 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +67 -7
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
- package/packages/datadog-plugin-graphql/src/utils.js +8 -1
- package/packages/datadog-plugin-jest/src/index.js +12 -2
- package/packages/datadog-plugin-mocha/src/index.js +22 -3
- package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
- package/packages/datadog-plugin-openai/src/tracing.js +1 -2
- package/packages/datadog-plugin-playwright/src/index.js +31 -5
- package/packages/datadog-plugin-vitest/src/index.js +25 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +11 -24
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +22 -2
- package/packages/dd-trace/src/appsec/iast/index.js +2 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
- package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -4
- package/packages/dd-trace/src/appsec/rasp/lfi.js +2 -2
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -49
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
- package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
- package/packages/dd-trace/src/config.js +16 -3
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
- package/packages/dd-trace/src/iitm.js +2 -2
- package/packages/dd-trace/src/llmobs/tagger.js +12 -2
- package/packages/dd-trace/src/opentracing/span.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +16 -3
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
- package/packages/dd-trace/src/plugins/util/test.js +6 -4
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/ritm.js +2 -1
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/memwatch/package.json +0 -9
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const dc = require('dc-polyfill')
|
|
5
|
+
const { storage } = require('../../../../../datadog-core')
|
|
6
|
+
const shimmer = require('../../../../../datadog-shimmer')
|
|
7
|
+
const log = require('../../../log')
|
|
8
|
+
const { parse, SANITIZER_TYPE } = require('./parser')
|
|
9
|
+
const TaintTrackingOperations = require('../taint-tracking/operations')
|
|
10
|
+
const { getIastContext } = require('../iast-context')
|
|
11
|
+
const { iterateObjectStrings } = require('../utils')
|
|
12
|
+
|
|
13
|
+
// esm
|
|
14
|
+
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
15
|
+
|
|
16
|
+
// cjs
|
|
17
|
+
const moduleLoadEndChannel = dc.channel('dd-trace:moduleLoadEnd')
|
|
18
|
+
|
|
19
|
+
let controls
|
|
20
|
+
let controlsKeys
|
|
21
|
+
let hooks
|
|
22
|
+
|
|
23
|
+
function configure (iastConfig) {
|
|
24
|
+
if (!iastConfig?.securityControlsConfiguration) return
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
controls = parse(iastConfig.securityControlsConfiguration)
|
|
28
|
+
if (controls?.size > 0) {
|
|
29
|
+
hooks = new WeakSet()
|
|
30
|
+
controlsKeys = [...controls.keys()]
|
|
31
|
+
|
|
32
|
+
moduleLoadStartChannel.subscribe(onModuleLoaded)
|
|
33
|
+
moduleLoadEndChannel.subscribe(onModuleLoaded)
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
log.error('[ASM] Error configuring IAST Security Controls', e)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function onModuleLoaded (payload) {
|
|
41
|
+
if (!payload?.module || hooks?.has(payload.module)) return
|
|
42
|
+
|
|
43
|
+
const { filename, module } = payload
|
|
44
|
+
|
|
45
|
+
const controlsByFile = getControls(filename)
|
|
46
|
+
if (controlsByFile) {
|
|
47
|
+
const hook = hookModule(filename, module, controlsByFile)
|
|
48
|
+
payload.module = hook
|
|
49
|
+
hooks.add(hook)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getControls (filename) {
|
|
54
|
+
if (filename.startsWith('file://')) {
|
|
55
|
+
filename = filename.substring(7)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let key = path.isAbsolute(filename) ? path.relative(process.cwd(), filename) : filename
|
|
59
|
+
key = key.replaceAll(path.sep, path.posix.sep)
|
|
60
|
+
|
|
61
|
+
if (key.includes('node_modules')) {
|
|
62
|
+
key = controlsKeys.find(file => key.endsWith(file))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return controls.get(key)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function hookModule (filename, module, controlsByFile) {
|
|
69
|
+
try {
|
|
70
|
+
controlsByFile.forEach(({ type, method, parameters, secureMarks }) => {
|
|
71
|
+
const { target, parent, methodName } = resolve(method, module)
|
|
72
|
+
if (!target) {
|
|
73
|
+
log.error('[ASM] Unable to resolve IAST security control %s:%s', filename, method)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let wrapper
|
|
78
|
+
if (type === SANITIZER_TYPE) {
|
|
79
|
+
wrapper = wrapSanitizer(target, secureMarks)
|
|
80
|
+
} else {
|
|
81
|
+
wrapper = wrapInputValidator(target, parameters, secureMarks)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (methodName) {
|
|
85
|
+
parent[methodName] = wrapper
|
|
86
|
+
} else {
|
|
87
|
+
module = wrapper
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
} catch (e) {
|
|
91
|
+
log.error('[ASM] Error initializing IAST security control for %', filename, e)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return module
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolve (path, obj, separator = '.') {
|
|
98
|
+
if (!path) {
|
|
99
|
+
// esm module with default export
|
|
100
|
+
if (obj?.default) {
|
|
101
|
+
return { target: obj.default, parent: obj, methodName: 'default' }
|
|
102
|
+
} else {
|
|
103
|
+
return { target: obj, parent: obj }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const properties = path.split(separator)
|
|
108
|
+
|
|
109
|
+
let parent
|
|
110
|
+
let methodName
|
|
111
|
+
const target = properties.reduce((prev, curr) => {
|
|
112
|
+
parent = prev
|
|
113
|
+
methodName = curr
|
|
114
|
+
return prev?.[curr]
|
|
115
|
+
}, obj)
|
|
116
|
+
|
|
117
|
+
return { target, parent, methodName }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function wrapSanitizer (target, secureMarks) {
|
|
121
|
+
return shimmer.wrapFunction(target, orig => function () {
|
|
122
|
+
const result = orig.apply(this, arguments)
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
return addSecureMarks(result, secureMarks)
|
|
126
|
+
} catch (e) {
|
|
127
|
+
log.error('[ASM] Error adding Secure mark for sanitizer', e)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function wrapInputValidator (target, parameters, secureMarks) {
|
|
135
|
+
const allParameters = !parameters?.length
|
|
136
|
+
|
|
137
|
+
return shimmer.wrapFunction(target, orig => function () {
|
|
138
|
+
try {
|
|
139
|
+
[...arguments].forEach((arg, index) => {
|
|
140
|
+
if (allParameters || parameters.includes(index)) {
|
|
141
|
+
addSecureMarks(arg, secureMarks, false)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
} catch (e) {
|
|
145
|
+
log.error('[ASM] Error adding Secure mark for input validator', e)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return orig.apply(this, arguments)
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function addSecureMarks (value, secureMarks, createNewTainted = true) {
|
|
153
|
+
if (!value) return
|
|
154
|
+
|
|
155
|
+
const store = storage('legacy').getStore()
|
|
156
|
+
const iastContext = getIastContext(store)
|
|
157
|
+
|
|
158
|
+
if (typeof value === 'string') {
|
|
159
|
+
return TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted)
|
|
160
|
+
} else {
|
|
161
|
+
iterateObjectStrings(value, (value, levelKeys, parent, lastKey) => {
|
|
162
|
+
try {
|
|
163
|
+
const securedTainted = TaintTrackingOperations.addSecureMark(iastContext, value, secureMarks, createNewTainted)
|
|
164
|
+
if (createNewTainted) {
|
|
165
|
+
parent[lastKey] = securedTainted
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
// if it is a readonly property, do nothing
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
return value
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function disable () {
|
|
176
|
+
if (moduleLoadStartChannel.hasSubscribers) moduleLoadStartChannel.unsubscribe(onModuleLoaded)
|
|
177
|
+
if (moduleLoadEndChannel.hasSubscribers) moduleLoadEndChannel.unsubscribe(onModuleLoaded)
|
|
178
|
+
|
|
179
|
+
controls = undefined
|
|
180
|
+
controlsKeys = undefined
|
|
181
|
+
hooks = undefined
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
configure,
|
|
186
|
+
disable
|
|
187
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../../log')
|
|
4
|
+
const { getMarkFromVulnerabilityType, CUSTOM_SECURE_MARK } = require('../taint-tracking/secure-marks')
|
|
5
|
+
|
|
6
|
+
const SECURITY_CONTROL_DELIMITER = ';'
|
|
7
|
+
const SECURITY_CONTROL_FIELD_DELIMITER = ':'
|
|
8
|
+
const SECURITY_CONTROL_ELEMENT_DELIMITER = ','
|
|
9
|
+
|
|
10
|
+
const INPUT_VALIDATOR_TYPE = 'INPUT_VALIDATOR'
|
|
11
|
+
const SANITIZER_TYPE = 'SANITIZER'
|
|
12
|
+
|
|
13
|
+
const validTypes = [INPUT_VALIDATOR_TYPE, SANITIZER_TYPE]
|
|
14
|
+
|
|
15
|
+
function parse (securityControlsConfiguration) {
|
|
16
|
+
const controls = new Map()
|
|
17
|
+
|
|
18
|
+
securityControlsConfiguration?.replace(/[\r\n\t\v\f]*/g, '')
|
|
19
|
+
.split(SECURITY_CONTROL_DELIMITER)
|
|
20
|
+
.map(parseControl)
|
|
21
|
+
.filter(control => !!control)
|
|
22
|
+
.forEach(control => {
|
|
23
|
+
if (!controls.has(control.file)) {
|
|
24
|
+
controls.set(control.file, [])
|
|
25
|
+
}
|
|
26
|
+
controls.get(control.file).push(control)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return controls
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseControl (control) {
|
|
33
|
+
if (!control) return
|
|
34
|
+
|
|
35
|
+
const fields = control.split(SECURITY_CONTROL_FIELD_DELIMITER)
|
|
36
|
+
|
|
37
|
+
if (fields.length < 3 || fields.length > 5) {
|
|
38
|
+
log.warn('[ASM] Security control configuration is invalid: %s', control)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let [type, marks, file, method, parameters] = fields
|
|
43
|
+
|
|
44
|
+
type = type.trim().toUpperCase()
|
|
45
|
+
if (!validTypes.includes(type)) {
|
|
46
|
+
log.warn('[ASM] Invalid security control type: %s', type)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let secureMarks = CUSTOM_SECURE_MARK
|
|
51
|
+
getSecureMarks(marks).forEach(mark => { secureMarks |= mark })
|
|
52
|
+
if (secureMarks === CUSTOM_SECURE_MARK) {
|
|
53
|
+
log.warn('[ASM] Invalid security control mark: %s', marks)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
file = file?.trim()
|
|
58
|
+
|
|
59
|
+
method = method?.trim()
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
parameters = getParameters(parameters)
|
|
63
|
+
} catch (e) {
|
|
64
|
+
log.warn('[ASM] Invalid non-numeric security control parameter %s', parameters)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { type, secureMarks, file, method, parameters }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getSecureMarks (marks) {
|
|
72
|
+
return marks?.split(SECURITY_CONTROL_ELEMENT_DELIMITER)
|
|
73
|
+
.map(getMarkFromVulnerabilityType)
|
|
74
|
+
.filter(mark => !!mark)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getParameters (parameters) {
|
|
78
|
+
return parameters?.split(SECURITY_CONTROL_ELEMENT_DELIMITER)
|
|
79
|
+
.map(param => {
|
|
80
|
+
const parsedParam = parseInt(param, 10)
|
|
81
|
+
|
|
82
|
+
// discard the securityControl if there is an incorrect parameter
|
|
83
|
+
if (isNaN(parsedParam)) {
|
|
84
|
+
throw new Error('Invalid non-numeric security control parameter')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return parsedParam
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
parse,
|
|
93
|
+
|
|
94
|
+
INPUT_VALIDATOR_TYPE,
|
|
95
|
+
SANITIZER_TYPE
|
|
96
|
+
}
|
|
@@ -84,10 +84,10 @@ function getRanges (iastContext, string) {
|
|
|
84
84
|
return result
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
function addSecureMark (iastContext, string, mark) {
|
|
87
|
+
function addSecureMark (iastContext, string, mark, createNewTainted = true) {
|
|
88
88
|
const transactionId = iastContext?.[IAST_TRANSACTION_ID]
|
|
89
89
|
if (transactionId) {
|
|
90
|
-
return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark)
|
|
90
|
+
return TaintedUtils.addSecureMarksToTaintedString(transactionId, string, mark, createNewTainted)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
return string
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const vulnerabilities = require('../vulnerabilities')
|
|
4
|
+
const { getNextSecureMark } = require('./secure-marks-generator')
|
|
5
|
+
|
|
6
|
+
const marks = {}
|
|
7
|
+
Object.keys(vulnerabilities).forEach(vulnerability => {
|
|
8
|
+
marks[vulnerability + '_MARK'] = getNextSecureMark()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
let asterisk = 0x0
|
|
12
|
+
Object.values(marks).forEach(mark => { asterisk |= mark })
|
|
13
|
+
|
|
14
|
+
marks.ASTERISK_MARK = asterisk
|
|
15
|
+
marks.CUSTOM_SECURE_MARK = getNextSecureMark()
|
|
16
|
+
|
|
17
|
+
function getMarkFromVulnerabilityType (vulnerabilityType) {
|
|
18
|
+
vulnerabilityType = vulnerabilityType?.trim()
|
|
19
|
+
const mark = vulnerabilityType === '*' ? 'ASTERISK_MARK' : vulnerabilityType + '_MARK'
|
|
20
|
+
return marks[mark]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
...marks,
|
|
25
|
+
getMarkFromVulnerabilityType,
|
|
26
|
+
|
|
27
|
+
ALL: marks
|
|
28
|
+
}
|
|
@@ -83,6 +83,9 @@ const REQUEST_TAINTED = new NoTaggedIastMetric('request.tainted', Scope.REQUEST)
|
|
|
83
83
|
const EXECUTED_PROPAGATION = new NoTaggedIastMetric('executed.propagation', Scope.REQUEST)
|
|
84
84
|
const EXECUTED_TAINTED = new NoTaggedIastMetric('executed.tainted', Scope.REQUEST)
|
|
85
85
|
|
|
86
|
+
const SUPPRESSED_VULNERABILITIES = new IastMetric('suppressed.vulnerabilities', Scope.REQUEST,
|
|
87
|
+
TagKey.VULNERABILITY_TYPE)
|
|
88
|
+
|
|
86
89
|
module.exports = {
|
|
87
90
|
INSTRUMENTED_PROPAGATION,
|
|
88
91
|
INSTRUMENTED_SOURCE,
|
|
@@ -95,6 +98,8 @@ module.exports = {
|
|
|
95
98
|
|
|
96
99
|
REQUEST_TAINTED,
|
|
97
100
|
|
|
101
|
+
SUPPRESSED_VULNERABILITIES,
|
|
102
|
+
|
|
98
103
|
PropagationType,
|
|
99
104
|
TagKey,
|
|
100
105
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function iterateObjectStrings (target, fn, levelKeys = [], depth = 20, visited = new Set()) {
|
|
4
|
+
if (target !== null && typeof target === 'object') {
|
|
5
|
+
if (visited.has(target)) return
|
|
6
|
+
|
|
7
|
+
visited.add(target)
|
|
8
|
+
|
|
9
|
+
Object.keys(target).forEach((key) => {
|
|
10
|
+
const nextLevelKeys = [...levelKeys, key]
|
|
11
|
+
const val = target[key]
|
|
12
|
+
|
|
13
|
+
if (typeof val === 'string') {
|
|
14
|
+
fn(val, nextLevelKeys, target, key)
|
|
15
|
+
} else if (depth > 0) {
|
|
16
|
+
iterateObjectStrings(val, fn, nextLevelKeys, depth - 1, visited)
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
iterateObjectStrings
|
|
24
|
+
}
|
|
@@ -81,21 +81,16 @@ class VulnerabilityFormatter {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
formatVulnerability (vulnerability, sourcesIndexes, sources) {
|
|
84
|
+
const { type, hash, stackId, evidence, location } = vulnerability
|
|
85
|
+
|
|
84
86
|
const formattedVulnerability = {
|
|
85
|
-
type
|
|
86
|
-
hash
|
|
87
|
-
stackId
|
|
88
|
-
evidence: this.formatEvidence(
|
|
89
|
-
location
|
|
90
|
-
spanId: vulnerability.location.spanId
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (vulnerability.location.path) {
|
|
94
|
-
formattedVulnerability.location.path = vulnerability.location.path
|
|
95
|
-
}
|
|
96
|
-
if (vulnerability.location.line) {
|
|
97
|
-
formattedVulnerability.location.line = vulnerability.location.line
|
|
87
|
+
type,
|
|
88
|
+
hash,
|
|
89
|
+
stackId,
|
|
90
|
+
evidence: this.formatEvidence(type, evidence, sourcesIndexes, sources),
|
|
91
|
+
location
|
|
98
92
|
}
|
|
93
|
+
|
|
99
94
|
return formattedVulnerability
|
|
100
95
|
}
|
|
101
96
|
|
|
@@ -31,20 +31,20 @@ function analyzeCommandInjection ({ file, fileArgs, shell, abortController }) {
|
|
|
31
31
|
const req = store?.req
|
|
32
32
|
if (!req) return
|
|
33
33
|
|
|
34
|
-
const
|
|
34
|
+
const ephemeral = {}
|
|
35
35
|
const raspRule = { type: RULE_TYPES.COMMAND_INJECTION }
|
|
36
36
|
const params = fileArgs ? [file, ...fileArgs] : file
|
|
37
37
|
|
|
38
38
|
if (shell) {
|
|
39
|
-
|
|
39
|
+
ephemeral[addresses.SHELL_COMMAND] = params
|
|
40
40
|
raspRule.variant = 'shell'
|
|
41
41
|
} else {
|
|
42
42
|
const commandParams = Array.isArray(params) ? params : [params]
|
|
43
|
-
|
|
43
|
+
ephemeral[addresses.EXEC_COMMAND] = commandParams
|
|
44
44
|
raspRule.variant = 'exec'
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const result = waf.run({
|
|
47
|
+
const result = waf.run({ ephemeral }, req, raspRule)
|
|
48
48
|
|
|
49
49
|
const res = store?.res
|
|
50
50
|
handleResult(result, req, res, abortController, config)
|
|
@@ -54,13 +54,13 @@ function analyzeLfi (ctx) {
|
|
|
54
54
|
if (!req || !fs) return
|
|
55
55
|
|
|
56
56
|
getPaths(ctx, fs).forEach(path => {
|
|
57
|
-
const
|
|
57
|
+
const ephemeral = {
|
|
58
58
|
[FS_OPERATION_PATH]: path
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const raspRule = { type: RULE_TYPES.LFI }
|
|
62
62
|
|
|
63
|
-
const result = waf.run({
|
|
63
|
+
const result = waf.run({ ephemeral }, req, raspRule)
|
|
64
64
|
handleResult(result, req, res, ctx.abortController, config)
|
|
65
65
|
})
|
|
66
66
|
}
|
|
@@ -67,14 +67,14 @@ function analyzeSqlInjection (query, dbSystem, abortController) {
|
|
|
67
67
|
}
|
|
68
68
|
executedQueries.add(query)
|
|
69
69
|
|
|
70
|
-
const
|
|
70
|
+
const ephemeral = {
|
|
71
71
|
[addresses.DB_STATEMENT]: query,
|
|
72
72
|
[addresses.DB_SYSTEM]: dbSystem
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const raspRule = { type: RULE_TYPES.SQL_INJECTION }
|
|
76
76
|
|
|
77
|
-
const result = waf.run({
|
|
77
|
+
const result = waf.run({ ephemeral }, req, raspRule)
|
|
78
78
|
|
|
79
79
|
handleResult(result, req, res, abortController, config)
|
|
80
80
|
}
|
|
@@ -25,13 +25,13 @@ function analyzeSsrf (ctx) {
|
|
|
25
25
|
|
|
26
26
|
if (!req || !outgoingUrl) return
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const ephemeral = {
|
|
29
29
|
[addresses.HTTP_OUTGOING_URL]: outgoingUrl
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const raspRule = { type: RULE_TYPES.SSRF }
|
|
33
33
|
|
|
34
|
-
const result = waf.run({
|
|
34
|
+
const result = waf.run({ ephemeral }, req, raspRule)
|
|
35
35
|
|
|
36
36
|
const res = store?.res
|
|
37
37
|
handleResult(result, req, res, ctx.abortController, config)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
const {
|
|
4
4
|
workerData: {
|
|
5
5
|
breakpointSetChannel,
|
|
@@ -8,10 +8,11 @@ const {
|
|
|
8
8
|
}
|
|
9
9
|
} = require('worker_threads')
|
|
10
10
|
const { randomUUID } = require('crypto')
|
|
11
|
-
const sourceMap = require('source-map')
|
|
12
11
|
|
|
13
12
|
// TODO: move debugger/devtools_client/session to common place
|
|
14
13
|
const session = require('../../../debugger/devtools_client/session')
|
|
14
|
+
// TODO: move debugger/devtools_client/source-maps to common place
|
|
15
|
+
const { getGeneratedPosition } = require('../../../debugger/devtools_client/source-maps')
|
|
15
16
|
// TODO: move debugger/devtools_client/snapshot to common place
|
|
16
17
|
const { getLocalStateForCallFrame } = require('../../../debugger/devtools_client/snapshot')
|
|
17
18
|
// TODO: move debugger/devtools_client/state to common place
|
|
@@ -98,17 +99,23 @@ async function addBreakpoint (probe) {
|
|
|
98
99
|
throw new Error(`No loaded script found for ${file}`)
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
const
|
|
102
|
+
const { url, scriptId, sourceMapURL, source } = script
|
|
102
103
|
|
|
103
|
-
log.warn(`Adding breakpoint at ${
|
|
104
|
+
log.warn(`Adding breakpoint at ${url}:${line}`)
|
|
104
105
|
|
|
105
106
|
let lineNumber = line
|
|
107
|
+
let columnNumber = 0
|
|
106
108
|
|
|
107
|
-
if (sourceMapURL
|
|
109
|
+
if (sourceMapURL) {
|
|
108
110
|
try {
|
|
109
|
-
lineNumber = await
|
|
111
|
+
({ line: lineNumber, column: columnNumber } = await getGeneratedPosition(url, source, line, sourceMapURL))
|
|
110
112
|
} catch (err) {
|
|
111
|
-
log.error('Error processing script with
|
|
113
|
+
log.error('Error processing script with source map', err)
|
|
114
|
+
}
|
|
115
|
+
if (lineNumber === null) {
|
|
116
|
+
log.error('Could not find generated position for %s:%s', url, line)
|
|
117
|
+
lineNumber = line
|
|
118
|
+
columnNumber = 0
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -116,14 +123,15 @@ async function addBreakpoint (probe) {
|
|
|
116
123
|
const { breakpointId } = await session.post('Debugger.setBreakpoint', {
|
|
117
124
|
location: {
|
|
118
125
|
scriptId,
|
|
119
|
-
lineNumber: lineNumber - 1
|
|
126
|
+
lineNumber: lineNumber - 1,
|
|
127
|
+
columnNumber
|
|
120
128
|
}
|
|
121
129
|
})
|
|
122
130
|
|
|
123
131
|
breakpointIdToProbe.set(breakpointId, probe)
|
|
124
132
|
probeIdToBreakpointId.set(probe.id, breakpointId)
|
|
125
133
|
} catch (e) {
|
|
126
|
-
log.error(
|
|
134
|
+
log.error('Error setting breakpoint at %s:%s', url, line, e)
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
|
|
@@ -131,43 +139,3 @@ function start () {
|
|
|
131
139
|
sessionStarted = true
|
|
132
140
|
return session.post('Debugger.enable') // return instead of await to reduce number of promises created
|
|
133
141
|
}
|
|
134
|
-
|
|
135
|
-
async function processScriptWithInlineSourceMap (params) {
|
|
136
|
-
const { file, line, sourceMapURL } = params
|
|
137
|
-
|
|
138
|
-
// Extract the base64-encoded source map
|
|
139
|
-
const base64SourceMap = sourceMapURL.split('base64,')[1]
|
|
140
|
-
|
|
141
|
-
// Decode the base64 source map
|
|
142
|
-
const decodedSourceMap = Buffer.from(base64SourceMap, 'base64').toString('utf8')
|
|
143
|
-
|
|
144
|
-
// Parse the source map
|
|
145
|
-
const consumer = await new sourceMap.SourceMapConsumer(decodedSourceMap)
|
|
146
|
-
|
|
147
|
-
let generatedPosition
|
|
148
|
-
|
|
149
|
-
// Map to the generated position. We'll attempt with the full file path first, then with the basename.
|
|
150
|
-
// TODO: figure out why sometimes the full path doesn't work
|
|
151
|
-
generatedPosition = consumer.generatedPositionFor({
|
|
152
|
-
source: file,
|
|
153
|
-
line,
|
|
154
|
-
column: 0
|
|
155
|
-
})
|
|
156
|
-
if (generatedPosition.line === null) {
|
|
157
|
-
generatedPosition = consumer.generatedPositionFor({
|
|
158
|
-
source: path.basename(file),
|
|
159
|
-
line,
|
|
160
|
-
column: 0
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
consumer.destroy()
|
|
165
|
-
|
|
166
|
-
// If we can't find the line, just return the original line
|
|
167
|
-
if (generatedPosition.line === null) {
|
|
168
|
-
log.error(`Could not find generated position for ${file}:${line}`)
|
|
169
|
-
return line
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return generatedPosition.line
|
|
173
|
-
}
|
|
@@ -6,6 +6,7 @@ const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata'
|
|
|
6
6
|
const { getLibraryConfiguration: getLibraryConfigurationRequest } = require('../requests/get-library-configuration')
|
|
7
7
|
const { getSkippableSuites: getSkippableSuitesRequest } = require('../intelligent-test-runner/get-skippable-suites')
|
|
8
8
|
const { getKnownTests: getKnownTestsRequest } = require('../early-flake-detection/get-known-tests')
|
|
9
|
+
const { getQuarantinedTests: getQuarantinedTestsRequest } = require('../quarantined-tests/get-quarantined-tests')
|
|
9
10
|
const log = require('../../log')
|
|
10
11
|
const AgentInfoExporter = require('../../exporters/common/agent-info-exporter')
|
|
11
12
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags')
|
|
@@ -92,6 +93,14 @@ class CiVisibilityExporter extends AgentInfoExporter {
|
|
|
92
93
|
)
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
shouldRequestQuarantinedTests () {
|
|
97
|
+
return !!(
|
|
98
|
+
this._canUseCiVisProtocol &&
|
|
99
|
+
this._config.isTestManagementEnabled &&
|
|
100
|
+
this._libraryConfig?.isQuarantinedTestsEnabled
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
95
104
|
shouldRequestLibraryConfiguration () {
|
|
96
105
|
return this._config.isIntelligentTestRunnerEnabled
|
|
97
106
|
}
|
|
@@ -138,6 +147,13 @@ class CiVisibilityExporter extends AgentInfoExporter {
|
|
|
138
147
|
getKnownTestsRequest(this.getRequestConfiguration(testConfiguration), callback)
|
|
139
148
|
}
|
|
140
149
|
|
|
150
|
+
getQuarantinedTests (testConfiguration, callback) {
|
|
151
|
+
if (!this.shouldRequestQuarantinedTests()) {
|
|
152
|
+
return callback(null)
|
|
153
|
+
}
|
|
154
|
+
getQuarantinedTestsRequest(this.getRequestConfiguration(testConfiguration), callback)
|
|
155
|
+
}
|
|
156
|
+
|
|
141
157
|
/**
|
|
142
158
|
* We can't request library configuration until we know whether we can use the
|
|
143
159
|
* CI Visibility Protocol, hence the this._canUseCiVisProtocol promise.
|
|
@@ -197,7 +213,8 @@ class CiVisibilityExporter extends AgentInfoExporter {
|
|
|
197
213
|
earlyFlakeDetectionFaultyThreshold,
|
|
198
214
|
isFlakyTestRetriesEnabled,
|
|
199
215
|
isDiEnabled,
|
|
200
|
-
isKnownTestsEnabled
|
|
216
|
+
isKnownTestsEnabled,
|
|
217
|
+
isQuarantinedTestsEnabled
|
|
201
218
|
} = remoteConfiguration
|
|
202
219
|
return {
|
|
203
220
|
isCodeCoverageEnabled,
|
|
@@ -210,7 +227,8 @@ class CiVisibilityExporter extends AgentInfoExporter {
|
|
|
210
227
|
isFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled && this._config.isFlakyTestRetriesEnabled,
|
|
211
228
|
flakyTestRetriesCount: this._config.flakyTestRetriesCount,
|
|
212
229
|
isDiEnabled: isDiEnabled && this._config.isTestDynamicInstrumentationEnabled,
|
|
213
|
-
isKnownTestsEnabled
|
|
230
|
+
isKnownTestsEnabled,
|
|
231
|
+
isQuarantinedTestsEnabled: isQuarantinedTestsEnabled && this._config.isTestManagementEnabled
|
|
214
232
|
}
|
|
215
233
|
}
|
|
216
234
|
|