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.
Files changed (65) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/index.d.ts +5 -0
  3. package/loader-hook.mjs +0 -4
  4. package/package.json +14 -13
  5. package/packages/datadog-instrumentations/src/cucumber.js +54 -1
  6. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  7. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  8. package/packages/datadog-instrumentations/src/jest.js +103 -5
  9. package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
  11. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
  12. package/packages/datadog-instrumentations/src/mysql2.js +3 -3
  13. package/packages/datadog-instrumentations/src/openai.js +8 -0
  14. package/packages/datadog-instrumentations/src/playwright.js +70 -22
  15. package/packages/datadog-instrumentations/src/vitest.js +60 -6
  16. package/packages/datadog-plugin-cucumber/src/index.js +20 -3
  17. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +67 -7
  18. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  19. package/packages/datadog-plugin-graphql/src/utils.js +8 -1
  20. package/packages/datadog-plugin-jest/src/index.js +12 -2
  21. package/packages/datadog-plugin-mocha/src/index.js +22 -3
  22. package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
  23. package/packages/datadog-plugin-openai/src/tracing.js +1 -2
  24. package/packages/datadog-plugin-playwright/src/index.js +31 -5
  25. package/packages/datadog-plugin-vitest/src/index.js +25 -1
  26. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -6
  27. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
  28. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +11 -24
  29. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +2 -6
  30. package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
  31. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
  32. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +22 -2
  33. package/packages/dd-trace/src/appsec/iast/index.js +2 -0
  34. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
  35. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
  36. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
  37. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
  38. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
  39. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
  40. package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
  41. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
  42. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
  43. package/packages/dd-trace/src/appsec/rasp/command_injection.js +4 -4
  44. package/packages/dd-trace/src/appsec/rasp/lfi.js +2 -2
  45. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +2 -2
  46. package/packages/dd-trace/src/appsec/rasp/ssrf.js +2 -2
  47. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +17 -49
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
  49. package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
  50. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
  51. package/packages/dd-trace/src/config.js +16 -3
  52. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  53. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
  54. package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
  55. package/packages/dd-trace/src/iitm.js +2 -2
  56. package/packages/dd-trace/src/llmobs/tagger.js +12 -2
  57. package/packages/dd-trace/src/opentracing/span.js +2 -2
  58. package/packages/dd-trace/src/plugins/ci_plugin.js +16 -3
  59. package/packages/dd-trace/src/plugins/database.js +14 -4
  60. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
  61. package/packages/dd-trace/src/plugins/util/test.js +6 -4
  62. package/packages/dd-trace/src/proxy.js +5 -1
  63. package/packages/dd-trace/src/ritm.js +2 -1
  64. package/packages/dd-trace/src/spanleak.js +0 -1
  65. 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
@@ -3,7 +3,7 @@
3
3
  let next = 0
4
4
 
5
5
  function getNextSecureMark () {
6
- return 1 << next++
6
+ return (1 << next++) >>> 0
7
7
  }
8
8
 
9
9
  function reset () {
@@ -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: vulnerability.type,
86
- hash: vulnerability.hash,
87
- stackId: vulnerability.stackId,
88
- evidence: this.formatEvidence(vulnerability.type, vulnerability.evidence, sourcesIndexes, sources),
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
 
@@ -131,6 +131,7 @@ function replaceCallSiteFromSourceMap (callsite) {
131
131
  if (line) {
132
132
  callsite.line = line
133
133
  }
134
+ // We send the column in the stack trace but not in the vulnerability location
134
135
  if (column) {
135
136
  callsite.column = column
136
137
  }
@@ -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 persistent = {}
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
- persistent[addresses.SHELL_COMMAND] = params
39
+ ephemeral[addresses.SHELL_COMMAND] = params
40
40
  raspRule.variant = 'shell'
41
41
  } else {
42
42
  const commandParams = Array.isArray(params) ? params : [params]
43
- persistent[addresses.EXEC_COMMAND] = commandParams
43
+ ephemeral[addresses.EXEC_COMMAND] = commandParams
44
44
  raspRule.variant = 'exec'
45
45
  }
46
46
 
47
- const result = waf.run({ persistent }, req, raspRule)
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 persistent = {
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({ persistent }, req, raspRule)
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 persistent = {
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({ persistent }, req, raspRule)
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 persistent = {
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({ persistent }, req, raspRule)
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
- const path = require('path')
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 [path, scriptId, sourceMapURL] = script
102
+ const { url, scriptId, sourceMapURL, source } = script
102
103
 
103
- log.warn(`Adding breakpoint at ${path}:${line}`)
104
+ log.warn(`Adding breakpoint at ${url}:${line}`)
104
105
 
105
106
  let lineNumber = line
107
+ let columnNumber = 0
106
108
 
107
- if (sourceMapURL && sourceMapURL.startsWith('data:')) {
109
+ if (sourceMapURL) {
108
110
  try {
109
- lineNumber = await processScriptWithInlineSourceMap({ file, line, sourceMapURL })
111
+ ({ line: lineNumber, column: columnNumber } = await getGeneratedPosition(url, source, line, sourceMapURL))
110
112
  } catch (err) {
111
- log.error('Error processing script with inline source map', err)
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(`Error setting breakpoint at ${path}:${line}:`, e)
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