dd-trace 2.12.2 → 2.15.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 (76) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/ext/tags.d.ts +2 -1
  3. package/ext/tags.js +2 -1
  4. package/index.d.ts +43 -20
  5. package/package.json +5 -3
  6. package/packages/datadog-core/src/storage/async_resource.js +19 -1
  7. package/packages/datadog-instrumentations/index.js +1 -52
  8. package/packages/datadog-instrumentations/src/crypto.js +30 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +15 -0
  10. package/packages/datadog-instrumentations/src/fs.js +11 -0
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +70 -0
  12. package/packages/datadog-instrumentations/src/helpers/instrument.js +5 -34
  13. package/packages/datadog-instrumentations/src/helpers/instrumentations.js +7 -0
  14. package/packages/datadog-instrumentations/src/helpers/register.js +59 -0
  15. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  16. package/packages/datadog-instrumentations/src/jest.js +33 -11
  17. package/packages/datadog-instrumentations/src/net.js +13 -0
  18. package/packages/datadog-plugin-cucumber/src/index.js +4 -0
  19. package/packages/datadog-plugin-fs/src/index.js +72 -38
  20. package/packages/datadog-plugin-jest/src/index.js +25 -4
  21. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  22. package/packages/datadog-plugin-mongodb-core/src/index.js +32 -8
  23. package/packages/datadog-plugin-oracledb/src/index.js +12 -4
  24. package/packages/dd-trace/index.js +1 -1
  25. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -0
  26. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +20 -0
  27. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +48 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
  29. package/packages/dd-trace/src/appsec/iast/iast-context.js +50 -0
  30. package/packages/dd-trace/src/appsec/iast/index.js +59 -0
  31. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +94 -0
  32. package/packages/dd-trace/src/appsec/iast/path-line.js +70 -0
  33. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +113 -0
  34. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +50 -0
  35. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +53 -8
  36. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +23 -24
  37. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +220 -0
  38. package/packages/dd-trace/src/config.js +89 -10
  39. package/packages/dd-trace/src/constants.js +9 -1
  40. package/packages/dd-trace/src/encode/0.4.js +51 -58
  41. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +13 -34
  42. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +84 -0
  43. package/packages/dd-trace/src/encode/span-stats.js +155 -0
  44. package/packages/dd-trace/src/exporters/agent/index.js +25 -7
  45. package/packages/dd-trace/src/exporters/agent/writer.js +7 -4
  46. package/packages/dd-trace/src/{profiling/exporters → exporters/common}/form-data.js +0 -0
  47. package/packages/dd-trace/src/exporters/common/request.js +25 -10
  48. package/packages/dd-trace/src/exporters/common/writer.js +9 -6
  49. package/packages/dd-trace/src/exporters/span-stats/index.js +20 -0
  50. package/packages/dd-trace/src/exporters/span-stats/writer.js +54 -0
  51. package/packages/dd-trace/src/format.js +2 -0
  52. package/packages/dd-trace/src/id.js +16 -13
  53. package/packages/dd-trace/src/iitm.js +11 -0
  54. package/packages/dd-trace/src/index.js +10 -0
  55. package/packages/dd-trace/src/noop/proxy.js +87 -0
  56. package/packages/dd-trace/src/opentracing/propagation/text_map.js +77 -6
  57. package/packages/dd-trace/src/opentracing/tracer.js +1 -1
  58. package/packages/dd-trace/src/plugin_manager.js +107 -65
  59. package/packages/dd-trace/src/plugins/index.js +58 -45
  60. package/packages/dd-trace/src/plugins/log_plugin.js +16 -9
  61. package/packages/dd-trace/src/plugins/util/ci.js +34 -9
  62. package/packages/dd-trace/src/plugins/util/git.js +52 -2
  63. package/packages/dd-trace/src/plugins/util/ip_blocklist.js +25 -0
  64. package/packages/dd-trace/src/plugins/util/tags.js +4 -1
  65. package/packages/dd-trace/src/plugins/util/web.js +99 -2
  66. package/packages/dd-trace/src/priority_sampler.js +36 -1
  67. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  68. package/packages/dd-trace/src/proxy.js +23 -89
  69. package/packages/dd-trace/src/ritm.js +10 -1
  70. package/packages/dd-trace/src/span_processor.js +7 -1
  71. package/packages/dd-trace/src/span_stats.js +210 -0
  72. package/packages/dd-trace/src/startup-log.js +8 -19
  73. package/packages/dd-trace/src/telemetry/dependencies.js +83 -0
  74. package/packages/dd-trace/src/{telemetry.js → telemetry/index.js} +11 -79
  75. package/packages/dd-trace/src/telemetry/send-data.js +35 -0
  76. package/scripts/install_plugin_modules.js +17 -26
