dd-trace 3.27.0 → 3.29.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 (91) hide show
  1. package/README.md +2 -2
  2. package/package.json +4 -4
  3. package/packages/datadog-core/src/storage/async_resource.js +4 -0
  4. package/packages/datadog-instrumentations/src/couchbase.js +4 -4
  5. package/packages/datadog-instrumentations/src/cucumber.js +5 -2
  6. package/packages/datadog-instrumentations/src/grpc/client.js +44 -42
  7. package/packages/datadog-instrumentations/src/grpc/server.js +69 -60
  8. package/packages/datadog-instrumentations/src/http2/client.js +25 -26
  9. package/packages/datadog-instrumentations/src/jest.js +9 -5
  10. package/packages/datadog-instrumentations/src/mocha.js +5 -3
  11. package/packages/datadog-plugin-cypress/src/plugin.js +4 -2
  12. package/packages/datadog-plugin-graphql/src/execute.js +6 -4
  13. package/packages/datadog-plugin-grpc/src/client.js +29 -11
  14. package/packages/datadog-plugin-grpc/src/server.js +22 -6
  15. package/packages/datadog-plugin-http2/src/client.js +46 -29
  16. package/packages/datadog-plugin-jest/src/index.js +8 -3
  17. package/packages/datadog-plugin-openai/src/index.js +39 -16
  18. package/packages/datadog-plugin-openai/src/services.js +13 -9
  19. package/packages/datadog-plugin-router/src/index.js +1 -1
  20. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -1
  21. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +3 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +7 -1
  23. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +45 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/index.js +3 -3
  25. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +3 -0
  26. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +66 -0
  27. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +19 -15
  28. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +5 -2
  29. package/packages/dd-trace/src/appsec/iast/analyzers/ssrf-analyzer.js +2 -0
  30. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +27 -8
  31. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +18 -19
  32. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +3 -0
  33. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +3 -0
  34. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +19 -0
  35. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  36. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +205 -0
  37. package/packages/dd-trace/src/appsec/iast/index.js +11 -7
  38. package/packages/dd-trace/src/appsec/iast/tags.js +2 -1
  39. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +6 -6
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +7 -5
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +23 -4
  42. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +49 -17
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +33 -0
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +23 -16
  45. package/packages/dd-trace/src/appsec/iast/taint-tracking/{origin-types.js → source-types.js} +1 -0
  46. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +76 -37
  47. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +101 -0
  48. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +45 -0
  49. package/packages/dd-trace/src/appsec/iast/telemetry/{logs.js → log/index.js} +5 -5
  50. package/packages/dd-trace/src/appsec/iast/telemetry/{log_collector.js → log/log-collector.js} +1 -1
  51. package/packages/dd-trace/src/appsec/iast/telemetry/namespaces.js +76 -0
  52. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +53 -0
  53. package/packages/dd-trace/src/appsec/iast/telemetry/verbosity.js +42 -0
  54. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +5 -1
  55. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +3 -1
  56. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  57. package/packages/dd-trace/src/config.js +47 -12
  58. package/packages/dd-trace/src/constants.js +1 -0
  59. package/packages/dd-trace/src/external-logger/src/index.js +9 -1
  60. package/packages/dd-trace/src/external-logger/test/index.spec.js +1 -1
  61. package/packages/dd-trace/src/format.js +1 -1
  62. package/packages/dd-trace/src/lambda/handler.js +8 -1
  63. package/packages/dd-trace/src/opentelemetry/span.js +3 -1
  64. package/packages/dd-trace/src/opentracing/span_context.js +2 -1
  65. package/packages/dd-trace/src/opentracing/tracer.js +1 -0
  66. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  67. package/packages/dd-trace/src/plugins/outbound.js +29 -12
  68. package/packages/dd-trace/src/plugins/plugin.js +28 -0
  69. package/packages/dd-trace/src/plugins/tracing.js +33 -16
  70. package/packages/dd-trace/src/plugins/util/ci.js +3 -2
  71. package/packages/dd-trace/src/plugins/util/test.js +55 -11
  72. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -22
  73. package/packages/dd-trace/src/plugins/util/web.js +1 -0
  74. package/packages/dd-trace/src/profiling/config.js +8 -8
  75. package/packages/dd-trace/src/profiling/exporters/agent.js +4 -1
  76. package/packages/dd-trace/src/profiling/index.js +0 -2
  77. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  78. package/packages/dd-trace/src/profiling/profilers/wall.js +162 -10
  79. package/packages/dd-trace/src/service-naming/index.js +2 -2
  80. package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +12 -0
  81. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  82. package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +12 -0
  83. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  84. package/packages/dd-trace/src/span_processor.js +0 -4
  85. package/packages/dd-trace/src/span_sampler.js +1 -1
  86. package/packages/dd-trace/src/telemetry/dependencies.js +24 -12
  87. package/packages/dd-trace/src/telemetry/metrics.js +11 -1
  88. package/packages/diagnostics_channel/src/index.js +64 -0
  89. package/scripts/install_plugin_modules.js +1 -0
  90. package/packages/dd-trace/src/profiling/profilers/cpu.js +0 -126
  91. package/scripts/version.js +0 -66
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { storage } = require('../../datadog-core')
3
4
  const ClientPlugin = require('../../dd-trace/src/plugins/client')
