dd-trace 3.1.0 → 3.3.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 (61) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/README.md +4 -0
  3. package/ext/tags.d.ts +2 -1
  4. package/ext/tags.js +2 -1
  5. package/index.d.ts +43 -20
  6. package/package.json +6 -4
  7. package/packages/datadog-instrumentations/src/crypto.js +32 -0
  8. package/packages/datadog-instrumentations/src/grpc/server.js +15 -7
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  10. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  11. package/packages/datadog-instrumentations/src/jest.js +136 -14
  12. package/packages/datadog-instrumentations/src/mocha.js +77 -31
  13. package/packages/datadog-instrumentations/src/net.js +13 -0
  14. package/packages/datadog-instrumentations/src/next.js +7 -3
  15. package/packages/datadog-plugin-jest/src/index.js +106 -6
  16. package/packages/datadog-plugin-mocha/src/index.js +15 -7
  17. package/packages/datadog-plugin-mongodb-core/src/index.js +19 -10
  18. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +4 -0
  19. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +20 -0
  20. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +48 -0
  21. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +27 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
  23. package/packages/dd-trace/src/appsec/iast/iast-context.js +50 -0
  24. package/packages/dd-trace/src/appsec/iast/index.js +59 -0
  25. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +94 -0
  26. package/packages/dd-trace/src/appsec/iast/path-line.js +70 -0
  27. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +121 -0
  28. package/packages/dd-trace/src/appsec/recommended.json +1144 -275
  29. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  30. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -3
  31. package/packages/dd-trace/src/config.js +90 -10
  32. package/packages/dd-trace/src/constants.js +9 -1
  33. package/packages/dd-trace/src/encode/0.4.js +7 -1
  34. package/packages/dd-trace/src/encode/0.5.js +7 -1
  35. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +2 -2
  36. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -20
  37. package/packages/dd-trace/src/encode/span-stats.js +155 -0
  38. package/packages/dd-trace/src/exporters/agent/index.js +14 -2
  39. package/packages/dd-trace/src/exporters/agent/writer.js +6 -3
  40. package/packages/dd-trace/src/exporters/common/request.js +9 -5
  41. package/packages/dd-trace/src/exporters/span-stats/index.js +20 -0
  42. package/packages/dd-trace/src/exporters/span-stats/writer.js +54 -0
  43. package/packages/dd-trace/src/format.js +2 -0
  44. package/packages/dd-trace/src/iitm.js +11 -0
  45. package/packages/dd-trace/src/opentracing/propagation/text_map.js +71 -0
  46. package/packages/dd-trace/src/opentracing/tracer.js +1 -1
  47. package/packages/dd-trace/src/plugin_manager.js +12 -2
  48. package/packages/dd-trace/src/plugins/index.js +3 -0
  49. package/packages/dd-trace/src/plugins/log_plugin.js +16 -9
  50. package/packages/dd-trace/src/plugins/util/ip_blocklist.js +51 -0
  51. package/packages/dd-trace/src/plugins/util/web.js +100 -2
  52. package/packages/dd-trace/src/priority_sampler.js +36 -1
  53. package/packages/dd-trace/src/proxy.js +3 -0
  54. package/packages/dd-trace/src/ritm.js +10 -1
  55. package/packages/dd-trace/src/span_processor.js +7 -1
  56. package/packages/dd-trace/src/span_stats.js +210 -0
  57. package/packages/dd-trace/src/telemetry/dependencies.js +83 -0
  58. package/packages/dd-trace/src/{telemetry.js → telemetry/index.js} +10 -65
  59. package/packages/dd-trace/src/telemetry/send-data.js +35 -0
  60. package/packages/dd-trace/src/plugins/util/redis.js +0 -74
  61. package/packages/dd-trace/src/plugins/util/tx.js +0 -75
