dd-trace 2.40.0 → 2.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/package.json +3 -3
  2. package/packages/datadog-instrumentations/src/cucumber.js +5 -2
  3. package/packages/datadog-instrumentations/src/grpc/client.js +44 -42
  4. package/packages/datadog-instrumentations/src/grpc/server.js +69 -60
  5. package/packages/datadog-instrumentations/src/http2/client.js +25 -26
  6. package/packages/datadog-instrumentations/src/jest.js +3 -1
  7. package/packages/datadog-instrumentations/src/mocha.js +5 -3
  8. package/packages/datadog-plugin-cypress/src/plugin.js +4 -2
  9. package/packages/datadog-plugin-grpc/src/client.js +29 -11
  10. package/packages/datadog-plugin-grpc/src/server.js +22 -6
  11. package/packages/datadog-plugin-http2/src/client.js +46 -29
  12. package/packages/datadog-plugin-router/src/index.js +1 -1
  13. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +3 -0
  14. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +7 -1
  15. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +3 -0
  16. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +19 -15
  17. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +5 -2
  18. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +2 -0
  19. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -1
  20. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +18 -19
  21. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +3 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +3 -0
  23. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  24. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +205 -0
  25. package/packages/dd-trace/src/appsec/iast/index.js +6 -5
  26. package/packages/dd-trace/src/appsec/iast/tags.js +2 -1
  27. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +6 -6
  28. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
  29. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +23 -4
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +32 -16
  31. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +33 -0
  32. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +23 -16
  33. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +76 -37
  34. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +101 -0
  35. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +45 -0
  36. package/packages/dd-trace/src/appsec/iast/telemetry/{logs.js → log/index.js} +5 -5
  37. package/packages/dd-trace/src/appsec/iast/telemetry/{log_collector.js → log/log-collector.js} +1 -1
  38. package/packages/dd-trace/src/appsec/iast/telemetry/namespaces.js +76 -0
  39. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +53 -0
  40. package/packages/dd-trace/src/appsec/iast/telemetry/verbosity.js +42 -0
  41. package/packages/dd-trace/src/config.js +21 -2
  42. package/packages/dd-trace/src/constants.js +1 -0
  43. package/packages/dd-trace/src/opentracing/tracer.js +1 -0
  44. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  45. package/packages/dd-trace/src/plugins/outbound.js +29 -12
  46. package/packages/dd-trace/src/plugins/plugin.js +28 -0
  47. package/packages/dd-trace/src/plugins/tracing.js +33 -16
  48. package/packages/dd-trace/src/plugins/util/ci.js +1 -1
  49. package/packages/dd-trace/src/plugins/util/test.js +55 -11
  50. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -22
  51. package/packages/dd-trace/src/profiling/config.js +0 -3
  52. package/packages/dd-trace/src/profiling/index.js +0 -2
  53. package/packages/dd-trace/src/profiling/profilers/wall.js +23 -11
  54. package/packages/diagnostics_channel/src/index.js +64 -0
  55. package/packages/dd-trace/src/profiling/profilers/cpu.js +0 -126
  56. /package/packages/dd-trace/src/appsec/iast/taint-tracking/{origin-types.js → source-types.js} +0 -0
@@ -13,8 +13,7 @@ const {
13
13
  taintTrackingPlugin
14
14
  } = require('./taint-tracking')
15
15
  const { IAST_ENABLED_TAG_KEY } = require('./tags')
16
-
17
- const telemetryLogs = require('./telemetry/logs')
16
+ const iastTelemetry = require('./telemetry')
18
17
 
19
18
  // TODO Change to `apm:http:server:request:[start|close]` when the subscription
20
19
  // order of the callbacks can be enforce
@@ -22,24 +21,24 @@ const requestStart = dc.channel('dd-trace:incomingHttpRequestStart')
22
21
  const requestClose = dc.channel('dd-trace:incomingHttpRequestEnd')
23
22
 
