dd-trace 2.23.0 → 2.25.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 (66) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +33 -1
  3. package/package.json +10 -7
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  5. package/packages/datadog-instrumentations/src/helpers/register.js +7 -0
  6. package/packages/datadog-instrumentations/src/http/server.js +7 -1
  7. package/packages/datadog-instrumentations/src/ldapjs.js +91 -0
  8. package/packages/datadog-instrumentations/src/mocha.js +33 -8
  9. package/packages/datadog-instrumentations/src/pg.js +6 -2
  10. package/packages/datadog-plugin-http/src/client.js +1 -1
  11. package/packages/datadog-plugin-http/src/server.js +7 -3
  12. package/packages/datadog-plugin-jest/src/index.js +2 -2
  13. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  14. package/packages/datadog-plugin-pg/src/index.js +1 -1
  15. package/packages/datadog-plugin-router/src/index.js +6 -3
  16. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  17. package/packages/dd-trace/src/appsec/blocking.js +44 -0
  18. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +1 -1
  19. package/packages/dd-trace/src/appsec/gateway/engine/engine.js +1 -1
  20. package/packages/dd-trace/src/appsec/gateway/engine/index.js +6 -1
  21. package/packages/dd-trace/src/appsec/gateway/engine/runner.js +0 -1
  22. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -1
  23. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +11 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  25. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +41 -3
  26. package/packages/dd-trace/src/appsec/iast/iast-context.js +3 -1
  27. package/packages/dd-trace/src/appsec/iast/index.js +15 -3
  28. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +20 -1
  29. package/packages/dd-trace/src/appsec/iast/path-line.js +6 -5
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +17 -0
  31. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -29
  32. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +16 -15
  33. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +103 -0
  34. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +63 -41
  35. package/packages/dd-trace/src/appsec/index.js +68 -27
  36. package/packages/dd-trace/src/{plugins/util → appsec}/ip_blocklist.js +0 -0
  37. package/packages/dd-trace/src/appsec/ip_extractor.js +98 -0
  38. package/packages/dd-trace/src/appsec/recommended.json +75 -8
  39. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -1
  40. package/packages/dd-trace/src/appsec/remote_config/manager.js +2 -2
  41. package/packages/dd-trace/src/appsec/templates/blocked.html +99 -0
  42. package/packages/dd-trace/src/appsec/templates/blocked.json +8 -0
  43. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +4 -0
  44. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +18 -2
  45. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +12 -6
  46. package/packages/dd-trace/src/config.js +49 -8
  47. package/packages/dd-trace/src/exporters/common/request.js +33 -1
  48. package/packages/dd-trace/src/format.js +5 -1
  49. package/packages/dd-trace/src/lambda/handler.js +72 -0
  50. package/packages/dd-trace/src/lambda/index.js +5 -0
  51. package/packages/dd-trace/src/lambda/runtime/errors.js +20 -0
  52. package/packages/dd-trace/src/lambda/runtime/patch.js +74 -0
  53. package/packages/dd-trace/src/lambda/runtime/ritm.js +143 -0
  54. package/packages/dd-trace/src/plugin_manager.js +5 -11
  55. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -0
  56. package/packages/dd-trace/src/plugins/database.js +4 -4
  57. package/packages/dd-trace/src/plugins/log_plugin.js +2 -2
  58. package/packages/dd-trace/src/plugins/util/ci.js +5 -2
  59. package/packages/dd-trace/src/plugins/util/test.js +2 -2
  60. package/packages/dd-trace/src/plugins/util/user-provided-git.js +14 -1
  61. package/packages/dd-trace/src/plugins/util/web.js +1 -104
  62. package/packages/dd-trace/src/priority_sampler.js +6 -2
  63. package/packages/dd-trace/src/proxy.js +4 -3
  64. package/packages/dd-trace/src/ritm.js +7 -1
  65. package/packages/dd-trace/src/span_processor.js +13 -0
  66. package/packages/dd-trace/src/span_sampler.js +1 -4