@@ -1,10 +1,18 @@
1
1
  'use strict'
2
2
 
3
+ // The `fs` plugin is an old style plugin that has not been updated for the new
4
+ // plugin system and was hacked in for backward compatibility with 2.x.
5
+
3
6
  const { storage } = require('../../datadog-core')
7
+ const { channel } = require('../../datadog-instrumentations/src/helpers/instrument')
8
+ const shimmer = require('../../datadog-shimmer')
9
+ const Plugin = require('../../dd-trace/src/plugins/plugin')
4
10
 
5
11
  let kDirReadPromisified
6
12
  let kDirClosePromisified
7
13
  let kHandle
14
+ let fsConfig
15
+ let fsInstance
8
16
 
9
17
  const ddFhSym = Symbol('ddFileHandle')
10
18
 
@@ -144,8 +152,8 @@ function createWrapDirAsyncIterator (config, tracer, instrumenter) {
144
152
  }
145
153
  }
146
154
  }
147
- instrumenter.wrap(this, kDirReadPromisified, createWrapDirRead(config, tracer))
148
- instrumenter.wrap(this, kDirClosePromisified, createWrapKDirClose(config, tracer, instrumenter))
155
+ shimmer.wrap(this, kDirReadPromisified, createWrapDirRead(config, tracer))
156
+ shimmer.wrap(this, kDirClosePromisified, createWrapKDirClose(config, tracer, instrumenter))
149
157
  return asyncIterator.apply(this, arguments)
150
158
  }
151
159
  }