24
23
  function enable (config, _tracer) {
24
+ iastTelemetry.configure(config, config.iast && config.iast.telemetryVerbosity)
25
25
  enableAllAnalyzers()
26
- enableTaintTracking(config.iast)
26
+ enableTaintTracking(config.iast, iastTelemetry.verbosity)
27
27
  requestStart.subscribe(onIncomingHttpRequestStart)
28
28
  requestClose.subscribe(onIncomingHttpRequestEnd)
29
29
  overheadController.configure(config.iast)
30
30
  overheadController.startGlobalContext()
31
31
  vulnerabilityReporter.start(config, _tracer)
32
- telemetryLogs.start()
33
32
  }
34
33
 
35
34
  function disable () {
35
+ iastTelemetry.stop()
36
36
  disableAllAnalyzers()
37
37
  disableTaintTracking()
38
38
  overheadController.finishGlobalContext()
39
39
  if (requestStart.hasSubscribers) requestStart.unsubscribe(onIncomingHttpRequestStart)
40
40
  if (requestClose.hasSubscribers) requestClose.unsubscribe(onIncomingHttpRequestEnd)
41
41
  vulnerabilityReporter.stop()
42
- telemetryLogs.stop()
43
42
  }
44
43
 
45
44
  function onIncomingHttpRequestStart (data) {
@@ -54,6 +53,7 @@ function onIncomingHttpRequestStart (data) {
54
53
  const iastContext = iastContextFunctions.saveIastContext(store, topContext, { rootSpan, req: data.req })
55
54
  createTransaction(rootSpan.context().toSpanId(), iastContext)
56
55
  overheadController.initializeRequestContext(iastContext)
56
+ iastTelemetry.onRequestStart(iastContext)
57
57
  taintTrackingPlugin.taintHeaders(data.req.headers, iastContext)
58
58
  }
59
59
  if (rootSpan.addTags) {
@@ -76,6 +76,7 @@ function onIncomingHttpRequestEnd (data) {
76
76
  const rootSpan = iastContext.rootSpan
77
77
  vulnerabilityReporter.sendVulnerabilities(vulnerabilities, rootSpan)
78
78
  removeTransaction(iastContext)
79
+ iastTelemetry.onRequestEnd(iastContext, iastContext.rootSpan)
79
80
  }
80
81
  // TODO web.getContext(data.req) is required when the request is aborted
81
82
  if (iastContextFunctions.cleanIastContext(store, topContext, iastContext)) {
@@ -2,5 +2,6 @@
2
2
 
3
3
  module.exports = {
4
4
  IAST_ENABLED_TAG_KEY: '_dd.iast.enabled',
5
- IAST_JSON_TAG_KEY: '_dd.iast.json'
5
+ IAST_JSON_TAG_KEY: '_dd.iast.json',
6
+ IAST_TRACE_METRIC_PREFIX: '_dd.iast.telemetry'
6
7
  }
@@ -1,15 +1,15 @@
1
1
  'use strict'
2
2
 
3
3
  const csiMethods = [
4
+ { src: 'concat' },
4
5
  { src: 'plusOperator', operator: true },
6
+ { src: 'replace' },
7
+ { src: 'slice' },
8
+ { src: 'substr' },
9
+ { src: 'substring' },
5
10
  { src: 'trim' },
6
- { src: 'trimStart', dst: 'trim' },
7
11
  { src: 'trimEnd' },
8
- { src: 'concat' },
9
- { src: 'substring' },
10
- { src: 'substr' },
11
- { src: 'slice' },
12
- { src: 'replace' }
12
+ { src: 'trimStart', dst: 'trim' }
13
13
  ]
14
14
 
15
15
  module.exports = {
@@ -10,9 +10,9 @@ const { createTransaction,
10
10
  const taintTrackingPlugin = require('./plugin')
11
11
 
12
12
  module.exports = {
13
- enableTaintTracking (config) {
14
- enableRewriter()
15
- enableTaintOperations()
13
+ enableTaintTracking (config, telemetryVerbosity) {
14
+ enableRewriter(telemetryVerbosity)
15
+ enableTaintOperations(telemetryVerbosity)
16
16
  taintTrackingPlugin.enable()
17
17
  setMaxTransactions(config.maxConcurrentRequests)
18
18
  },
@@ -3,7 +3,10 @@
3
3
  const TaintedUtils = require('@datadog/native-iast-taint-tracking')
4
4
  const { IAST_TRANSACTION_ID } = require('../iast-context')
5
5
  const iastLog = require('../iast-log')
6
- const { TaintTracking, TaintTrackingDummy } = require('./taint-tracking-impl')
6
+ const iastTelemetry = require('../telemetry')
7
+ const { REQUEST_TAINTED } = require('../telemetry/iast-metric')
8
+ const { isInfoAllowed } = require('../telemetry/verbosity')
9
+ const { getTaintTrackingImpl, getTaintTrackingNoop } = require('./taint-tracking-impl')
7
10
 
8
11
  function createTransaction (id, iastContext) {
9
12
  if (id && iastContext) {
@@ -11,9 +14,21 @@ function createTransaction (id, iastContext) {
11
14
  }
12
15
  }
13
16
 
17
+ let onRemoveTransaction = (transactionId, iastContext) => {}
18
+
19
+ function onRemoveTransactionInformationTelemetry (transactionId, iastContext) {
20
+ const metrics = TaintedUtils.getMetrics(transactionId, iastTelemetry.verbosity)
21
+ if (metrics && metrics.requestCount) {
22
+ REQUEST_TAINTED.add(metrics.requestCount, null, iastContext)
23
+ }
24
+ }
25
+
14
26
  function removeTransaction (iastContext) {
15
27
  if (iastContext && iastContext[IAST_TRANSACTION_ID]) {
16
28
  const transactionId = iastContext[IAST_TRANSACTION_ID]
29
+
30
+ onRemoveTransaction(transactionId, iastContext)
31
+
17
32
  TaintedUtils.removeTransaction(transactionId)
18
33
  delete iastContext[IAST_TRANSACTION_ID]
19
34
  }
@@ -96,12 +111,16 @@ function getRanges (iastContext, string) {
96
111
  return result
97
112
  }
98
113
 
99
- function enableTaintOperations () {
100
- global._ddiast = TaintTracking
114
+ function enableTaintOperations (telemetryVerbosity) {
115
+ if (isInfoAllowed(telemetryVerbosity)) {
116
+ onRemoveTransaction = onRemoveTransactionInformationTelemetry
117
+ }
118
+
119
+ global._ddiast = getTaintTrackingImpl(telemetryVerbosity)
101
120
  }
102
121
 
103
122
  function disableTaintOperations () {
104
- global._ddiast = TaintTrackingDummy
123
+ global._ddiast = getTaintTrackingNoop()
105
124
  }
106
125
 
107
126
  function setMaxTransactions (transactions) {
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const Plugin = require('../../../plugins/plugin')
3
+ const { SourceIastPlugin } = require('../iast-plugin')
4
4
  const { getIastContext } = require('../iast-context')
5
5
  const { storage } = require('../../../../../datadog-core')
6
6
  const { taintObject } = require('./operations')
@@ -12,15 +12,17 @@ const {
12
12
  HTTP_REQUEST_HEADER_NAME,
13
13
  HTTP_REQUEST_PARAMETER,
14
14
  HTTP_REQUEST_PATH_PARAM
15
+ } = require('./source-types')
15
16
 
16
- } = require('./origin-types')
17
-
18
- class TaintTrackingPlugin extends Plugin {
17
+ class TaintTrackingPlugin extends SourceIastPlugin {
19
18
  constructor () {
20
19
  super()
21
20
  this._type = 'taint-tracking'
21
+ }
22
+
23
+ onConfigure () {
22
24
  this.addSub(
23
- 'datadog:body-parser:read:finish',
25
+ { channelName: 'datadog:body-parser:read:finish', tag: HTTP_REQUEST_BODY },
24
26
  ({ req }) => {
25
27
  const iastContext = getIastContext(storage.getStore())
26
28
  if (iastContext && iastContext['body'] !== req.body) {
@@ -29,31 +31,41 @@ class TaintTrackingPlugin extends Plugin {
29
31
  }
30
32
  }
31
33
  )
34
+
32
35
  this.addSub(
33
- 'datadog:qs:parse:finish',
36
+ { channelName: 'datadog:qs:parse:finish', tag: HTTP_REQUEST_PARAMETER },
34
37
  ({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs)
35
38
  )
36
- this.addSub('apm:express:middleware:next', ({ req }) => {
37
- if (req && req.body && typeof req.body === 'object') {
38
- const iastContext = getIastContext(storage.getStore())
39
- if (iastContext && iastContext['body'] !== req.body) {
40
- this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
41
- iastContext['body'] = req.body
39
+
40
+ this.addSub(
41
+ { channelName: 'apm:express:middleware:next', tag: HTTP_REQUEST_BODY },
42
+ ({ req }) => {
43
+ if (req && req.body && typeof req.body === 'object') {
44
+ const iastContext = getIastContext(storage.getStore())
45
+ if (iastContext && iastContext['body'] !== req.body) {
46
+ this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
47
+ iastContext['body'] = req.body
48
+ }
42
49
  }
43
50
  }
44
- })
51
+ )
52
+
45
53
  this.addSub(
46
- 'datadog:cookie:parse:finish',
54
+ { channelName: 'datadog:cookie:parse:finish', tag: [HTTP_REQUEST_COOKIE_VALUE, HTTP_REQUEST_COOKIE_NAME] },
47
55
  ({ cookies }) => this._cookiesTaintTrackingHandler(cookies)
48
56
  )
57
+
49
58
  this.addSub(
50
- 'datadog:express:process_params:start',
59
+ { channelName: 'datadog:express:process_params:start', tag: HTTP_REQUEST_PATH_PARAM },
51
60
  ({ req }) => {
52
61
  if (req && req.params && typeof req.params === 'object') {
53
62
  this._taintTrackingHandler(HTTP_REQUEST_PATH_PARAM, req, 'params')
54
63
  }
55
64
  }
56
65
  )
66
+
67
+ // this is a special case to increment INSTRUMENTED_SOURCE metric for header
68
+ this.addInstrumentedSource('http', [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME])
57
69
  }
58
70
 
59
71
  _taintTrackingHandler (type, target, property, iastContext = getIastContext(storage.getStore())) {
@@ -70,7 +82,11 @@ class TaintTrackingPlugin extends Plugin {
70
82
  }
71
83
 
72
84
  taintHeaders (headers, iastContext) {
73
- taintObject(iastContext, headers, HTTP_REQUEST_HEADER_VALUE, true, HTTP_REQUEST_HEADER_NAME)
85
+ this.execSource({
86
+ handler: () => taintObject(iastContext, headers, HTTP_REQUEST_HEADER_VALUE, true, HTTP_REQUEST_HEADER_NAME),
87
+ tag: [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME],
88
+ iastContext
89
+ })
74
90
  }
75
91
 
76
92
  enable () {
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const iastTelemetry = require('../telemetry')
4
+ const { Verbosity } = require('../telemetry/verbosity')
5
+ const { INSTRUMENTED_PROPAGATION } = require('../telemetry/iast-metric')
6
+
7
+ const telemetryRewriter = {
8
+ off (content, filename, rewriter) {
9
+ return rewriter.rewrite(content, filename)
10
+ },
11
+
12
+ information (content, filename, rewriter) {
13
+ const response = this.off(content, filename, rewriter)
14
+
15
+ const metrics = response.metrics
16
+ if (metrics && metrics.instrumentedPropagation) {
17
+ INSTRUMENTED_PROPAGATION.add(metrics.instrumentedPropagation)
18
+ }
19
+
20
+ return response
21
+ }
22
+ }
23
+
24
+ function getRewriteFunction (rewriter) {
25
+ switch (iastTelemetry.verbosity) {
26
+ case Verbosity.OFF:
27
+ return (content, filename) => telemetryRewriter.off(content, filename, rewriter)
28
+ default:
29
+ return (content, filename) => telemetryRewriter.information(content, filename, rewriter)
30
+ }
31
+ }
32
+
33
+ module.exports = { getRewriteFunction }
@@ -5,20 +5,17 @@ const shimmer = require('../../../../../datadog-shimmer')
5
5
  const iastLog = require('../iast-log')
6
6
  const { isPrivateModule, isNotLibraryFile } = require('./filter')
7
7
  const { csiMethods } = require('./csi-methods')
8
+ const { getName } = require('../telemetry/verbosity')
9
+ const { getRewriteFunction } = require('./rewriter-telemetry')
8
10
 
9
11
  let rewriter
10
12
  let getPrepareStackTrace
11
- function getRewriter () {
13
+ function getRewriter (telemetryVerbosity) {
12
14
  if (!rewriter) {
13
- try {
14
- const iastRewriter = require('@datadog/native-iast-rewriter')
15
- const Rewriter = iastRewriter.Rewriter
16
- getPrepareStackTrace = iastRewriter.getPrepareStackTrace
17
- rewriter = new Rewriter({ csiMethods })
18
- } catch (e) {
19
- iastLog.error('Unable to initialize TaintTracking Rewriter')
20
- .errorAndPublish(e)
21
- }
15
+ const iastRewriter = require('@datadog/native-iast-rewriter')
16
+ const Rewriter = iastRewriter.Rewriter
17
+ getPrepareStackTrace = iastRewriter.getPrepareStackTrace
18
+ rewriter = new Rewriter({ csiMethods, telemetryVerbosity: getName(telemetryVerbosity) })
22
19
  }
23
20
  return rewriter
24
21
  }
@@ -27,6 +24,7 @@ let originalPrepareStackTrace = Error.prepareStackTrace
27
24
  function getPrepareStackTraceAccessor () {
28
25
  let actual = getPrepareStackTrace(originalPrepareStackTrace)
29
26
  return {
27
+ configurable: true,
30
28
  get () {
31
29
  return actual
32
30
  },
@@ -38,10 +36,11 @@ function getPrepareStackTraceAccessor () {
38
36
  }
39
37
 
40
38
  function getCompileMethodFn (compileMethod) {
39
+ const rewriteFn = getRewriteFunction(rewriter)
41
40
  return function (content, filename) {
42
41
  try {
43
42
  if (isPrivateModule(filename) && isNotLibraryFile(filename)) {
44
- const rewritten = rewriter.rewrite(content, filename)
43
+ const rewritten = rewriteFn(content, filename)
45
44
  if (rewritten && rewritten.content) {
46
45
  return compileMethod.apply(this, [rewritten.content, filename])
47
46
  }
@@ -54,11 +53,19 @@ function getCompileMethodFn (compileMethod) {
54
53
  }
55
54
  }
56
55
 
57
- function enableRewriter () {
58
- const rewriter = getRewriter()
59
- if (rewriter) {
60
- Object.defineProperty(global.Error, 'prepareStackTrace', getPrepareStackTraceAccessor())
61
- shimmer.wrap(Module.prototype, '_compile', compileMethod => getCompileMethodFn(compileMethod))
56
+ function enableRewriter (telemetryVerbosity) {
57
+ try {
58
+ const rewriter = getRewriter(telemetryVerbosity)
59
+ if (rewriter) {
60
+ const pstDescriptor = Object.getOwnPropertyDescriptor(global.Error, 'prepareStackTrace')
61
+ if (!pstDescriptor || pstDescriptor.configurable) {
62
+ Object.defineProperty(global.Error, 'prepareStackTrace', getPrepareStackTraceAccessor())
63
+ }
64
+ shimmer.wrap(Module.prototype, '_compile', compileMethod => getCompileMethodFn(compileMethod))
65
+ }
66
+ } catch (e) {
67
+ iastLog.error('Error enabling TaintTracking Rewriter')
68
+ .errorAndPublish(e)
62
69
  }
63
70
  }
64
71
 
@@ -4,30 +4,45 @@ const TaintedUtils = require('@datadog/native-iast-taint-tracking')
4
4
  const { storage } = require('../../../../../datadog-core')
5
5
  const iastContextFunctions = require('../iast-context')
6
6
  const iastLog = require('../iast-log')
7
+ const { EXECUTED_PROPAGATION } = require('../telemetry/iast-metric')
8
+ const { isDebugAllowed } = require('../telemetry/verbosity')
7
9
 
8
10
  function noop (res) { return res }
9
- const TaintTrackingDummy = {
11
+ // NOTE: methods of this object must be synchronized with csi-methods.js file definitions!
12
+ // Otherwise you may end up rewriting a method and not providing its rewritten implementation
13
+ const TaintTrackingNoop = {
10
14
  plusOperator: noop,
11
- trim: noop,
12
- trimEnd: noop,
13
15
  concat: noop,
14
- substring: noop,
15
- substr: noop,
16
+ replace: noop,
16
17
  slice: noop,
17
- replace: noop
18
+ substr: noop,
19
+ substring: noop,
20
+ trim: noop,
21
+ trimEnd: noop
18
22
  }
19
23
 
20
- function getTransactionId () {
21
- const store = storage.getStore()
22
- const iastContext = iastContextFunctions.getIastContext(store)
24
+ function getTransactionId (iastContext) {
23
25
  return iastContext && iastContext[iastContextFunctions.IAST_TRANSACTION_ID]
24
26
  }
25
27
 
26
- function getFilteredCsiFn (cb, filter) {
28
+ function getContextDefault () {
29
+ const store = storage.getStore()
30
+ return iastContextFunctions.getIastContext(store)
31
+ }
32
+
33
+ function getContextDebug () {
34
+ const iastContext = getContextDefault()
35
+ EXECUTED_PROPAGATION.inc(null, iastContext)
36
+ return iastContext
37
+ }
38
+
39
+ function getFilteredCsiFn (cb, filter, getContext) {
27
40
  return function csiCall (res, fn, target, ...rest) {
28
41
  try {
29
42
  if (filter(res, fn, target)) { return res }
30
- const transactionId = getTransactionId()
43
+
44
+ const context = getContext()
45
+ const transactionId = getTransactionId(context)
31
46
  if (transactionId) {
32
47
  return cb(transactionId, res, target, ...rest)
33
48
  }
@@ -47,7 +62,7 @@ function isValidCsiMethod (fn, protos) {
47
62
  return protos.some(proto => fn === proto)
48
63
  }
49
64
 
50
- function getCsiFn (cb, ...protos) {
65
+ function getCsiFn (cb, getContext, ...protos) {
51
66
  let filter
52
67
  if (!protos || protos.length === 0) {
53
68
  filter = (res, fn, target) => notString(res, target)
@@ -57,49 +72,73 @@ function getCsiFn (cb, ...protos) {
57
72
  } else {
58
73
  filter = (res, fn, target) => notString(res, target) || !isValidCsiMethod(fn, protos)
59
74
  }
60
- return getFilteredCsiFn(cb, filter)
75
+ return getFilteredCsiFn(cb, filter, getContext)
61
76
  }
62
77
 
63
- function csiMethodsDefaults (names, excluded) {
78
+ function csiMethodsDefaults (names, excluded, getContext) {
64
79
  const impl = {}
65
80
  names.forEach(name => {
66
81
  if (excluded.indexOf(name) !== -1) return
67
82
  impl[name] = getCsiFn(
68
83
  (transactionId, res, target, ...rest) => TaintedUtils[name](transactionId, res, target, ...rest),
84
+ getContext,
69
85
  String.prototype[name]
70
86
  )
71
87
  })
72
88
  return impl
73
89
  }
74
90
 
75
- const csiMethodsOverrides = {
76
- plusOperator: function (res, op1, op2) {
77
- try {
78
- if (notString(res) || (notString(op1) && notString(op2))) { return res }
79
- const transactionId = getTransactionId()
80
- if (transactionId) {
81
- return TaintedUtils.concat(transactionId, res, op1, op2)
91
+ function csiMethodsOverrides (getContext) {
92
+ return {
93
+ plusOperator: function (res, op1, op2) {
94
+ try {
95
+ if (notString(res) || (notString(op1) && notString(op2))) { return res }
96
+ const iastContext = getContext()
97
+ const transactionId = getTransactionId(iastContext)
98
+ if (transactionId) {
99
+ return TaintedUtils.concat(transactionId, res, op1, op2)
100
+ }
101
+ } catch (e) {
102
+ iastLog.error(`Error invoking CSI plusOperator`)
103
+ .errorAndPublish(e)
82
104
  }
83
- } catch (e) {
84
- iastLog.error(`Error invoking CSI plusOperator`)
85
- .errorAndPublish(e)
86
- }
87
- return res
88
- },
105
+ return res
106
+ },
107
+
108
+ trim: getCsiFn(
109
+ (transactionId, res, target) => TaintedUtils.trim(transactionId, res, target),
110
+ getContext,
111
+ String.prototype.trim,
112
+ String.prototype.trimStart
113
+ )
114
+ }
115
+ }
116
+
117
+ function createImplWith (getContext) {
118
+ const methodNames = Object.keys(TaintTrackingNoop)
119
+ const overrides = csiMethodsOverrides(getContext)
120
+
121
+ // impls could be cached but at the moment there is only one invocation to getTaintTrackingImpl
122
+ return {
123
+ ...csiMethodsDefaults(methodNames, Object.keys(overrides), getContext),
124
+ ...overrides
125
+ }
126
+ }
127
+
128
+ function getTaintTrackingImpl (telemetryVerbosity, dummy = false) {
129
+ if (dummy) return TaintTrackingNoop
89
130
 
90
- trim: getCsiFn(
91
- (transactionId, res, target) => TaintedUtils.trim(transactionId, res, target),
92
- String.prototype.trim,
93
- String.prototype.trimStart
94
- )
131
+ // with Verbosity.DEBUG every invocation of a TaintedUtils method increases the EXECUTED_PROPAGATION metric
132
+ return isDebugAllowed(telemetryVerbosity)
133
+ ? createImplWith(getContextDebug)
134
+ : createImplWith(getContextDefault)
95
135
  }
96
136
 
97
- const TaintTracking = {
98
- ...csiMethodsDefaults(Object.keys(TaintTrackingDummy), Object.keys(csiMethodsOverrides)),
99
- ...csiMethodsOverrides
137
+ function getTaintTrackingNoop () {
138
+ return getTaintTrackingImpl(null, true)
100
139
  }
101
140
 
102
141
  module.exports = {
103
- TaintTracking,
104
- TaintTrackingDummy
142
+ getTaintTrackingImpl,
143
+ getTaintTrackingNoop
105
144
  }
@@ -0,0 +1,101 @@
1
+ 'use strict'
2
+
3
+ const { getNamespaceFromContext, globalNamespace } = require('./namespaces')
4
+
5
+ const Scope = {
6
+ GLOBAL: 'GLOBAL',
7
+ REQUEST: 'REQUEST'
8
+ }
9
+
10
+ const PropagationType = {
11
+ STRING: 'STRING',
12
+ JSON: 'JSON',
13
+ URL: 'URL'
14
+ }
15
+
16
+ const TagKey = {
17
+ VULNERABILITY_TYPE: 'vulnerability_type',
18
+ SOURCE_TYPE: 'source_type',
19
+ PROPAGATION_TYPE: 'propagation_type'
20
+ }
21
+
22
+ class IastMetric {
23
+ constructor (name, scope, tagKey) {
24
+ this.name = name
25
+ this.scope = scope
26
+ this.tagKey = tagKey
27
+ }
28
+
29
+ getNamespace (context) {
30
+ return getNamespaceFromContext(context) || globalNamespace
31
+ }
32
+
33
+ getTag (tagValue) {
34
+ return tagValue ? { [this.tagKey]: tagValue } : undefined
35
+ }
36
+
37
+ addValue (value, tagValue, context) {
38
+ this.getNamespace(context)
39
+ .count(this.name, this.getTag(tagValue))
40
+ .inc(value)
41
+ }
42
+
43
+ add (value, tagValue, context) {
44
+ if (Array.isArray(tagValue)) {
45
+ tagValue.forEach(tag => this.addValue(value, tag, context))
46
+ } else {
47
+ this.addValue(value, tagValue, context)
48
+ }
49
+ }
50
+
51
+ inc (tagValue, context) {
52
+ this.add(1, tagValue, context)
53
+ }
54
+ }
55
+
56
+ function getExecutedMetric (tagKey) {
57
+ return tagKey === TagKey.VULNERABILITY_TYPE ? EXECUTED_SINK : EXECUTED_SOURCE
58
+ }
59
+
60
+ function getInstrumentedMetric (tagKey) {
61
+ return tagKey === TagKey.VULNERABILITY_TYPE ? INSTRUMENTED_SINK : INSTRUMENTED_SOURCE
62
+ }
63
+
64
+ const INSTRUMENTED_PROPAGATION = new IastMetric('instrumented.propagation', Scope.GLOBAL)
65
+ const INSTRUMENTED_SOURCE = new IastMetric('instrumented.source', Scope.GLOBAL, TagKey.SOURCE_TYPE)
66
+ const INSTRUMENTED_SINK = new IastMetric('instrumented.sink', Scope.GLOBAL, TagKey.VULNERABILITY_TYPE)
67
+
68
+ const EXECUTED_SOURCE = new IastMetric('executed.source', Scope.REQUEST, TagKey.SOURCE_TYPE)
69
+ const EXECUTED_SINK = new IastMetric('executed.sink', Scope.REQUEST, TagKey.VULNERABILITY_TYPE)
70
+
71
+ const REQUEST_TAINTED = new IastMetric('request.tainted', Scope.REQUEST)
72
+
73
+ // DEBUG using metrics
74
+ const EXECUTED_PROPAGATION = new IastMetric('executed.propagation', Scope.REQUEST)
75
+ const EXECUTED_TAINTED = new IastMetric('executed.tainted', Scope.REQUEST)
76
+
77
+ // DEBUG using distribution endpoint
78
+ const INSTRUMENTATION_TIME = new IastMetric('instrumentation.time', Scope.GLOBAL)
79
+
80
+ module.exports = {
81
+ INSTRUMENTED_PROPAGATION,
82
+ INSTRUMENTED_SOURCE,
83
+ INSTRUMENTED_SINK,
84
+
85
+ EXECUTED_PROPAGATION,
86
+ EXECUTED_SOURCE,
87
+ EXECUTED_SINK,
88
+ EXECUTED_TAINTED,
89
+
90
+ REQUEST_TAINTED,
91
+
92
+ INSTRUMENTATION_TIME,
93
+
94
+ PropagationType,
95
+ TagKey,
96
+
97
+ IastMetric,
98
+
99
+ getExecutedMetric,
100
+ getInstrumentedMetric
101
+ }
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const telemetryMetrics = require('../../../telemetry/metrics')
4
+ const telemetryLogs = require('./log')
5
+ const { Verbosity, getVerbosity } = require('./verbosity')
6
+ const { initRequestNamespace, finalizeRequestNamespace, globalNamespace } = require('./namespaces')
7
+
8
+ class Telemetry {
9
+ configure (config, verbosity) {
10
+ // in order to telemetry be enabled, tracer telemetry and metrics collection have to be enabled
11
+ this.enabled = config && config.telemetry && config.telemetry.enabled && config.telemetry.metrics
12
+ this.verbosity = this.enabled ? getVerbosity(verbosity) : Verbosity.OFF
13
+
14
+ if (this.enabled) {
15
+ telemetryMetrics.manager.set('iast', globalNamespace)
16
+ }
17
+
18
+ telemetryLogs.start()
19
+ }
20
+
21
+ stop () {
22
+ this.enabled = false
23
+ telemetryMetrics.manager.delete('iast')
24
+
25
+ telemetryLogs.stop()
26
+ }
27
+
28
+ isEnabled () {
29
+ return this.enabled
30
+ }
31
+
32
+ onRequestStart (context) {
33
+ if (this.isEnabled() && this.verbosity !== Verbosity.OFF) {
34
+ initRequestNamespace(context)
35
+ }
36
+ }
37
+
38
+ onRequestEnd (context, rootSpan) {
39
+ if (this.isEnabled() && this.verbosity !== Verbosity.OFF) {
40
+ finalizeRequestNamespace(context, rootSpan)
41
+ }
42
+ }
43
+ }
44
+
45
+ module.exports = new Telemetry()