@@ -2,6 +2,7 @@
2
2
 
3
3
  const Plugin = require('../../../../src/plugins/plugin')
4
4
  const { storage } = require('../../../../../datadog-core')
5
+ const log = require('../../../log')
5
6
  const { getFirstNonDDPathAndLine } = require('../path-line')
6
7
  const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
7
8
  const { getIastContext } = require('../iast-context')
@@ -13,6 +14,20 @@ class Analyzer extends Plugin {
13
14
  this._type = type
14
15
  }
15
16
 
17
+ _wrapHandler (handler) {
18
+ return (message, name) => {
19
+ try {
20
+ handler(message, name)
21
+ } catch (e) {
22
+ log.debug(e)
23
+ }
24
+ }
25
+ }
26
+
27
+ addSub (channelName, handler) {
28
+ super.addSub(channelName, this._wrapHandler(handler))
29
+ }
30
+
16
31
  _isVulnerable (value, context) {
17
32
  return false
18
33
  }
@@ -25,6 +40,14 @@ class Analyzer extends Plugin {
25
40
  addVulnerability(context, vulnerability)
26
41
  }
27
42
 
43
+ _reportIfVulnerable (value, context) {
44
+ if (this._isVulnerable(value, context) && this._checkOCE(context)) {
45
+ this._report(value, context)
46
+ return true
47
+ }
48
+ return false
49
+ }
50
+
28
51
  _getEvidence (value) {
29
52
  return { value }
30
53
  }
@@ -34,9 +57,24 @@ class Analyzer extends Plugin {
34
57
  }
35
58
 
36
59
  analyze (value) {
37
- const iastContext = getIastContext(storage.getStore())
38
- if (iastContext && this._isVulnerable(value, iastContext) && this._checkOCE(iastContext)) {
39
- this._report(value, iastContext)
60
+ const store = storage.getStore()
61
+ const iastContext = getIastContext(store)
62
+ if (store && !iastContext) return
63
+ this._reportIfVulnerable(value, iastContext)
64
+ }
65
+
66
+ analyzeAll (...values) {
67
+ const store = storage.getStore()
68
+ const iastContext = getIastContext(store)
69
+ if (store && !iastContext) return
70
+ for (let i = 0; i < values.length; i++) {
71
+ const value = values[i]
72
+ if (this._isVulnerable(value, iastContext)) {
73
+ if (this._checkOCE(iastContext)) {
74
+ this._report(value, iastContext)
75
+ }
76
+ break
77
+ }
40
78
  }
41
79
  }
42
80
 
@@ -1,4 +1,5 @@
1
1
  const IAST_CONTEXT_KEY = Symbol('_dd.iast.context')
2
+ const IAST_TRANSACTION_ID = Symbol('_dd.iast.transactionId')
2
3
 
3
4
  function getIastContext (store) {
4
5
  return store && store[IAST_CONTEXT_KEY]
@@ -46,5 +47,6 @@ module.exports = {
46
47
  getIastContext,
47
48
  saveIastContext,
48
49
  cleanIastContext,
49
- IAST_CONTEXT_KEY
50
+ IAST_CONTEXT_KEY,
51
+ IAST_TRANSACTION_ID
50
52
  }
@@ -1,4 +1,4 @@
1
- const { sendVulnerabilities } = require('./vulnerability-reporter')
1
+ const { sendVulnerabilities, setTracer } = require('./vulnerability-reporter')
2
2
  const { enableAllAnalyzers, disableAllAnalyzers } = require('./analyzers')
3
3
  const web = require('../../plugins/util/web')
4
4
  const { storage } = require('../../../../datadog-core')
@@ -7,22 +7,27 @@ const dc = require('diagnostics_channel')
7
7
  const iastContextFunctions = require('./iast-context')
8
8
  const { enableTaintTracking, disableTaintTracking, createTransaction, removeTransaction } = require('./taint-tracking')
9
9
 
10
+ const IAST_ENABLED_TAG_KEY = '_dd.iast.enabled'
11
+
10
12
  // TODO Change to `apm:http:server:request:[start|close]` when the subscription
11
13
  // order of the callbacks can be enforce
12
14
  const requestStart = dc.channel('dd-trace:incomingHttpRequestStart')
13
15
  const requestClose = dc.channel('dd-trace:incomingHttpRequestEnd')
14
16
 
15
- function enable (config) {
17
+ function enable (config, _tracer) {
16
18
  enableAllAnalyzers()
17
19
  enableTaintTracking()
18
20
  requestStart.subscribe(onIncomingHttpRequestStart)
19
21
  requestClose.subscribe(onIncomingHttpRequestEnd)
20
22
  overheadController.configure(config.iast)
23
+ overheadController.startGlobalContext()
24
+ setTracer(_tracer)
21
25
  }
22
26
 
23
27
  function disable () {
24
28
  disableAllAnalyzers()
25
29
  disableTaintTracking()
30
+ overheadController.finishGlobalContext()
26
31
  if (requestStart.hasSubscribers) requestStart.unsubscribe(onIncomingHttpRequestStart)
27
32
  if (requestClose.hasSubscribers) requestClose.unsubscribe(onIncomingHttpRequestEnd)
28
33
  }
@@ -40,6 +45,11 @@ function onIncomingHttpRequestStart (data) {
40
45
  createTransaction(rootSpan.context().toSpanId(), iastContext)
41
46
  overheadController.initializeRequestContext(iastContext)
42
47
  }
48
+ if (rootSpan.addTags) {
49
+ rootSpan.addTags({
50
+ [IAST_ENABLED_TAG_KEY]: isRequestAcquired ? '1' : '0'
51
+ })
52
+ }
43
53
  }
44
54
  }
45
55
  }