4
5
  const { TEXT_MAP } = require('../../../ext/formats')
5
6
  const { addMetadataTags, getFilter, getMethodMetadata } = require('./util')
@@ -7,9 +8,20 @@ const { addMetadataTags, getFilter, getMethodMetadata } = require('./util')
7
8
  class GrpcClientPlugin extends ClientPlugin {
8
9
  static get id () { return 'grpc' }
9
10
  static get operation () { return 'client:request' }
11
+ static get prefix () { return `apm:grpc:client:request` }
10
12
  static get peerServicePrecursors () { return ['rpc.service'] }
11
13
 
12
- start ({ metadata, path, type }) {
14
+ constructor (...args) {
15
+ super(...args)
16
+
17
+ this.addTraceBind('emit', ({ parentStore }) => {
18
+ return parentStore
19
+ })
20
+ }
21
+
22
+ bindStart (message) {
23
+ const store = storage.getStore()
24
+ const { metadata, path, type } = message
13
25
  const metadataFilter = this.config.metadataFilter
14
26
  const method = getMethodMetadata(path, type)
15
27
  const span = this.startSpan(this.operationName(), {
@@ -28,7 +40,7 @@ class GrpcClientPlugin extends ClientPlugin {
28
40
  metrics: {
29
41
  'grpc.status.code': 0
30
42
  }
31
- })
43
+ }, false)
32
44
 
33
45
  // needed as precursor for peer.service
34
46
  if (method.service && method.package) {
@@ -39,22 +51,27 @@ class GrpcClientPlugin extends ClientPlugin {
39
51
  addMetadataTags(span, metadata, metadataFilter, 'request')
40
52
  inject(this.tracer, span, metadata)
41
53
  }
42
- }
43
54
 
44
- error (error) {
45
- const span = this.activeSpan
55
+ message.span = span
56
+ message.parentStore = store
57
+ message.currentStore = { ...store, span }
46
58
 
47
- if (!span) return
59
+ return message.currentStore
60
+ }
48
61
 
49
- this.addCode(span, error.code)
50
- this.addError(error)
62
+ bindAsyncStart ({ parentStore }) {
63
+ return parentStore
51
64
  }
52
65
 
53
- finish ({ code, metadata }) {
54
- const span = this.activeSpan
66
+ error ({ span, error }) {
67
+ this.addCode(span, error.code)
68
+ this.addError(error, span)
69
+ }
55
70
 
71
+ finish ({ span, result }) {
56
72
  if (!span) return
57
73
 
74
+ const { code, metadata } = result || {}
58
75
  const metadataFilter = this.config.metadataFilter
59
76
 
60
77
  this.addCode(span, code)
@@ -63,7 +80,8 @@ class GrpcClientPlugin extends ClientPlugin {
63
80
  addMetadataTags(span, metadata, metadataFilter, 'response')
64
81
  }
65
82
 
66
- super.finish()
83
+ this.tagPeerService(span)
84
+ span.finish()
67
85
  }
68
86
 
69
87
  configure (config) {
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { storage } = require('../../datadog-core')
3
4
  const ServerPlugin = require('../../dd-trace/src/plugins/server')
4
5
  const { TEXT_MAP } = require('../../../ext/formats')
5
6
  const { addMetadataTags, getFilter, getMethodMetadata } = require('./util')
@@ -7,6 +8,7 @@ const { addMetadataTags, getFilter, getMethodMetadata } = require('./util')
7
8
  class GrpcServerPlugin extends ServerPlugin {
8
9
  static get id () { return 'grpc' }
9
10
  static get operation () { return 'server:request' }
11
+ static get prefix () { return `apm:grpc:server:request` }
10
12
 
11
13
  constructor (...args) {
12
14
  super(...args)
@@ -18,9 +20,15 @@ class GrpcServerPlugin extends ServerPlugin {
18
20
 
19
21
  this.addCode(span, code)
20
22
  })
23
+
24
+ this.addTraceBind('emit', ({ currentStore }) => {
25
+ return currentStore
26
+ })
21
27
  }
22
28
 
23
- start ({ name, metadata, type }) {
29
+ bindStart (message) {
30
+ const store = storage.getStore()
31
+ const { name, metadata, type } = message
24
32
  const metadataFilter = this.config.metadataFilter
25
33
  const childOf = extract(this.tracer, metadata)
26
34
  const method = getMethodMetadata(name, type)
@@ -44,9 +52,19 @@ class GrpcServerPlugin extends ServerPlugin {
44
52
  })
45
53
 
46
54
  addMetadataTags(span, metadata, metadataFilter, 'request')
55
+
56
+ message.span = span
57
+ message.parentStore = store
58
+ message.currentStore = { ...store, span }
59
+
60
+ return message.currentStore
47
61
  }
48
62
 
49
- error (error) {
63
+ bindAsyncStart ({ parentStore }) {
64
+ return parentStore
65
+ }
66
+
67
+ error ({ error }) {
50
68
  const span = this.activeSpan
51
69
 
52
70
  if (!span) return
@@ -55,9 +73,7 @@ class GrpcServerPlugin extends ServerPlugin {
55
73
  this.addError(error)
56
74
  }
57
75
 
58
- finish ({ code, trailer } = {}) {
59
- const span = this.activeSpan
60
-
76
+ finish ({ span, code, trailer }) {
61
77
  if (!span) return
62
78
 
63
79
  const metadataFilter = this.config.metadataFilter
@@ -68,7 +84,7 @@ class GrpcServerPlugin extends ServerPlugin {
68
84
  addMetadataTags(span, trailer, metadataFilter, 'response')
69
85
  }
70
86
 
71
- super.finish()
87
+ span.finish()
72
88
  }
73
89
 
74
90
  configure (config) {
@@ -8,7 +8,6 @@ const log = require('../../dd-trace/src/log')
8
8
  const tags = require('../../../ext/tags')
9
9
  const kinds = require('../../../ext/kinds')
10
10
  const formats = require('../../../ext/formats')
11
- const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
12
11
  const { COMPONENT, CLIENT_PORT_KEY } = require('../../dd-trace/src/constants')
13
12
  const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter')
14
13
 
@@ -25,32 +24,11 @@ const HTTP2_HEADER_STATUS = ':status'
25
24
  const HTTP2_METHOD_GET = 'GET'
26
25
 
27
26
  class Http2ClientPlugin extends ClientPlugin {
28
- static get id () {
29
- return 'http2'
30
- }
31
-
32
- constructor (...args) {
33
- super(...args)
34
-
35
- this.addSub('apm:http2:client:response', (headers) => {
36
- const span = storage.getStore().span
37
- const status = headers && headers[HTTP2_HEADER_STATUS]
38
-
39
- span.setTag(HTTP_STATUS_CODE, status)
40
-
41
- if (!this.config.validateStatus(status)) {
42
- this.addError()
43
- }
27
+ static get id () { return 'http2' }
28
+ static get prefix () { return `apm:http2:client:request` }
44
29
 
45
- addHeaderTags(span, headers, HTTP_RESPONSE_HEADERS, this.config)
46
- })
47
- }
48
-
49
- addTraceSub (eventName, handler) {
50
- this.addSub(`apm:${this.constructor.id}:client:${this.operation}:${eventName}`, handler)
51
- }
52
-
53
- start ({ authority, options, headers = {} }) {
30
+ bindStart (message) {
31
+ const { authority, options, headers = {} } = message
54
32
  const sessionDetails = extractSessionDetails(authority, options)
55
33
  const path = headers[HTTP2_HEADER_PATH] || '/'
56
34
  const pathname = path.split(/[?#]/)[0]
@@ -75,7 +53,7 @@ class Http2ClientPlugin extends ClientPlugin {
75
53
  metrics: {
76
54
  [CLIENT_PORT_KEY]: parseInt(sessionDetails.port)
77
55
  }
78
- })
56
+ }, false)
79
57
 
80
58
  // TODO: Figure out a better way to do this for any span.
81
59
  if (!allowed) {
@@ -88,14 +66,53 @@ class Http2ClientPlugin extends ClientPlugin {
88
66
  this.tracer.inject(span, HTTP_HEADERS, headers)
89
67
  }
90
68
 
91
- analyticsSampler.sample(span, this.config.measured)
69
+ message.parentStore = store
70
+ message.currentStore = { ...store, span }
71
+
72
+ return message.currentStore
73
+ }
74
+
75
+ bindAsyncStart ({ eventName, eventData, currentStore, parentStore }) {
76
+ switch (eventName) {
77
+ case 'response':
78
+ this._onResponse(currentStore, eventData)
79
+ return parentStore
80
+ case 'error':
81
+ this._onError(currentStore, eventData)
82
+ return parentStore
83
+ case 'close':
84
+ this._onClose(currentStore, eventData)
85
+ return parentStore
86
+ }
92
87
 
93
- this.enter(span, store)
88
+ return storage.getStore()
94
89
  }
95
90
 
96
91
  configure (config) {
97
92
  return super.configure(normalizeConfig(config))
98
93
  }
94
+
95
+ _onResponse (store, headers) {
96
+ const status = headers && headers[HTTP2_HEADER_STATUS]
97
+
98
+ store.span.setTag(HTTP_STATUS_CODE, status)
99
+
100
+ if (!this.config.validateStatus(status)) {
101
+ storage.run(store, () => this.addError())
102
+ }
103
+
104
+ addHeaderTags(store.span, headers, HTTP_RESPONSE_HEADERS, this.config)
105
+ }
106
+
107
+ _onError ({ span }, error) {
108
+ span.setTag('error', error)
109
+ span.finish()
110
+ }
111
+
112
+ _onClose ({ span }) {
113
+ this.tagPeerService(span)
114
+ span.finish()
115
+ }
99
116
  }
100
117
 
101
118
  function extractSessionDetails (authority, options) {
@@ -166,9 +166,12 @@ class JestPlugin extends CiPlugin {
166
166
  this.enter(span, store)
167
167
  })
168
168
 
169
- this.addSub('ci:jest:test:finish', (status) => {
169
+ this.addSub('ci:jest:test:finish', ({ status, testStartLine }) => {
170
170
  const span = storage.getStore().span
171
171
  span.setTag(TEST_STATUS, status)
172
+ if (testStartLine) {
173
+ span.setTag(TEST_SOURCE_START, testStartLine)
174
+ }
172
175
  span.finish()
173
176
  finishAllTraceSpans(span)
174
177
  })
@@ -197,8 +200,10 @@ class JestPlugin extends CiPlugin {
197
200
  const extraTags = {
198
201
  [JEST_TEST_RUNNER]: runner,
199
202
  [TEST_PARAMETERS]: testParameters,
200
- [TEST_FRAMEWORK_VERSION]: frameworkVersion,
201
- [TEST_SOURCE_START]: testStartLine
203
+ [TEST_FRAMEWORK_VERSION]: frameworkVersion
204
+ }
205
+ if (testStartLine) {
206
+ extraTags[TEST_SOURCE_START] = testStartLine
202
207
  }
203
208
 
204
209
  return super.startTestSpan(name, suite, this.testSuiteSpan, extraTags)
@@ -8,6 +8,10 @@ const services = require('./services')
8
8
  const Sampler = require('../../dd-trace/src/sampler')
9
9
  const { MEASURED } = require('../../../ext/tags')
10
10
 
11
+ // String#replaceAll unavailable on Node.js@v14 (dd-trace@<=v3)
12
+ const RE_NEWLINE = /\n/g
13
+ const RE_TAB = /\t/g
14
+
11
15
  // TODO: In the future we should refactor config.js to make it requirable
12
16
  let MAX_TEXT_LEN = 128
13
17
 
@@ -26,7 +30,9 @@ class OpenApiPlugin extends TracingPlugin {
26
30
  this.sampler = new Sampler(0.1) // default 10% log sampling
27
31
 
28
32
  // hoist the max length env var to avoid making all of these functions a class method
29
- MAX_TEXT_LEN = this._tracerConfig.openaiSpanCharLimit
33
+ if (this._tracerConfig) {
34
+ MAX_TEXT_LEN = this._tracerConfig.openaiSpanCharLimit
35
+ }
30
36
  }
31
37
 
32
38
  configure (config) {
@@ -83,11 +89,11 @@ class OpenApiPlugin extends TracingPlugin {
83
89
  store.prompt = prompt
84
90
  if (typeof prompt === 'string' || (Array.isArray(prompt) && typeof prompt[0] === 'number')) {
85
91
  // This is a single prompt, either String or [Number]
86
- tags[`openai.request.prompt`] = normalizeStringOrTokenArray(prompt)
92
+ tags[`openai.request.prompt`] = normalizeStringOrTokenArray(prompt, true)
87
93
  } else if (Array.isArray(prompt)) {
88
94
  // This is multiple prompts, either [String] or [[Number]]
89
95
  for (let i = 0; i < prompt.length; i++) {
90
- tags[`openai.request.prompt.${i}`] = normalizeStringOrTokenArray(prompt[i])
96
+ tags[`openai.request.prompt.${i}`] = normalizeStringOrTokenArray(prompt[i], true)
91
97
  }
92
98
  }
93
99
  }
@@ -363,6 +369,8 @@ function retrieveModelResponseExtraction (tags, body) {
363
369
  tags['openai.response.parent'] = body.parent
364
370
  tags['openai.response.root'] = body.root
365
371
 
372
+ if (!body.permission) return
373
+
366
374
  tags['openai.response.permission.id'] = body.permission[0].id
367
375
  tags['openai.response.permission.created'] = body.permission[0].created
368
376
  tags['openai.response.permission.allow_create_engine'] = body.permission[0].allow_create_engine
@@ -382,10 +390,14 @@ function commonLookupFineTuneRequestExtraction (tags, body) {
382
390
  }
383
391
 
384
392
  function listModelsResponseExtraction (tags, body) {
393
+ if (!body.data) return
394
+
385
395
  tags['openai.response.count'] = body.data.length
386
396
  }
387
397
 
388
398
  function commonImageResponseExtraction (tags, body) {
399
+ if (!body.data) return
400
+
389
401
  tags['openai.response.images_count'] = body.data.length
390
402
 
391
403
  for (let i = 0; i < body.data.length; i++) {
@@ -400,7 +412,7 @@ function createAudioResponseExtraction (tags, body) {
400
412
  tags['openai.response.text'] = body.text
401
413
  tags['openai.response.language'] = body.language
402
414
  tags['openai.response.duration'] = body.duration
403
- tags['openai.response.segments_count'] = body.segments.length
415
+ tags['openai.response.segments_count'] = defensiveArrayLength(body.segments)
404
416
  }
405
417
 
406
418
  function createFineTuneRequestExtraction (tags, body) {
@@ -417,21 +429,24 @@ function createFineTuneRequestExtraction (tags, body) {
417
429
  }
418
430
 
419
431
  function commonFineTuneResponseExtraction (tags, body) {
420
- tags['openai.response.events_count'] = body.events.length
432
+ tags['openai.response.events_count'] = defensiveArrayLength(body.events)
421
433
  tags['openai.response.fine_tuned_model'] = body.fine_tuned_model
422
- tags['openai.response.hyperparams.n_epochs'] = body.hyperparams.n_epochs
423
- tags['openai.response.hyperparams.batch_size'] = body.hyperparams.batch_size
424
- tags['openai.response.hyperparams.prompt_loss_weight'] = body.hyperparams.prompt_loss_weight
425
- tags['openai.response.hyperparams.learning_rate_multiplier'] = body.hyperparams.learning_rate_multiplier
426
- tags['openai.response.training_files_count'] = body.training_files.length
427
- tags['openai.response.result_files_count'] = body.result_files.length
428
- tags['openai.response.validation_files_count'] = body.validation_files.length
434
+ if (body.hyperparams) {
435
+ tags['openai.response.hyperparams.n_epochs'] = body.hyperparams.n_epochs
436
+ tags['openai.response.hyperparams.batch_size'] = body.hyperparams.batch_size
437
+ tags['openai.response.hyperparams.prompt_loss_weight'] = body.hyperparams.prompt_loss_weight
438
+ tags['openai.response.hyperparams.learning_rate_multiplier'] = body.hyperparams.learning_rate_multiplier
439
+ }
440
+ tags['openai.response.training_files_count'] = defensiveArrayLength(body.training_files)
441
+ tags['openai.response.result_files_count'] = defensiveArrayLength(body.result_files)
442
+ tags['openai.response.validation_files_count'] = defensiveArrayLength(body.validation_files)
429
443
  tags['openai.response.updated_at'] = body.updated_at
430
444
  tags['openai.response.status'] = body.status
431
445
  }
432
446
 
433
447
  // the OpenAI package appears to stream the content download then provide it all as a singular string
434
448
  function downloadFileResponseExtraction (tags, body) {
449
+ if (!body.file) return
435
450
  tags['openai.response.total_bytes'] = body.file.length
436
451
  }
437
452
 
@@ -472,6 +487,8 @@ function createRetrieveFileResponseExtraction (tags, body) {
472
487
  function createEmbeddingResponseExtraction (tags, body) {
473
488
  usageExtraction(tags, body)
474
489
 
490
+ if (!body.data) return
491
+
475
492
  tags['openai.response.embeddings_count'] = body.data.length
476
493
  for (let i = 0; i < body.data.length; i++) {
477
494
  tags[`openai.response.embedding.${i}.embedding_length`] = body.data[i].embedding.length
@@ -479,6 +496,7 @@ function createEmbeddingResponseExtraction (tags, body) {
479
496
  }
480
497
 
481
498
  function commonListCountResponseExtraction (tags, body) {
499
+ if (!body.data) return
482
500
  tags['openai.response.count'] = body.data.length
483
501
  }
484
502
 
@@ -486,6 +504,9 @@ function commonListCountResponseExtraction (tags, body) {
486
504
  function createModerationResponseExtraction (tags, body) {
487
505
  tags['openai.response.id'] = body.id
488
506
  // tags[`openai.response.model`] = body.model // redundant, already extracted globally
507
+
508
+ if (!body.results) return
509
+
489
510
  tags['openai.response.flagged'] = body.results[0].flagged
490
511
 
491
512
  for (const [category, match] of Object.entries(body.results[0].categories)) {
@@ -501,6 +522,8 @@ function createModerationResponseExtraction (tags, body) {
501
522
  function commonCreateResponseExtraction (tags, body, store) {
502
523
  usageExtraction(tags, body)
503
524
 
525
+ if (!body.choices) return
526
+
504
527
  tags['openai.response.choices_count'] = body.choices.length
505
528
 
506
529
  store.choices = body.choices
@@ -530,7 +553,7 @@ function usageExtraction (tags, body) {
530
553
  }
531
554
 
532
555
  function truncateApiKey (apiKey) {
533
- return `sk-...${apiKey.substr(apiKey.length - 4)}`
556
+ return apiKey && `sk-...${apiKey.substr(apiKey.length - 4)}`
534
557
  }
535
558
 
536
559
  /**
@@ -540,8 +563,8 @@ function truncateText (text) {
540
563
  if (!text) return
541
564
 
542
565
  text = text
543
- .replaceAll('\n', '\\n')
544
- .replaceAll('\t', '\\t')
566
+ .replace(RE_NEWLINE, '\\n')
567
+ .replace(RE_TAB, '\\t')
545
568
 
546
569
  if (text.length > MAX_TEXT_LEN) {
547
570
  return text.substring(0, MAX_TEXT_LEN) + '...'
@@ -671,7 +694,7 @@ function normalizeRequestPayload (methodName, args) {
671
694
  * "foo" -> "foo"
672
695
  * [1,2,3] -> "[1, 2, 3]"
673
696
  */
674
- function normalizeStringOrTokenArray (input, truncate = true) {
697
+ function normalizeStringOrTokenArray (input, truncate) {
675
698
  const normalized = Array.isArray(input)
676
699
  ? `[${input.join(', ')}]` // "[1, 2, 999]"
677
700
  : input // "foo"
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { DogStatsDClient, NoopDogStatsDClient } = require('../../dd-trace/src/dogstatsd')
4
- const ExternalLogger = require('../../dd-trace/src/external-logger/src')
4
+ const { ExternalLogger, NoopExternalLogger } = require('../../dd-trace/src/external-logger/src')
5
5
 
6
6
  const FLUSH_INTERVAL = 10 * 1000
7
7
 
@@ -10,7 +10,7 @@ let logger = null
10
10
  let interval = null
11
11
 
12
12
  module.exports.init = function (tracerConfig) {
13
- if (tracerConfig.dogstatsd) {
13
+ if (tracerConfig && tracerConfig.dogstatsd) {
14
14
  metrics = new DogStatsDClient({
15
15
  host: tracerConfig.dogstatsd.hostname,
16
16
  port: tracerConfig.dogstatsd.port,
@@ -24,13 +24,17 @@ module.exports.init = function (tracerConfig) {
24
24
  metrics = new NoopDogStatsDClient()
25
25
  }
26
26
 
27
- logger = new ExternalLogger({
28
- ddsource: 'openai',
29
- hostname: tracerConfig.hostname,
30
- service: tracerConfig.service,
31
- apiKey: tracerConfig.apiKey,
32
- interval: FLUSH_INTERVAL
33
- })
27
+ if (tracerConfig && tracerConfig.apiKey) {
28
+ logger = new ExternalLogger({
29
+ ddsource: 'openai',
30
+ hostname: tracerConfig.hostname,
31
+ service: tracerConfig.service,
32
+ apiKey: tracerConfig.apiKey,
33
+ interval: FLUSH_INTERVAL
34
+ })
35
+ } else {
36
+ logger = new NoopExternalLogger()
37
+ }
34
38
 
35
39
  interval = setInterval(() => {
36
40
  metrics.flush()
@@ -156,7 +156,7 @@ function isMoreSpecificThan (routeA, routeB) {
156
156
  }
157
157
 
158
158
  function routeIsRegex (route) {
159
- return route.includes('(/') && route.includes('/)')
159
+ return route.includes('(/')
160
160
  }
161
161
 
162
162
  module.exports = RouterPlugin
@@ -2,6 +2,7 @@
2
2
 
3
3
  module.exports = {
4
4
  'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
5
+ 'HSTS_HEADER_MISSING_ANALYZER': require('./hsts-header-missing-analyzer'),
5
6
  'INSECURE_COOKIE_ANALYZER': require('./insecure-cookie-analyzer'),
6
7
  'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
7
8
  'NO_HTTPONLY_COOKIE_ANALYZER': require('./no-httponly-cookie-analyzer'),
@@ -11,5 +12,6 @@ module.exports = {
11
12
  'SSRF': require('./ssrf-analyzer'),
12
13
  'UNVALIDATED_REDIRECT_ANALYZER': require('./unvalidated-redirect-analyzer'),
13
14
  'WEAK_CIPHER_ANALYZER': require('./weak-cipher-analyzer'),
14
- 'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer')
15
+ 'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer'),
16
+ 'XCONTENTTYPE_HEADER_MISSING_ANALYZER': require('./xcontenttype-header-missing-analyzer')
15
17
  }
@@ -5,6 +5,9 @@ const { COMMAND_INJECTION } = require('../vulnerabilities')
5
5
  class CommandInjectionAnalyzer extends InjectionAnalyzer {
6
6
  constructor () {
7
7
  super(COMMAND_INJECTION)
8
+ }
9
+
10
+ onConfigure () {
8
11
  this.addSub('datadog:child_process:execution:start', ({ command }) => this.analyze(command))
9
12
  }
10
13
  }
@@ -9,7 +9,13 @@ class CookieAnalyzer extends Analyzer {
9
9
  constructor (type, propertyToBeSafe) {
10
10
  super(type)
11
11
  this.propertyToBeSafe = propertyToBeSafe.toLowerCase()
12
- this.addSub('datadog:iast:set-cookie', (cookieInfo) => this.analyze(cookieInfo))
12
+ }
13
+
14
+ onConfigure () {
15
+ this.addSub(
16
+ { channelName: 'datadog:iast:set-cookie', moduleName: 'http' },
17
+ (cookieInfo) => this.analyze(cookieInfo)
18
+ )
13
19
  }
14
20
 
15
21
  _isVulnerable ({ cookieProperties, cookieValue }) {
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const { HSTS_HEADER_MISSING } = require('../vulnerabilities')
4
+ const { MissingHeaderAnalyzer } = require('./missing-header-analyzer')
5
+
6
+ const HSTS_HEADER_NAME = 'Strict-Transport-Security'
7
+ const HEADER_VALID_PREFIX = 'max-age'
8
+ class HstsHeaderMissingAnalyzer extends MissingHeaderAnalyzer {
9
+ constructor () {
10
+ super(HSTS_HEADER_MISSING, HSTS_HEADER_NAME)
11
+ }
12
+ _isVulnerableFromRequestAndResponse (req, res) {
13
+ const headerToCheck = res.getHeader(HSTS_HEADER_NAME)
14
+ return !this._isHeaderValid(headerToCheck) && this._isHttpsProtocol(req)
15
+ }
16
+
17
+ _isHeaderValid (headerValue) {
18
+ if (!headerValue) {
19
+ return false
20
+ }
21
+ headerValue = headerValue.trim()
22
+
23
+ if (!headerValue.startsWith(HEADER_VALID_PREFIX)) {
24
+ return false
25
+ }
26
+
27
+ const semicolonIndex = headerValue.indexOf(';')
28
+ let timestampString
29
+ if (semicolonIndex > -1) {
30
+ timestampString = headerValue.substring(HEADER_VALID_PREFIX.length + 1, semicolonIndex)
31
+ } else {
32
+ timestampString = headerValue.substring(HEADER_VALID_PREFIX.length + 1)
33
+ }
34
+
35
+ const timestamp = parseInt(timestampString)
36
+ // eslint-disable-next-line eqeqeq
37
+ return timestamp == timestampString && timestamp > 0
38
+ }
39
+
40
+ _isHttpsProtocol (req) {
41
+ return req.protocol === 'https' || req.headers['x-forwarded-proto'] === 'https'
42
+ }
43
+ }
44
+
45
+ module.exports = new HstsHeaderMissingAnalyzer()
@@ -3,10 +3,10 @@
3
3
  const analyzers = require('./analyzers')
4
4
  const setCookiesHeaderInterceptor = require('./set-cookies-header-interceptor')
5
5
 
6
- function enableAllAnalyzers () {
7
- setCookiesHeaderInterceptor.configure(true)
6
+ function enableAllAnalyzers (tracerConfig) {
7
+ setCookiesHeaderInterceptor.configure({ enabled: true, tracerConfig })
8
8
  for (const analyzer in analyzers) {
9
- analyzers[analyzer].configure(true)
9
+ analyzers[analyzer].configure({ enabled: true, tracerConfig })
10
10
  }
11
11
  }
12
12
 
@@ -5,6 +5,9 @@ const { LDAP_INJECTION } = require('../vulnerabilities')
5
5
  class LdapInjectionAnalyzer extends InjectionAnalyzer {
6
6
  constructor () {
7
7
  super(LDAP_INJECTION)
8
+ }
9
+
10
+ onConfigure () {
8
11
  this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
9
12
  }
10
13
  }