@@ -158,8 +166,8 @@ function createWrapKDirClose (config, tracer, instrumenter) {
158
166
  return tracer.trace('fs.operation', { tags, orphanable }, (span) => {
159
167
  const p = kDirClose.apply(this, arguments)
160
168
  const unwrapBoth = () => {
161
- instrumenter.unwrap(this, kDirReadPromisified)
162
- instrumenter.unwrap(this, kDirClosePromisified)
169
+ shimmer.unwrap(this, kDirReadPromisified)
170
+ shimmer.unwrap(this, kDirClosePromisified)
163
171
  }
164
172
  p.then(unwrapBoth, unwrapBoth)
165
173
  return p
@@ -339,7 +347,7 @@ function makeFSTags (resourceName, path, options, config, tracer) {
339
347
  'component': 'fs',
340
348
  'span.kind': 'internal',
341
349
  'resource.name': resourceName,
342
- 'service.name': config.service || tracer._service
350
+ 'service.name': fsConfig.service || tracer._service
343
351
  }
344
352
 
345
353
  switch (typeof path) {
@@ -399,9 +407,9 @@ function patchClassicFunctions (fs, tracer, config) {
399
407
  if (tagMakerName in tagMakers) {
400
408
  const tagMaker = tagMakers[tagMakerName]
401
409
  if (name.endsWith('Sync')) {
402
- this.wrap(fs, name, createWrap(tracer, config, name, tagMaker))
410
+ shimmer.wrap(fs, name, createWrap(tracer, config, name, tagMaker))
403
411
  } else {
404
- this.wrap(fs, name, createWrapCb(tracer, config, name, tagMaker))
412
+ shimmer.wrap(fs, name, createWrapCb(tracer, config, name, tagMaker))
405
413
  }
406
414
  if (name in promisifiable) {
407
415
  copySymbols(original, fs[name])
@@ -424,8 +432,6 @@ function patchFileHandle (fs, tracer, config) {
424
432
  tagMaker = createFDTags
425
433
  }
426
434
 
427
- const instrumenter = this
428
-
429
435
  const desc = Reflect.getOwnPropertyDescriptor(fileHandlePrototype, kHandle)
430
436
  if (!desc || !desc.get) {
431
437
  Reflect.defineProperty(fileHandlePrototype, kHandle, {
@@ -434,13 +440,13 @@ function patchFileHandle (fs, tracer, config) {
434
440
  },
435
441
  set (h) {
436
442
  this[ddFhSym] = h
437
- instrumenter.wrap(this, 'close', createWrap(tracer, config, 'filehandle.close', tagMakers.close))
443
+ shimmer.wrap(this, 'close', createWrap(tracer, config, 'filehandle.close', tagMakers.close))
438
444
  },
439
445
  configurable: true
440
446
  })
441
447
  }
442
448
 
443
- this.wrap(fileHandlePrototype, name, createWrap(tracer, config, 'filehandle.' + name, tagMaker))
449
+ shimmer.wrap(fileHandlePrototype, name, createWrap(tracer, config, 'filehandle.' + name, tagMaker))
444
450
  }
445
451
  })
446
452
  }
@@ -449,17 +455,17 @@ function patchPromiseFunctions (fs, tracer, config) {
449
455
  for (const name in fs.promises) {
450
456
  if (name in tagMakers) {
451
457
  const tagMaker = tagMakers[name]
452
- this.wrap(fs.promises, name, createWrap(tracer, config, 'promises.' + name, tagMaker))
458
+ shimmer.wrap(fs.promises, name, createWrap(tracer, config, 'promises.' + name, tagMaker))
453
459
  }
454
460
  }
455
461
  }
456
462
 
457
463
  function patchDirFunctions (fs, tracer, config) {
458
- this.wrap(fs.Dir.prototype, 'close', createWrapDirClose(config, tracer))
459
- this.wrap(fs.Dir.prototype, 'closeSync', createWrapDirClose(config, tracer, true))
460
- this.wrap(fs.Dir.prototype, 'read', createWrapDirRead(config, tracer))
461
- this.wrap(fs.Dir.prototype, 'readSync', createWrapDirRead(config, tracer, true))
462
- this.wrap(fs.Dir.prototype, Symbol.asyncIterator, createWrapDirAsyncIterator(config, tracer, this))
464
+ shimmer.wrap(fs.Dir.prototype, 'close', createWrapDirClose(config, tracer))
465
+ shimmer.wrap(fs.Dir.prototype, 'closeSync', createWrapDirClose(config, tracer, true))
466
+ shimmer.wrap(fs.Dir.prototype, 'read', createWrapDirRead(config, tracer))
467
+ shimmer.wrap(fs.Dir.prototype, 'readSync', createWrapDirRead(config, tracer, true))
468
+ shimmer.wrap(fs.Dir.prototype, Symbol.asyncIterator, createWrapDirAsyncIterator(config, tracer, this))
463
469
  }
464
470
 
465
471
  function unpatchClassicFunctions (fs) {
@@ -467,7 +473,7 @@ function unpatchClassicFunctions (fs) {
467
473
  if (!fs[name]) continue
468
474
  const tagMakerName = name.endsWith('Sync') ? name.substr(0, name.length - 4) : name
469
475
  if (tagMakerName in tagMakers) {
470
- this.unwrap(fs, name)
476
+ shimmer.unwrap(fs, name)
471
477
  }
472
478
  }
473
479
  }
@@ -478,7 +484,7 @@ function unpatchFileHandle (fs) {
478
484
  if (typeof name !== 'string' || name === 'constructor' || name === 'fd' || name === 'getAsyncId') {
479
485
  continue
480
486
  }
481
- this.unwrap(fileHandlePrototype, name)
487
+ shimmer.unwrap(fileHandlePrototype, name)
482
488
  }
483
489
  delete fileHandlePrototype[kHandle]
484
490
  })
@@ -487,22 +493,46 @@ function unpatchFileHandle (fs) {
487
493
  function unpatchPromiseFunctions (fs) {
488
494
  for (const name in fs.promises) {
489
495
  if (name in tagMakers) {
490
- this.unwrap(fs.promises, name)
496
+ shimmer.unwrap(fs.promises, name)
491
497
  }
492
498
  }
493
499
  }
494
500
 
495
501
  function unpatchDirFunctions (fs) {
496
- this.unwrap(fs.Dir.prototype, 'close')
497
- this.unwrap(fs.Dir.prototype, 'closeSync')
498
- this.unwrap(fs.Dir.prototype, 'read')
499
- this.unwrap(fs.Dir.prototype, 'readSync')
500
- this.unwrap(fs.Dir.prototype, Symbol.asyncIterator)
502
+ shimmer.unwrap(fs.Dir.prototype, 'close')
503
+ shimmer.unwrap(fs.Dir.prototype, 'closeSync')
504
+ shimmer.unwrap(fs.Dir.prototype, 'read')
505
+ shimmer.unwrap(fs.Dir.prototype, 'readSync')
506
+ shimmer.unwrap(fs.Dir.prototype, Symbol.asyncIterator)
501
507
  }
502
508
 
503
- module.exports = {
504
- name: 'fs',
505
- patch (fs, tracer, config) {
509
+ const hookChannel = channel('apm:fs:hook')
510
+
511
+ hookChannel.subscribe(fs => {
512
+ fsInstance = fs
513
+ })
514
+
515
+ class FsPlugin extends Plugin {
516
+ static get name () {
517
+ return 'fs'
518
+ }
519
+
520
+ configure (config) {
521
+ fsConfig = config
522
+
523
+ super.configure(config)
524
+
525
+ this._unpatch()
526
+
527
+ if (this._enabled) {
528
+ this._patch()
529
+ }
530
+ }
531
+
532
+ _patch () {
533
+ const fs = fsInstance
534
+ const tracer = this.tracer
535
+ const config = this.config
506
536
  const realpathNative = fs.realpath.native
507
537
  const realpathSyncNative = fs.realpathSync.native
508
538
  patchClassicFunctions.call(this, fs, tracer, config)
@@ -513,18 +543,20 @@ module.exports = {
513
543
  if (fs.Dir) {
514
544
  patchDirFunctions.call(this, fs, tracer, config)
515
545
  }
516
- this.wrap(fs, 'createReadStream', createWrapCreateReadStream(config, tracer))
517
- this.wrap(fs, 'createWriteStream', createWrapCreateWriteStream(config, tracer))
518
- this.wrap(fs, 'existsSync', createWrap(tracer, config, 'existsSync', createPathTags))
519
- this.wrap(fs, 'exists', createWrapExists(config, tracer))
546
+ shimmer.wrap(fs, 'createReadStream', createWrapCreateReadStream(config, tracer))
547
+ shimmer.wrap(fs, 'createWriteStream', createWrapCreateWriteStream(config, tracer))
548
+ shimmer.wrap(fs, 'existsSync', createWrap(tracer, config, 'existsSync', createPathTags))
549
+ shimmer.wrap(fs, 'exists', createWrapExists(config, tracer))
520
550
  if (realpathNative) {
521
551
  fs.realpath.native = createWrapCb(tracer, config, 'realpath.native', createPathTags)(realpathNative)
522
552
  }
523
553
  if (realpathSyncNative) {
524
554
  fs.realpathSync.native = createWrap(tracer, config, 'realpath.native', createPathTags)(realpathSyncNative)
525
555
  }
526
- },
527
- unpatch (fs) {
556
+ }
557
+
558
+ _unpatch () {
559
+ const fs = fsInstance
528
560
  unpatchClassicFunctions.call(this, fs)
529
561
  if (fs.promises) {
530
562
  unpatchFileHandle.call(this, fs)
@@ -533,13 +565,15 @@ module.exports = {
533
565
  if (fs.Dir) {
534
566
  unpatchDirFunctions.call(this, fs)
535
567
  }
536
- this.unwrap(fs, 'createReadStream')
537
- this.unwrap(fs, 'createWriteStream')
538
- this.unwrap(fs, 'existsSync')
539
- this.unwrap(fs, 'exists')
568
+ shimmer.unwrap(fs, 'createReadStream')
569
+ shimmer.unwrap(fs, 'createWriteStream')
570
+ shimmer.unwrap(fs, 'existsSync')
571
+ shimmer.unwrap(fs, 'exists')
540
572
  }
541
573
  }
542
574
 
575
+ module.exports = FsPlugin
576
+
543
577
  /** TODO fs functions:
544
578
 
545
579
  unwatchFile
@@ -15,6 +15,9 @@ const {
15
15
  TEST_CODE_OWNERS
16
16
  } = require('../../dd-trace/src/plugins/util/test')
17
17
 
18
+ // https://github.com/facebook/jest/blob/d6ad15b0f88a05816c2fe034dd6900d28315d570/packages/jest-worker/src/types.ts#L38
19
+ const CHILD_MESSAGE_END = 2
20
+
18
21
  function getTestSpanMetadata (tracer, test) {
19
22
  const childOf = getTestParentSpan(tracer)
20
23
 
@@ -38,9 +41,31 @@ class JestPlugin extends Plugin {
38
41
  constructor (...args) {
39
42
  super(...args)
40
43
 
44
+ // Used to handle the end of a jest worker to be able to flush
45
+ const handler = ([message]) => {
46
+ if (message === CHILD_MESSAGE_END) {
47
+ this.tracer._exporter._writer.flush(() => {
48
+ // eslint-disable-next-line
49
+ // https://github.com/facebook/jest/blob/24ed3b5ecb419c023ee6fdbc838f07cc028fc007/packages/jest-worker/src/workers/processChild.ts#L118-L133
50
+ // Only after the flush is done we clean up open handles
51
+ // so the worker process can hopefully exit gracefully
52
+ process.removeListener('message', handler)
53
+ })
54
+ }
55
+ }
56
+ process.on('message', handler)
57
+
41
58
  this.testEnvironmentMetadata = getTestEnvironmentMetadata('jest', this.config)
42
59
  this.codeOwnersEntries = getCodeOwnersFileEntries()
43
60
 
61
+ this.addSub('ci:jest:test:code-coverage', (coverageFiles) => {
62
+ if (!this.config.isAgentlessEnabled || !this.config.isIntelligentTestRunnerEnabled) {
63
+ return
64
+ }
65
+ const testSpan = storage.getStore().span
66
+ this.tracer._exporter.exportCoverage({ testSpan, coverageFiles })
67
+ })
68
+
44
69
  this.addSub('ci:jest:test:start', (test) => {
45
70
  const store = storage.getStore()
46
71
  const span = this.startTestSpan(test)
@@ -55,10 +80,6 @@ class JestPlugin extends Plugin {
55
80
  finishAllTraceSpans(span)
56
81
  })
57
82
 
58
- this.addSub('ci:jest:test-suite:finish', () => {
59
- this.tracer._exporter._writer.flush()
60
- })
61
-
62
83
  this.addSub('ci:jest:test:err', (error) => {
63
84
  if (error) {
64
85
  const span = storage.getStore().span
@@ -161,12 +161,12 @@ class MochaPlugin extends Plugin {
161
161
  const testSuiteSpan = this._testSuites.get(test.parent)
162
162
 
163
163
  if (testSuiteSpan) {
164
- const testSuiteId = testSuiteSpan.context()._spanId.toString('hex')
164
+ const testSuiteId = testSuiteSpan.context()._spanId.toString(16)
165
165
  testSuiteTags[TEST_SUITE_ID] = testSuiteId
166
166
  }
167
167
 
168
168
  if (this.testSessionSpan) {
169
- const testSessionId = this.testSessionSpan.context()._traceId.toString('hex')
169
+ const testSessionId = this.testSessionSpan.context()._traceId.toString(16)
170
170
  testSuiteTags[TEST_SESSION_ID] = testSessionId
171
171
  testSuiteTags[TEST_COMMAND] = this.command
172
172
  }
@@ -14,7 +14,7 @@ class MongodbCorePlugin extends Plugin {
14
14
 
15
15
  this.addSub(`apm:mongodb:query:start`, ({ ns, ops, options, name }) => {
16
16
  const query = getQuery(ops)
17
- const resource = getResource(ns, query, name)
17
+ const resource = truncate(getResource(ns, query, name))
18
18
  const store = storage.getStore()
19
19
  const childOf = store ? store.span : store
20
20
  const span = this.tracer.startSpan('mongodb.query', {
@@ -55,8 +55,8 @@ class MongodbCorePlugin extends Plugin {
55
55
 
56
56
  function getQuery (cmd) {
57
57
  if (!cmd || typeof cmd !== 'object' || Array.isArray(cmd)) return
58
- if (cmd.query) return JSON.stringify(sanitize(cmd.query))
59
- if (cmd.filter) return JSON.stringify(sanitize(cmd.filter))
58
+ if (cmd.query) return JSON.stringify(limitDepth(cmd.query))
59
+ if (cmd.filter) return JSON.stringify(limitDepth(cmd.filter))
60
60
  }
61
61
 
62
62
  function getResource (ns, query, operationName) {
@@ -69,12 +69,25 @@ function getResource (ns, query, operationName) {
69
69
  return parts.join(' ')
70
70
  }
71
71
 
72
+ function truncate (input) {
73
+ return input.slice(0, Math.min(input.length, 10000))
74
+ }
75
+
76
+ function shouldSimplify (input) {
77
+ return !isObject(input)
78
+ }
79
+
72
80
  function shouldHide (input) {
73
- return !isObject(input) || Buffer.isBuffer(input) || isBSON(input)
81
+ return Buffer.isBuffer(input) || typeof input === 'function' || isBinary(input)
74
82
  }
75
83
 
76
- function sanitize (input) {
84
+ function limitDepth (input) {
85
+ if (isBSON(input)) {
86
+ input = input.toJSON()
87
+ }
88
+
77
89
  if (shouldHide(input)) return '?'
90
+ if (shouldSimplify(input)) return input
78
91
 
79
92
  const output = {}
80
93
  const queue = [{
@@ -91,9 +104,16 @@ function sanitize (input) {
91
104
  for (const key in input) {
92
105
  if (typeof input[key] === 'function') continue
93
106
 
94
- const child = input[key]
95
- if (depth >= 20 || shouldHide(child)) {
107
+ let child = input[key]
108
+
109
+ if (isBSON(child)) {
110
+ child = child.toJSON()
111
+ }
112
+
113
+ if (depth >= 10 || shouldHide(child)) {
96
114
  output[key] = '?'
115
+ } else if (shouldSimplify(child)) {
116
+ output[key] = child
97
117
  } else {
98
118
  queue.push({
99
119
  input: child,
@@ -112,7 +132,11 @@ function isObject (val) {
112
132
  }
113
133
 
114
134
  function isBSON (val) {
115
- return val && val._bsontype
135
+ return val && val._bsontype && !isBinary(val)
136
+ }
137
+
138
+ function isBinary (val) {
139
+ return val && val._bsontype === 'Binary'
116
140
  }
117
141
 
118
142
  module.exports = MongodbCorePlugin
@@ -3,6 +3,7 @@
3
3
  const Plugin = require('../../dd-trace/src/plugins/plugin')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
+ const log = require('../../dd-trace/src/log')
6
7
 
7
8
  class OracledbPlugin extends Plugin {
8
9
  static get name () {
@@ -14,18 +15,25 @@ class OracledbPlugin extends Plugin {
14
15
 
15
16
  this.addSub('apm:oracledb:execute:start', ({ query, connAttrs }) => {
16
17
  const service = getServiceName(this.tracer, this.config, connAttrs)
17
- const connectStringObj = new URL('http://' + connAttrs.connectString)
18
+ let connectStringObj
19
+ try {
20
+ connectStringObj = new URL(`http://${connAttrs.connectString}`)
21
+ } catch (e) {
22
+ log.error(e)
23
+ }
18
24
  const tags = {
19
25
  'span.kind': 'client',
20
26
  'span.type': 'sql',
21
27
  'sql.query': query,
22
- 'db.instance': connectStringObj.pathname.substring(1),
23
- 'db.hostname': connectStringObj.hostname,
24
28
  'db.user': this.config.user,
25
- 'db.port': connectStringObj.port,
26
29
  'resource.name': query,
27
30
  'service.name': service
28
31
  }
32
+ if (typeof connectStringObj !== 'undefined') {
33
+ tags['db.instance'] = connectStringObj.pathname.substring(1)
34
+ tags['db.hostname'] = connectStringObj.hostname
35
+ tags['db.port'] = connectStringObj.port
36
+ }
29
37
  const store = storage.getStore()
30
38
  const childOf = store ? store.span : store
31
39
  const span = this.tracer.startSpan('oracle.query', {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  if (!global._ddtrace) {
4
- const TracerProxy = require('./src/proxy')
4
+ const TracerProxy = require('./src')
5
5
 
6
6
  Object.defineProperty(global, '_ddtrace', {
7
7
  value: new TracerProxy(),
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ 'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer')
3
+ }
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const analyzers = require('./analyzers')
4
+
5
+ function enableAllAnalyzers () {
6
+ for (const analyzer in analyzers) {
7
+ analyzers[analyzer].configure(true)
8
+ }
9
+ }
10
+
11
+ function disableAllAnalyzers () {
12
+ for (const analyzer in analyzers) {
13
+ analyzers[analyzer].configure(false)
14
+ }
15
+ }
16
+
17
+ module.exports = {
18
+ enableAllAnalyzers,
19
+ disableAllAnalyzers
20
+ }
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ const Plugin = require('../../../../src/plugins/plugin')
4
+ const { storage } = require('../../../../../datadog-core')
5
+ const { getFirstNonDDPathAndLine } = require('./../path-line')
6
+ const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
7
+ const { getIastContext } = require('../iast-context')
8
+ const overheadController = require('../overhead-controller')
9
+
10
+ class Analyzer extends Plugin {
11
+ constructor (type) {
12
+ super()
13
+ this._type = type
14
+ }
15
+
16
+ _isVulnerable (value, context) {
17
+ return false
18
+ }
19
+
20
+ _report (value, context) {
21
+ const evidence = this._getEvidence(value)
22
+ const location = this._getLocation()
23
+ const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
24
+ const vulnerability = createVulnerability(this._type, evidence, spanId, location)
25
+ addVulnerability(context, vulnerability)
26
+ }
27
+
28
+ _getEvidence (value) {
29
+ return { value }
30
+ }
31
+
32
+ _getLocation () {
33
+ return getFirstNonDDPathAndLine()
34
+ }
35
+
36
+ analyze (value) {
37
+ const iastContext = getIastContext(storage.getStore())
38
+ if (iastContext && this._isVulnerable(value, iastContext) && this._checkOCE(iastContext)) {
39
+ this._report(value, iastContext)
40
+ }
41
+ }
42
+
43
+ _checkOCE (context) {
44
+ return overheadController.hasQuota(overheadController.OPERATIONS.REPORT_VULNERABILITY, context)
45
+ }
46
+ }
47
+
48
+ module.exports = Analyzer
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+ const Analyzer = require('./vulnerability-analyzer')
3
+
4
+ const INSECURE_HASH_ALGORITHMS = new Set([
5
+ 'md4', 'md4WithRSAEncryption', 'RSA-MD4',
6
+ 'RSA-MD5', 'md5', 'md5-sha1', 'ssl3-md5', 'md5WithRSAEncryption',
7
+ 'RSA-SHA1', 'RSA-SHA1-2', 'sha1', 'md5-sha1', 'sha1WithRSAEncryption', 'ssl3-sha1'
8
+ ].map(algorithm => algorithm.toLowerCase()))
9
+
10
+ class WeakHashAnalyzer extends Analyzer {
11
+ constructor () {
12
+ super('WEAK_HASH')
13
+ this.addSub('datadog:crypto:hashing:start', ({ algorithm }) => this.analyze(algorithm))
14
+ }
15
+
16
+ _isVulnerable (algorithm) {
17
+ if (typeof algorithm === 'string') {
18
+ return INSECURE_HASH_ALGORITHMS.has(algorithm.toLowerCase())
19
+ }
20
+ return false
21
+ }
22
+ }
23
+
24
+ module.exports = new WeakHashAnalyzer()
@@ -0,0 +1,50 @@
1
+ const IAST_CONTEXT_KEY = Symbol('_dd.iast.context')
2
+
3
+ function getIastContext (store) {
4
+ return store && store[IAST_CONTEXT_KEY]
5
+ }
6
+
7
+ /* TODO Fix storage problem when the close event is called without
8
+ finish event to remove `topContext` references
9
+ We have to save the context in two places, because
10
+ clean can be called when the storage store is not available
11
+ */
12
+ function saveIastContext (store, topContext, context) {
13
+ if (store && topContext) {
14
+ store[IAST_CONTEXT_KEY] = context
15
+ topContext[IAST_CONTEXT_KEY] = context
16
+ return store[IAST_CONTEXT_KEY]
17
+ }
18
+ }
19
+
20
+ /* TODO Fix storage problem when the close event is called without
21
+ finish event to remove `topContext` references
22
+ iastContext is currently saved in store and request rootContext
23
+ to fix problems with `close` without `finish` events
24
+ */
25
+ function cleanIastContext (store, context, iastContext) {
26
+ if (store) {
27
+ if (!iastContext) {
28
+ iastContext = store[IAST_CONTEXT_KEY]
29
+ }
30
+ store[IAST_CONTEXT_KEY] = null
31
+ }
32
+ if (context) {
33
+ if (!iastContext) {
34
+ iastContext = context[IAST_CONTEXT_KEY]
35
+ }
36
+ context[IAST_CONTEXT_KEY] = null
37
+ }
38
+ if (iastContext) {
39
+ Object.keys(iastContext).forEach(key => delete iastContext[key])
40
+ return true
41
+ }
42
+ return false
43
+ }
44
+
45
+ module.exports = {
46
+ getIastContext,
47
+ saveIastContext,
48
+ cleanIastContext,
49
+ IAST_CONTEXT_KEY
50
+ }
@@ -0,0 +1,59 @@
1
+ const { sendVulnerabilities } = require('./vulnerability-reporter')
2
+ const { enableAllAnalyzers, disableAllAnalyzers } = require('./analyzers')
3
+ const web = require('../../plugins/util/web')
4
+ const { storage } = require('../../../../datadog-core')
5
+ const overheadController = require('./overhead-controller')
6
+ const dc = require('diagnostics_channel')
7
+ const { saveIastContext, getIastContext, cleanIastContext } = require('./iast-context')
8
+
9
+ // TODO Change to `apm:http:server:request:[start|close]` when the subscription
10
+ // order of the callbacks can be enforce
11
+ const requestStart = dc.channel('dd-trace:incomingHttpRequestStart')
12
+ const requestClose = dc.channel('dd-trace:incomingHttpRequestEnd')
13
+
14
+ function enable (config) {
15
+ enableAllAnalyzers()
16
+ requestStart.subscribe(onIncomingHttpRequestStart)
17
+ requestClose.subscribe(onIncomingHttpRequestEnd)
18
+ overheadController.configure(config.iast)
19
+ }
20
+
21
+ function disable () {
22
+ disableAllAnalyzers()
23
+ if (requestStart.hasSubscribers) requestStart.unsubscribe(onIncomingHttpRequestStart)
24
+ if (requestClose.hasSubscribers) requestClose.unsubscribe(onIncomingHttpRequestEnd)
25
+ }
26
+
27
+ function onIncomingHttpRequestStart (data) {
28
+ if (data && data.req) {
29
+ const store = storage.getStore()
30
+ if (store) {
31
+ const topContext = web.getContext(data.req)
32
+ if (topContext) {
33
+ const rootSpan = topContext.span
34
+ const isRequestAcquired = overheadController.acquireRequest(rootSpan)
35
+ if (isRequestAcquired) {
36
+ const iastContext = saveIastContext(store, topContext, { rootSpan, req: data.req })
37
+ overheadController.initializeRequestContext(iastContext)
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ function onIncomingHttpRequestEnd (data) {
45
+ if (data && data.req) {
46
+ const store = storage.getStore()
47
+ const iastContext = getIastContext(storage.getStore())
48
+ if (iastContext && iastContext.rootSpan) {
49
+ overheadController.releaseRequest()
50
+ sendVulnerabilities(iastContext, iastContext.rootSpan)
51
+ }
52
+ // TODO web.getContext(data.req) is required when the request is aborted
53
+ if (cleanIastContext(store, web.getContext(data.req), iastContext)) {
54
+ overheadController.releaseRequest()
55
+ }
56
+ }
57
+ }
58
+
59
+ module.exports = { enable, disable, onIncomingHttpRequestEnd, onIncomingHttpRequestStart }