@@ -50,7 +60,9 @@ function onIncomingHttpRequestEnd (data) {
50
60
  const store = storage.getStore()
51
61
  const iastContext = iastContextFunctions.getIastContext(storage.getStore())
52
62
  if (iastContext && iastContext.rootSpan) {
53
- sendVulnerabilities(iastContext, iastContext.rootSpan)
63
+ const vulnerabilities = iastContext.vulnerabilities
64
+ const rootSpan = iastContext.rootSpan
65
+ sendVulnerabilities(vulnerabilities, rootSpan)
54
66
  removeTransaction(iastContext)
55
67
  }
56
68
  // TODO web.getContext(data.req) is required when the request is aborted
@@ -2,8 +2,11 @@
2
2
 
3
3
  const OVERHEAD_CONTROLLER_CONTEXT_KEY = 'oce'
4
4
  const REPORT_VULNERABILITY = 'REPORT_VULNERABILITY'
5
+ const INTERVAL_RESET_GLOBAL_CONTEXT = 60 * 1000
5
6
 
6
7
  const GLOBAL_OCE_CONTEXT = {}
8
+
9
+ let resetGlobalContextInterval
7
10
  let config = {}
8
11
  let availableRequest = 0
9
12
  const OPERATIONS = {
@@ -80,11 +83,27 @@ function configure (cfg) {
80
83
  availableRequest = config.maxConcurrentRequests
81
84
  }
82
85
 
83
- _resetGlobalContext()
86
+ function startGlobalContext () {
87
+ if (resetGlobalContextInterval) return
88
+ _resetGlobalContext()
89
+ resetGlobalContextInterval = setInterval(() => {
90
+ _resetGlobalContext()
91
+ }, INTERVAL_RESET_GLOBAL_CONTEXT)
92
+ resetGlobalContextInterval.unref && resetGlobalContextInterval.unref()
93
+ }
94
+
95
+ function finishGlobalContext () {
96
+ if (resetGlobalContextInterval) {
97
+ clearInterval(resetGlobalContextInterval)
98
+ resetGlobalContextInterval = null
99
+ }
100
+ }
84
101
 
85
102
  module.exports = {
86
103
  OVERHEAD_CONTROLLER_CONTEXT_KEY,
87
104
  OPERATIONS,
105
+ startGlobalContext,
106
+ finishGlobalContext,
88
107
  _resetGlobalContext,
89
108
  initializeRequestContext,
90
109
  hasQuota,
@@ -1,4 +1,5 @@
1
1
  const path = require('path')
2
+ const process = require('process')
2
3
  const pathLine = {
3
4
  getFirstNonDDPathAndLine,
4
5
  getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
@@ -7,7 +8,7 @@ const pathLine = {
7
8
  }
8
9
 
9
10
  const EXCLUDED_PATHS = [
10
- '/node_modules/diagnostics_channel'
11
+ path.join(path.sep, 'node_modules', 'diagnostics_channel')
11
12
  ]
12
13
  const EXCLUDED_PATH_PREFIXES = [
13
14
  'node:diagnostics_channel',
@@ -20,7 +21,7 @@ const EXCLUDED_PATH_PREFIXES = [
20
21
 
21
22
  function calculateDDBasePath (dirname) {
22
23
  const dirSteps = dirname.split(path.sep)
23
- const packagesIndex = dirSteps.indexOf('packages')
24
+ const packagesIndex = dirSteps.lastIndexOf('packages')
24
25
  return dirSteps.slice(0, packagesIndex).join(path.sep) + path.sep
25
26
  }
26
27
 
@@ -43,10 +44,10 @@ function getFirstNonDDPathAndLineFromCallsites (callsites) {
43
44
  if (callsites) {
44
45
  for (let i = 0; i < callsites.length; i++) {
45
46
  const callsite = callsites[i]
46
- const path = callsite.getFileName()
47
- if (!isExcluded(callsite) && path.indexOf(pathLine.ddBasePath) === -1) {
47
+ const filepath = callsite.getFileName()
48
+ if (!isExcluded(callsite) && filepath.indexOf(pathLine.ddBasePath) === -1) {
48
49
  return {
49
- path,
50
+ path: path.relative(process.cwd(), filepath),
50
51
  line: callsite.getLineNumber()
51
52
  }
52
53
  }
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ const csiMethods = [
4
+ { src: 'plusOperator', operator: true },
5
+ { src: 'trim' },
6
+ { src: 'trimStart', dst: 'trim' },
7
+ { src: 'trimEnd' },
8
+ { src: 'concat' },
9
+ { src: 'substring' },
10
+ { src: 'substr' },
11
+ { src: 'slice' },
12
+ { src: 'replace' }
13
+ ]
14
+
15
+ module.exports = {
16
+ csiMethods
17
+ }
@@ -1,33 +1,6 @@
1
- const { storage } = require('../../../../../datadog-core')
2
- const iastContextFunctions = require('../iast-context')
3
- const log = require('../../../log')
4
1
  const TaintedUtils = require('@datadog/native-iast-taint-tracking')
5
-
6
- const IAST_TRANSACTION_ID = Symbol('_dd.iast.transactionId')
7
-
8
- function noop (res) { return res }
9
- const TaintTrackingDummy = {
10
- plusOperator: noop
11
- }
12
-
13
- const TaintTracking = {
14
- plusOperator: function (res, op1, op2) {
15
- try {
16
- if (typeof res !== 'string' ||
17
- (typeof op1 !== 'string' && typeof op2 !== 'string')) { return res }
18
-
19
- const store = storage.getStore()
20
- const iastContext = iastContextFunctions.getIastContext(store)
21
- const transactionId = iastContext && iastContext[IAST_TRANSACTION_ID]
22
- if (transactionId) {
23
- return TaintedUtils.concat(transactionId, res, op1, op2)
24
- }
25
- } catch (e) {
26
- log.debug(e)
27
- }
28
- return res
29
- }
30
- }
2
+ const { IAST_TRANSACTION_ID } = require('../iast-context')
3
+ const { TaintTracking, TaintTrackingDummy } = require('./taint-tracking-impl')
31
4
 
32
5
  function createTransaction (id, iastContext) {
33
6
  if (id && iastContext) {
@@ -4,20 +4,7 @@ const Module = require('module')
4
4
  const shimmer = require('../../../../../datadog-shimmer')
5
5
  const log = require('../../../log')
6
6
  const { isPrivateModule, isNotLibraryFile } = require('./filter')
7
-
8
- let originalPrepareStackTrace = Error.prepareStackTrace
9
- function getPrepareStackTraceAccessor () {
10
- let actual = getPrepareStackTrace(originalPrepareStackTrace)
11
- return {
12
- get () {
13
- return actual
14
- },
15
- set (value) {
16
- actual = getPrepareStackTrace(value)
17
- originalPrepareStackTrace = value
18
- }
19
- }
20
- }
7
+ const { csiMethods } = require('./csi-methods')
21
8
 
22
9
  let rewriter
23
10
  let getPrepareStackTrace
@@ -27,7 +14,7 @@ function getRewriter () {
27
14
  const iastRewriter = require('@datadog/native-iast-rewriter')
28
15
  const Rewriter = iastRewriter.Rewriter
29
16
  getPrepareStackTrace = iastRewriter.getPrepareStackTrace
30
- rewriter = new Rewriter()
17
+ rewriter = new Rewriter({ csiMethods })
31
18
  } catch (e) {
32
19
  log.warn(`Unable to initialize TaintTracking Rewriter: ${e.message}`)
33
20
  }
@@ -35,6 +22,20 @@ function getRewriter () {
35
22
  return rewriter
36
23
  }
37
24
 
25
+ let originalPrepareStackTrace = Error.prepareStackTrace
26
+ function getPrepareStackTraceAccessor () {
27
+ let actual = getPrepareStackTrace(originalPrepareStackTrace)
28
+ return {
29
+ get () {
30
+ return actual
31
+ },
32
+ set (value) {
33
+ actual = getPrepareStackTrace(value)
34
+ originalPrepareStackTrace = value
35
+ }
36
+ }
37
+ }
38
+
38
39
  function getCompileMethodFn (compileMethod) {
39
40
  return function (content, filename) {
40
41
  try {
@@ -0,0 +1,103 @@
1
+ 'use strict'
2
+
3
+ const TaintedUtils = require('@datadog/native-iast-taint-tracking')
4
+ const { storage } = require('../../../../../datadog-core')
5
+ const iastContextFunctions = require('../iast-context')
6
+ const log = require('../../../log')
7
+
8
+ function noop (res) { return res }
9
+ const TaintTrackingDummy = {
10
+ plusOperator: noop,
11
+ trim: noop,
12
+ trimEnd: noop,
13
+ concat: noop,
14
+ substring: noop,
15
+ substr: noop,
16
+ slice: noop,
17
+ replace: noop
18
+ }
19
+
20
+ function getTransactionId () {
21
+ const store = storage.getStore()
22
+ const iastContext = iastContextFunctions.getIastContext(store)
23
+ return iastContext && iastContext[iastContextFunctions.IAST_TRANSACTION_ID]
24
+ }
25
+
26
+ function getFilteredCsiFn (cb, filter) {
27
+ return function csiCall (res, fn, target, ...rest) {
28
+ try {
29
+ if (filter(res, fn, target)) { return res }
30
+ const transactionId = getTransactionId()
31
+ if (transactionId) {
32
+ return cb(transactionId, res, target, ...rest)
33
+ }
34
+ } catch (e) {
35
+ log.debug(e)
36
+ }
37
+ return res
38
+ }
39
+ }
40
+
41
+ function notString () {
42
+ return Array.prototype.some.call(arguments, (p) => typeof p !== 'string')
43
+ }
44
+
45
+ function isValidCsiMethod (fn, protos) {
46
+ return protos.some(proto => fn === proto)
47
+ }
48
+
49
+ function getCsiFn (cb, ...protos) {
50
+ let filter
51
+ if (!protos || protos.length === 0) {
52
+ filter = (res, fn, target) => notString(res, target)
53
+ } else if (protos.length === 1) {
54
+ const protoFn = protos[0]
55
+ filter = (res, fn, target) => notString(res, target) || fn !== protoFn
56
+ } else {
57
+ filter = (res, fn, target) => notString(res, target) || !isValidCsiMethod(fn, protos)
58
+ }
59
+ return getFilteredCsiFn(cb, filter)
60
+ }
61
+
62
+ function csiMethodsDefaults (names, excluded) {
63
+ const impl = {}
64
+ names.forEach(name => {
65
+ if (excluded.indexOf(name) !== -1) return
66
+ impl[name] = getCsiFn(
67
+ (transactionId, res, target, ...rest) => TaintedUtils[name](transactionId, res, target, ...rest),
68
+ String.prototype[name]
69
+ )
70
+ })
71
+ return impl
72
+ }
73
+
74
+ const csiMethodsOverrides = {
75
+ plusOperator: function (res, op1, op2) {
76
+ try {
77
+ if (notString(res) || (notString(op1) && notString(op2))) { return res }
78
+ const transactionId = getTransactionId()
79
+ if (transactionId) {
80
+ return TaintedUtils.concat(transactionId, res, op1, op2)
81
+ }
82
+ } catch (e) {
83
+ log.debug(e)
84
+ }
85
+ return res
86
+ },
87
+
88
+ trim: getCsiFn(
89
+ (transactionId, res, target) => TaintedUtils.trim(transactionId, res, target),
90
+ String.prototype.trim,
91
+ String.prototype.trimStart
92
+ )
93
+ }
94
+
95
+ const TaintTracking = {
96
+ ...csiMethodsDefaults(Object.keys(TaintTrackingDummy), Object.keys(csiMethodsOverrides)),
97
+ ...csiMethodsOverrides
98
+ }
99
+
100
+ module.exports = {
101
+ TaintTracking,
102
+ TaintTrackingDummy
103
+ }
@@ -5,13 +5,16 @@ const IAST_JSON_TAG_KEY = '_dd.iast.json'
5
5
  const VULNERABILITY_HASHES_MAX_SIZE = 1000
6
6
  const VULNERABILITY_HASHES = new LRU({ max: VULNERABILITY_HASHES_MAX_SIZE })
7
7
 
8
+ let tracer
9
+
8
10
  function createVulnerability (type, evidence, spanId, location) {
9
- if (type && evidence && spanId) {
11
+ if (type && evidence) {
12
+ const _spanId = spanId || 0
10
13
  return {
11
14
  type,
12
15
  evidence,
13
16
  location: {
14
- spanId,
17
+ spanId: _spanId,
15
18
  ...location
16
19
  },
17
20
  hash: createHash(type, location)
@@ -37,10 +40,14 @@ function createHash (type, location) {
37
40
  }
38
41
 
39
42
  function addVulnerability (iastContext, vulnerability) {
40
- if (iastContext && vulnerability && vulnerability.evidence && vulnerability.type &&
41
- vulnerability.location && vulnerability.location.spanId) {
42
- iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
43
- iastContext[VULNERABILITIES_KEY].push(vulnerability)
43
+ if (vulnerability && vulnerability.evidence && vulnerability.type &&
44
+ vulnerability.location) {
45
+ if (iastContext && iastContext.rootSpan) {
46
+ iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
47
+ iastContext[VULNERABILITIES_KEY].push(vulnerability)
48
+ } else {
49
+ sendVulnerabilities([vulnerability])
50
+ }
44
51
  }
45
52
  }
46
53
 
@@ -101,43 +108,53 @@ function jsonVulnerabilityFromVulnerability (vulnerability, sourcesIndexes) {
101
108
  return jsonVulnerability
102
109
  }
103
110
 
104
- function sendVulnerabilities (iastContext) {
105
- if (iastContext && iastContext.rootSpan && iastContext[VULNERABILITIES_KEY] &&
106
- iastContext[VULNERABILITIES_KEY].length && iastContext.rootSpan.addTags) {
107
- const span = iastContext.rootSpan
108
- const allVulnerabilities = iastContext[VULNERABILITIES_KEY]
109
- const jsonToSend = {
110
- sources: [],
111
- vulnerabilities: []
111
+ function sendVulnerabilities (vulnerabilities, rootSpan) {
112
+ if (vulnerabilities && vulnerabilities.length) {
113
+ let span = rootSpan
114
+ if (!span && tracer) {
115
+ span = tracer.startSpan('vulnerability', {
116
+ type: 'vulnerability'
117
+ })
118
+ vulnerabilities.forEach((vulnerability) => {
119
+ vulnerability.location.spanId = span.context().toSpanId()
120
+ })
112
121
  }
113
122
 
114
- deduplicateVulnerabilities(allVulnerabilities).forEach((vulnerability) => {
115
- if (isValidVulnerability(vulnerability)) {
116
- const sourcesIndexes = []
117
- const vulnerabilitySources = extractSourcesFromVulnerability(vulnerability)
118
- vulnerabilitySources.forEach((source) => {
119
- let sourceIndex = jsonToSend.sources.findIndex(
120
- existingSource =>
121
- existingSource.origin === source.origin &&
122
- existingSource.name === source.name &&
123
- existingSource.value === source.value
124
- )
125
- if (sourceIndex === -1) {
126
- sourceIndex = jsonToSend.sources.length
127
- jsonToSend.sources.push(source)
128
- }
129
- sourcesIndexes.push(sourceIndex)
130
- })
131
- jsonToSend.vulnerabilities.push(jsonVulnerabilityFromVulnerability(vulnerability, sourcesIndexes))
123
+ if (span && span.addTags) {
124
+ const jsonToSend = {
125
+ sources: [],
126
+ vulnerabilities: []
127
+ }
128
+
129
+ deduplicateVulnerabilities(vulnerabilities).forEach((vulnerability) => {
130
+ if (isValidVulnerability(vulnerability)) {
131
+ const sourcesIndexes = []
132
+ const vulnerabilitySources = extractSourcesFromVulnerability(vulnerability)
133
+ vulnerabilitySources.forEach((source) => {
134
+ let sourceIndex = jsonToSend.sources.findIndex(
135
+ existingSource =>
136
+ existingSource.origin === source.origin &&
137
+ existingSource.name === source.name &&
138
+ existingSource.value === source.value
139
+ )
140
+ if (sourceIndex === -1) {
141
+ sourceIndex = jsonToSend.sources.length
142
+ jsonToSend.sources.push(source)
143
+ }
144
+ sourcesIndexes.push(sourceIndex)
145
+ })
146
+ jsonToSend.vulnerabilities.push(jsonVulnerabilityFromVulnerability(vulnerability, sourcesIndexes))
147
+ }
148
+ })
149
+
150
+ if (jsonToSend.vulnerabilities.length > 0) {
151
+ const tags = {}
152
+ // TODO: Store this outside of the span and set the tag in the exporter.
153
+ tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
154
+ tags[MANUAL_KEEP] = 'true'
155
+ span.addTags(tags)
156
+ if (!rootSpan) span.finish()
132
157
  }
133
- })
134
-
135
- if (jsonToSend.vulnerabilities.length > 0) {
136
- const tags = {}
137
- // TODO: Store this outside of the span and set the tag in the exporter.
138
- tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
139
- tags[MANUAL_KEEP] = 'true'
140
- span.addTags(tags)
141
158
  }
142
159
  }
143
160
  return IAST_JSON_TAG_KEY
@@ -159,9 +176,14 @@ function deduplicateVulnerabilities (vulnerabilities) {
159
176
  return deduplicated
160
177
  }
161
178
 
179
+ function setTracer (_tracer) {
180
+ tracer = _tracer
181
+ }
182
+
162
183
  module.exports = {
163
184
  createVulnerability,
164
185
  addVulnerability,
165
186
  sendVulnerabilities,
166
- clearCache
187
+ clearCache,
188
+ setTracer
167
189
  }