@@ -0,0 +1,54 @@
1
+
2
+ const { SpanStatsEncoder } = require('../../encode/span-stats')
3
+
4
+ const pkg = require('../../../../../package.json')
5
+
6
+ const BaseWriter = require('../common/writer')
7
+ const request = require('../common/request')
8
+ const log = require('../../log')
9
+
10
+ class Writer extends BaseWriter {
11
+ constructor ({ url }) {
12
+ super(...arguments)
13
+ this._url = url
14
+ this._encoder = new SpanStatsEncoder(this)
15
+ }
16
+
17
+ _sendPayload (data, _, done) {
18
+ makeRequest(data, this._url, (err, res) => {
19
+ if (err) {
20
+ log.error(err)
21
+ done()
22
+ return
23
+ }
24
+ log.debug(`Response from the intake: ${res}`)
25
+ done()
26
+ })
27
+ }
28
+ }
29
+
30
+ function makeRequest (data, url, cb) {
31
+ const options = {
32
+ path: '/v0.6/stats',
33
+ method: 'PUT',
34
+ headers: {
35
+ 'Datadog-Meta-Lang': 'javascript',
36
+ 'Datadog-Meta-Tracer-Version': pkg.version,
37
+ 'Content-Type': 'application/msgpack'
38
+ }
39
+ }
40
+
41
+ options.protocol = url.protocol
42
+ options.hostname = url.hostname
43
+ options.port = url.port
44
+
45
+ log.debug(() => `Request to the intake: ${JSON.stringify(options)}`)
46
+
47
+ request(data, options, (err, res) => {
48
+ cb(err, res)
49
+ })
50
+ }
51
+
52
+ module.exports = {
53
+ Writer
54
+ }
@@ -12,6 +12,7 @@ const SAMPLING_AGENT_DECISION = constants.SAMPLING_AGENT_DECISION
12
12
  const MEASURED = tags.MEASURED
13
13
  const ORIGIN_KEY = constants.ORIGIN_KEY
14
14
  const HOSTNAME_KEY = constants.HOSTNAME_KEY
15
+ const TOP_LEVEL_KEY = constants.TOP_LEVEL_KEY
15
16
 
16
17
  const map = {
17
18
  'service.name': 'service',
@@ -110,6 +111,7 @@ function extractRootTags (trace, span) {
110
111
  addTag({}, trace.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
111
112
  addTag({}, trace.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
112
113
  addTag({}, trace.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
114
+ addTag({}, trace.metrics, TOP_LEVEL_KEY, 1)
113
115
  }
114
116
 
115
117
  function extractChunkTags (trace, span) {
@@ -2,8 +2,19 @@
2
2
 
3
3
  const semver = require('semver')
4
4
  const logger = require('./log')
5
+ const { addHook } = require('import-in-the-middle')
6
+ const dc = require('diagnostics_channel')
5
7
 
6
8
  if (semver.satisfies(process.versions.node, '>=14.13.1')) {
9
+ const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
10
+ addHook((name, namespace) => {
11
+ if (moduleLoadStartChannel.hasSubscribers) {
12
+ moduleLoadStartChannel.publish({
13
+ filename: name,
14
+ module: namespace
15
+ })
16
+ }
17
+ })
7
18
  module.exports = require('import-in-the-middle')
8
19
  } else {
9
20
  logger.warn('ESM is not fully supported by this version of Node.js, ' +
@@ -11,6 +11,7 @@ const traceKey = 'x-datadog-trace-id'
11
11
  const spanKey = 'x-datadog-parent-id'
12
12
  const originKey = 'x-datadog-origin'
13
13
  const samplingKey = 'x-datadog-sampling-priority'
14
+ const tagsKey = 'x-datadog-tags'
14
15
  const baggagePrefix = 'ot-baggage-'
15
16
  const b3TraceKey = 'x-b3-traceid'
16
17
  const b3TraceExpr = /^([0-9a-f]{16}){1,2}$/i
@@ -23,6 +24,8 @@ const b3HeaderKey = 'b3'
23
24
  const sqsdHeaderHey = 'x-aws-sqsd-attr-_datadog'
24
25
  const b3HeaderExpr = /^(([0-9a-f]{16}){1,2}-[0-9a-f]{16}(-[01d](-[0-9a-f]{16})?)?|[01d])$/i
25
26
  const baggageExpr = new RegExp(`^${baggagePrefix}(.+)$`)
27
+ const tagKeyExpr = /^_dd\.p\.[\x21-\x2b\x2d-\x7e]+$/ // ASCII minus spaces and commas
28
+ const tagValueExpr = /^[\x20-\x2b\x2d-\x7e]*$/ // ASCII minus commas
26
29
  const ddKeys = [traceKey, spanKey, samplingKey, originKey]
27
30
  const b3Keys = [b3TraceKey, b3SpanKey, b3ParentKey, b3SampledKey, b3FlagsKey, b3HeaderKey]
28
31
  const logKeys = ddKeys.concat(b3Keys)
@@ -43,6 +46,7 @@ class TextMapPropagator {
43
46
  this._injectBaggageItems(spanContext, carrier)
44
47
  this._injectB3(spanContext, carrier)
45
48
  this._injectTraceparent(spanContext, carrier)
49
+ this._injectTags(spanContext, carrier)
46
50
 
47
51
  log.debug(() => `Inject into carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
48
52
  }
@@ -79,6 +83,35 @@ class TextMapPropagator {
79
83
  })
80
84
  }
81
85
 
86
+ _injectTags (spanContext, carrier) {
87
+ const trace = spanContext._trace
88
+
89
+ if (this._config.tagsHeaderMaxLength === 0) {
90
+ log.debug('Trace tag propagation is disabled, skipping injection.')
91
+ return
92
+ }
93
+
94
+ const tags = []
95
+
96
+ for (const key in trace.tags) {
97
+ if (!trace.tags[key] || !key.startsWith('_dd.p.')) continue
98
+ if (!this._validateTagKey(key) || !this._validateTagValue(trace.tags[key])) {
99
+ log.error('Trace tags from span are invalid, skipping injection.')
100
+ return
101
+ }
102
+
103
+ tags.push(`${key}=${trace.tags[key]}`)
104
+ }
105
+
106
+ const header = tags.join(',')
107
+
108
+ if (header.length > this._config.tagsHeaderMaxLength) {
109
+ log.error('Trace tags from span are too large, skipping injection.')
110
+ } else if (header) {
111
+ carrier[tagsKey] = header
112
+ }
113
+ }
114
+
82
115
  _injectB3 (spanContext, carrier) {
83
116
  if (!this._config.experimental.b3) return
84
117
 
@@ -118,6 +151,7 @@ class TextMapPropagator {
118
151
  this._extractOrigin(carrier, spanContext)
119
152
  this._extractBaggageItems(carrier, spanContext)
120
153
  this._extractSamplingPriority(carrier, spanContext)
154
+ this._extractTags(carrier, spanContext)
121
155
  }
122
156
 
123
157
  return spanContext
@@ -273,6 +307,43 @@ class TextMapPropagator {
273
307
  }
274
308
  }
275
309
 
310
+ _extractTags (carrier, spanContext) {
311
+ if (!carrier[tagsKey]) return
312
+
313
+ const trace = spanContext._trace
314
+
315
+ if (this._config.tagsHeaderMaxLength === 0) {
316
+ log.debug('Trace tag propagation is disabled, skipping extraction.')
317
+ } else if (carrier[tagsKey].length > this._config.tagsHeaderMaxLength) {
318
+ log.error('Trace tags from carrier are too large, skipping extraction.')
319
+ } else {
320
+ const pairs = carrier[tagsKey].split(',')
321
+ const tags = {}
322
+
323
+ for (const pair of pairs) {
324
+ const [key, ...rest] = pair.split('=')
325
+ const value = rest.join('=')
326
+
327
+ if (!this._validateTagKey(key) || !this._validateTagValue(value)) {
328
+ log.error('Trace tags from carrier are invalid, skipping extraction.')
329
+ return
330
+ }
331
+
332
+ tags[key] = value
333
+ }
334
+
335
+ Object.assign(trace.tags, tags)
336
+ }
337
+ }
338
+
339
+ _validateTagKey (key) {
340
+ return tagKeyExpr.test(key)
341
+ }
342
+
343
+ _validateTagValue (value) {
344
+ return tagValueExpr.test(value)
345
+ }
346
+
276
347
  _getPriority (sampled, debug) {
277
348
  if (debug) {
278
349
  return USER_KEEP
@@ -28,7 +28,7 @@ class DatadogTracer {
28
28
  this._tags = config.tags
29
29
  this._logInjection = config.logInjection
30
30
  this._debug = config.debug
31
- this._prioritySampler = new PrioritySampler(config.env, config.experimental.sampler)
31
+ this._prioritySampler = new PrioritySampler(config.env, config.sampler)
32
32
  this._exporter = new Exporter(config, this._prioritySampler)
33
33
  this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config)
34
34
  this._url = this._exporter._url
@@ -119,9 +119,11 @@ module.exports = class PluginManager {
119
119
  const {
120
120
  logInjection,
121
121
  serviceMapping,
122
- experimental,
123
122
  queryStringObfuscation,
124
- isIntelligentTestRunnerEnabled
123
+ clientIpHeaderDisabled,
124
+ clientIpHeader,
125
+ isIntelligentTestRunnerEnabled,
126
+ experimental
125
127
  } = this._tracerConfig
126
128
 
127
129
  const sharedConfig = {}
@@ -134,6 +136,14 @@ module.exports = class PluginManager {
134
136
  sharedConfig.queryStringObfuscation = queryStringObfuscation
135
137
  }
136
138
 
139
+ if (clientIpHeaderDisabled !== undefined) {
140
+ sharedConfig.clientIpHeaderDisabled = clientIpHeaderDisabled
141
+ }
142
+
143
+ if (clientIpHeader !== undefined) {
144
+ sharedConfig.clientIpHeader = clientIpHeader
145
+ }
146
+
137
147
  if (experimental) {
138
148
  sharedConfig.isAgentlessEnabled = experimental.exporter === 'datadog'
139
149
  }
@@ -7,6 +7,7 @@ module.exports = {
7
7
  get '@google-cloud/pubsub' () { return require('../../../datadog-plugin-google-cloud-pubsub/src') },
8
8
  get '@grpc/grpc-js' () { return require('../../../datadog-plugin-grpc/src') },
9
9
  get '@hapi/hapi' () { return require('../../../datadog-plugin-hapi/src') },
10
+ get '@jest/core' () { return require('../../../datadog-plugin-jest/src') },
10
11
  get '@koa/router' () { return require('../../../datadog-plugin-koa/src') },
11
12
  get '@node-redis/client' () { return require('../../../datadog-plugin-redis/src') },
12
13
  get 'amqp10' () { return require('../../../datadog-plugin-amqp10/src') },
@@ -29,6 +30,8 @@ module.exports = {
29
30
  get 'http2' () { return require('../../../datadog-plugin-http2/src') },
30
31
  get 'https' () { return require('../../../datadog-plugin-http/src') },
31
32
  get 'ioredis' () { return require('../../../datadog-plugin-ioredis/src') },
33
+ get 'jest-circus' () { return require('../../../datadog-plugin-jest/src') },
34
+ get 'jest-config' () { return require('../../../datadog-plugin-jest/src') },
32
35
  get 'jest-environment-node' () { return require('../../../datadog-plugin-jest/src') },
33
36
  get 'jest-environment-jsdom' () { return require('../../../datadog-plugin-jest/src') },
34
37
  get 'jest-jasmine2' () { return require('../../../datadog-plugin-jest/src') },
@@ -9,25 +9,32 @@ const hasOwn = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
9
9
  function messageProxy (message, holder) {
10
10
  return new Proxy(message, {
11
11
  get (target, p, receiver) {
12
- switch (p) {
13
- case Symbol.toStringTag:
14
- return Object.prototype.toString.call(target).slice(8, -1)
15
- case 'dd':
16
- return holder.dd
17
- default:
18
- return Reflect.get(target, p, receiver)
12
+ if (p === Symbol.toStringTag) {
13
+ return Object.prototype.toString.call(target).slice(8, -1)
19
14
  }
15
+
16
+ if (shouldOverride(target, p)) {
17
+ return holder.dd
18
+ }
19
+
20
+ return Reflect.get(target, p, receiver)
20
21
  },
21
22
  ownKeys (target) {
22
23
  const ownKeys = Reflect.ownKeys(target)
23
- return hasOwn(target, 'dd') ? ownKeys : ['dd', ...ownKeys]
24
+ return hasOwn(target, 'dd') || !Reflect.isExtensible(target)
25
+ ? ownKeys
26
+ : ['dd', ...ownKeys]
24
27
  },
25
28
  getOwnPropertyDescriptor (target, p) {
26
- return Reflect.getOwnPropertyDescriptor(p === 'dd' ? holder : target, p)
29
+ return Reflect.getOwnPropertyDescriptor(shouldOverride(target, p) ? holder : target, p)
27
30
  }
28
31
  })
29
32
  }
30
33
 
34
+ function shouldOverride (target, p) {
35
+ return p === 'dd' && !Reflect.has(target, p) && Reflect.isExtensible(target)
36
+ }
37
+
31
38
  module.exports = class LogPlugin extends Plugin {
32
39
  constructor (...args) {
33
40
  super(...args)
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const semver = require('semver')
4
+
5
+ if (semver.satisfies(process.version, '>=14.18.0')) {
6
+ const net = require('net')
7
+
8
+ module.exports = net.BlockList
9
+ } else {
10
+ const ipaddr = require('ipaddr.js')
11
+
12
+ module.exports = class BlockList {
13
+ constructor () {
14
+ this.v4Ranges = []
15
+ this.v6Ranges = []
16
+ }
17
+
18
+ addSubnet (net, prefix, type) {
19
+ this[type === 'ipv4' ? 'v4Ranges' : 'v6Ranges'].push(ipaddr.parseCIDR(`${net}/${prefix}`))
20
+ }
21
+
22
+ check (address, type) {
23
+ try {
24
+ let ip = ipaddr.parse(address)
25
+
26
+ type = ip.kind()
27
+
28
+ if (type === 'ipv6') {
29
+ for (const range of this.v6Ranges) {
30
+ if (ip.match(range)) return true
31
+ }
32
+
33
+ if (ip.isIPv4MappedAddress()) {
34
+ ip = ip.toIPv4Address()
35
+ type = ip.kind()
36
+ }
37
+ }
38
+
39
+ if (type === 'ipv4') {
40
+ for (const range of this.v4Ranges) {
41
+ if (ip.match(range)) return true
42
+ }
43
+ }
44
+
45
+ return false
46
+ } catch {
47
+ return false
48
+ }
49
+ }
50
+ }
51
+ }
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const net = require('net')
3
4
  const uniq = require('lodash.uniq')
4
5
  const analyticsSampler = require('../../analytics_sampler')
5
6
  const FORMAT_HTTP_HEADERS = 'http_headers'
@@ -8,6 +9,7 @@ const tags = require('../../../../../ext/tags')
8
9
  const types = require('../../../../../ext/types')
9
10
  const kinds = require('../../../../../ext/kinds')
10
11
  const urlFilter = require('./urlfilter')
12
+ const BlockList = require('./ip_blocklist')
11
13
  const { incomingHttpRequestEnd } = require('../../appsec/gateway/channels')
12
14
 
13
15
  const WEB = types.WEB
@@ -24,12 +26,25 @@ const HTTP_ROUTE = tags.HTTP_ROUTE
24
26
  const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS
25
27
  const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS
26
28
  const HTTP_USERAGENT = tags.HTTP_USERAGENT
29
+ const HTTP_CLIENT_IP = tags.HTTP_CLIENT_IP
27
30
  const MANUAL_DROP = tags.MANUAL_DROP
28
31
 
29
32
  const HTTP2_HEADER_AUTHORITY = ':authority'
30
33
  const HTTP2_HEADER_SCHEME = ':scheme'
31
34
  const HTTP2_HEADER_PATH = ':path'
32
35
 
36
+ const ipHeaderList = [
37
+ 'x-forwarded-for',
38
+ 'x-real-ip',
39
+ 'client-ip',
40
+ 'x-forwarded',
41
+ 'x-cluster-client-ip',
42
+ 'forwarded-for',
43
+ 'forwarded',
44
+ 'via',
45
+ 'true-client-ip'
46
+ ]
47
+
33
48
  const contexts = new WeakMap()
34
49
  const ends = new WeakMap()
35
50
 
@@ -337,6 +352,42 @@ const web = {
337
352
 
338
353
  return `${path}?${qs}`
339
354
  },
355
+
356
+ extractIp (context) {
357
+ const { req, config } = context
358
+
359
+ if (config.clientIpHeaderDisabled) return
360
+
361
+ const headers = req.headers
362
+
363
+ if (config.clientIpHeader) {
364
+ const header = headers[config.clientIpHeader]
365
+ if (!header) return
366
+
367
+ return findFirstIp(header)
368
+ }
369
+
370
+ const foundHeaders = []
371
+
372
+ for (let i = 0; i < ipHeaderList.length; i++) {
373
+ if (headers[ipHeaderList[i]]) {
374
+ foundHeaders.push(ipHeaderList[i])
375
+ }
376
+ }
377
+
378
+ if (foundHeaders.length === 1) {
379
+ const header = headers[foundHeaders[0]]
380
+ const firstIp = findFirstIp(header)
381
+
382
+ if (firstIp) return firstIp
383
+ } else if (foundHeaders.length > 1) {
384
+ log.error(`Cannot find client IP: multiple IP headers detected ${foundHeaders}`)
385
+ return
386
+ }
387
+
388
+ return req.socket && req.socket.remoteAddress
389
+ },
390
+
340
391
  wrapWriteHead (context) {
341
392
  const { req, res } = context
342
393
  const writeHead = res.writeHead
@@ -392,7 +443,8 @@ function addAllowHeaders (req, res, headers) {
392
443
  'x-datadog-parent-id',
393
444
  'x-datadog-sampled', // Deprecated, but still accept it in case it's sent.
394
445
  'x-datadog-sampling-priority',
395
- 'x-datadog-trace-id'
446
+ 'x-datadog-trace-id',
447
+ 'x-datadog-tags'
396
448
  ]
397
449
 
398
450
  for (const header of contextHeaders) {
@@ -434,7 +486,8 @@ function addRequestTags (context) {
434
486
  [HTTP_METHOD]: req.method,
435
487
  [SPAN_KIND]: SERVER,
436
488
  [SPAN_TYPE]: WEB,
437
- [HTTP_USERAGENT]: req.headers['user-agent']
489
+ [HTTP_USERAGENT]: req.headers['user-agent'],
490
+ [HTTP_CLIENT_IP]: web.extractIp(context)
438
491
  })
439
492
 
440
493
  addHeaders(context)
@@ -502,6 +555,51 @@ function getProtocol (req) {
502
555
  return 'http'
503
556
  }
504
557
 
558
+ const privateCIDRs = [
559
+ '127.0.0.0/8',
560
+ '10.0.0.0/8',
561
+ '172.16.0.0/12',
562
+ '192.168.0.0/16',
563
+ '169.254.0.0/16',
564
+ '::1/128',
565
+ 'fec0::/10',
566
+ 'fe80::/10',
567
+ 'fc00::/7',
568
+ 'fd00::/8'
569
+ ]
570
+
571
+ const privateIPMatcher = new BlockList()
572
+
573
+ for (const cidr of privateCIDRs) {
574
+ const [ address, prefix ] = cidr.split('/')
575
+
576
+ privateIPMatcher.addSubnet(address, parseInt(prefix), net.isIPv6(address) ? 'ipv6' : 'ipv4')
577
+ }
578
+
579
+ function findFirstIp (str) {
580
+ let firstPrivateIp
581
+ const splitted = str.split(',')
582
+
583
+ for (let i = 0; i < splitted.length; i++) {
584
+ const chunk = splitted[i].trim()
585
+
586
+ // TODO: strip port and interface data ?
587
+
588
+ const type = net.isIP(chunk)
589
+ if (!type) continue
590
+
591
+ if (!privateIPMatcher.check(chunk, type === 6 ? 'ipv6' : 'ipv4')) {
592
+ // it's public, return it immediately
593
+ return chunk
594
+ }
595
+
596
+ // it's private, only save the first one found
597
+ if (!firstPrivateIp) firstPrivateIp = chunk
598
+ }
599
+
600
+ return firstPrivateIp
601
+ }
602
+
505
603
  function getHeadersToRecord (config) {
506
604
  if (Array.isArray(config.headers)) {
507
605
  try {
@@ -6,9 +6,14 @@ const ext = require('../../../ext')
6
6
  const { setSamplingRules } = require('./startup-log')
7
7
 
8
8
  const {
9
+ SAMPLING_MECHANISM_DEFAULT,
10
+ SAMPLING_MECHANISM_AGENT,
11
+ SAMPLING_MECHANISM_RULE,
12
+ SAMPLING_MECHANISM_MANUAL,
9
13
  SAMPLING_RULE_DECISION,
10
14
  SAMPLING_LIMIT_DECISION,
11
- SAMPLING_AGENT_DECISION
15
+ SAMPLING_AGENT_DECISION,
16
+ DECISION_MAKER_KEY
12
17
  } = require('./constants')
13
18
 
14
19
  const SERVICE_NAME = ext.tags.SERVICE_NAME
@@ -45,6 +50,7 @@ class PrioritySampler {
45
50
  const context = this._getContext(span)
46
51
  const root = context._trace.started[0]
47
52
 
53
+ // TODO: remove the decision maker tag when priority is less than AUTO_KEEP
48
54
  if (context._sampling.priority !== undefined) return
49
55
  if (!root) return // noop span
50
56
 
@@ -52,9 +58,14 @@ class PrioritySampler {
52
58
 
53
59
  if (this.validate(tag)) {
54
60
  context._sampling.priority = tag
61
+ context._sampling.mechanism = SAMPLING_MECHANISM_MANUAL
55
62
  } else if (auto) {
56
63
  context._sampling.priority = this._getPriorityFromAuto(root)
64
+ } else {
65
+ return
57
66
  }
67
+
68
+ this._addDecisionMaker(root)
58
69
  }
59
70
 
60
71
  update (rates) {
@@ -115,6 +126,7 @@ class PrioritySampler {
115
126
 
116
127
  _getPriorityByRule (context, rule) {
117
128
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
129
+ context._sampling.mechanism = SAMPLING_MECHANISM_RULE
118
130
 
119
131
  return rule.sampler.isSampled(context) && this._isSampledByRateLimit(context) ? USER_KEEP : USER_REJECT
120
132
  }
@@ -133,10 +145,33 @@ class PrioritySampler {
133
145
 
134
146
  context._trace[SAMPLING_AGENT_DECISION] = sampler.rate()
135
147
 
148
+ if (sampler === defaultSampler) {
149
+ context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
150
+ } else {
151
+ context._sampling.mechanism = SAMPLING_MECHANISM_AGENT
152
+ }
153
+
136
154
  return sampler.isSampled(context) ? AUTO_KEEP : AUTO_REJECT
137
155
  }
138
156
 
157
+ _addDecisionMaker (span) {
158
+ const context = span.context()
159
+ const trace = context._trace
160
+ const priority = context._sampling.priority
161
+ const mechanism = context._sampling.mechanism
162
+
163
+ if (priority >= AUTO_KEEP) {
164
+ if (!trace.tags[DECISION_MAKER_KEY]) {
165
+ trace.tags[DECISION_MAKER_KEY] = `-${mechanism}`
166
+ }
167
+ } else {
168
+ delete trace.tags[DECISION_MAKER_KEY]
169
+ }
170
+ }
171
+
139
172
  _normalizeRules (rules, sampleRate) {
173
+ rules = [].concat(rules || [])
174
+
140
175
  return rules
141
176
  .concat({ sampleRate })
142
177
  .map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) }))
@@ -48,6 +48,9 @@ class Tracer extends NoopProxy {
48
48
  if (config.appsec.enabled) {
49
49
  require('./appsec').enable(config)
50
50
  }
51
+ if (config.iast.enabled) {
52
+ require('./appsec/iast').enable(config)
53
+ }
51
54
 
52
55
  this._tracer = new DatadogTracer(config)
53
56
  this._pluginManager.configure(config)
@@ -3,6 +3,7 @@
3
3
  const path = require('path')
4
4
  const Module = require('module')
5
5
  const parse = require('module-details-from-path')
6
+ const dc = require('diagnostics_channel')
6
7
 
7
8
  const origRequire = Module.prototype.require
8
9
 
@@ -14,7 +15,7 @@ let moduleHooks = Object.create(null)
14
15
  let cache = Object.create(null)
15
16
  let patching = Object.create(null)
16
17
  let patchedRequire = null
17
-
18
+ const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
18
19
  function Hook (modules, options, onrequire) {
19
20
  if (!(this instanceof Hook)) return new Hook(modules, options, onrequire)
20
21
  if (typeof modules === 'function') {
@@ -78,6 +79,14 @@ function Hook (modules, options, onrequire) {
78
79
  // so the patching mark can be cleaned up.
79
80
  delete patching[filename]
80
81
 
82
+ if (moduleLoadStartChannel.hasSubscribers) {
83
+ moduleLoadStartChannel.publish({
84
+ filename,
85
+ module: exports,
86
+ request
87
+ })
88
+ }
89
+
81
90
  if (core) {
82
91
  hooks = moduleHooks[filename]
83
92
  if (!hooks) return exports // abort if module name isn't on whitelist
@@ -3,6 +3,8 @@
3
3
  const log = require('./log')
4
4
  const format = require('./format')
5
5
 
6
+ const { SpanStatsProcessor } = require('./span_stats')
7
+
6
8
  const startedSpans = new WeakSet()
7
9
  const finishedSpans = new WeakSet()
8
10
 
@@ -11,6 +13,8 @@ class SpanProcessor {
11
13
  this._exporter = exporter
12
14
  this._prioritySampler = prioritySampler
13
15
  this._config = config
16
+
17
+ this._stats = new SpanStatsProcessor(config)
14
18
  }
15
19
 
16
20
  process (span) {
@@ -26,7 +30,9 @@ class SpanProcessor {
26
30
 
27
31
  for (const span of started) {
28
32
  if (span._duration !== undefined) {
29
- formatted.push(format(span))
33
+ const formattedSpan = format(span)
34
+ this._stats.onSpanFinished(formattedSpan)
35
+ formatted.push(formattedSpan)
30
36
  } else {
31
37
  active.push(span)
32
38
  }