dd-trace 2.35.1 → 2.37.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 (67) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/MIGRATING.md +158 -0
  3. package/README.md +18 -11
  4. package/index.d.ts +281 -0
  5. package/package.json +6 -4
  6. package/packages/datadog-instrumentations/src/cookie.js +21 -0
  7. package/packages/datadog-instrumentations/src/fetch.js +48 -0
  8. package/packages/datadog-instrumentations/src/grpc/server.js +1 -1
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  10. package/packages/datadog-instrumentations/src/helpers/register.js +10 -0
  11. package/packages/datadog-instrumentations/src/jest.js +2 -3
  12. package/packages/datadog-instrumentations/src/next.js +2 -2
  13. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +18 -0
  14. package/packages/datadog-plugin-cypress/src/plugin.js +109 -47
  15. package/packages/datadog-plugin-cypress/src/support.js +3 -2
  16. package/packages/datadog-plugin-fetch/src/index.js +36 -0
  17. package/packages/datadog-plugin-http/src/client.js +24 -8
  18. package/packages/datadog-plugin-mysql/src/index.js +2 -11
  19. package/packages/datadog-plugin-tedious/src/index.js +2 -2
  20. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -0
  21. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +52 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +3 -22
  23. package/packages/dd-trace/src/appsec/iast/analyzers/no-httponly-cookie-analyzer.js +12 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/no-samesite-cookie-analyzer.js +12 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +7 -3
  26. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +3 -3
  27. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +48 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +3 -3
  29. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
  30. package/packages/dd-trace/src/appsec/iast/index.js +9 -2
  31. package/packages/dd-trace/src/appsec/iast/path-line.js +13 -0
  32. package/packages/dd-trace/src/appsec/iast/tags.js +6 -0
  33. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +2 -1
  34. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +13 -4
  35. package/packages/dd-trace/src/appsec/iast/taint-tracking/origin-types.js +5 -1
  36. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +24 -4
  37. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -1
  38. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +3 -0
  39. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +7 -1
  40. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -3
  41. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +5 -2
  42. package/packages/dd-trace/src/config.js +13 -0
  43. package/packages/dd-trace/src/external-logger/src/index.js +126 -0
  44. package/packages/dd-trace/src/external-logger/test/index.spec.js +147 -0
  45. package/packages/dd-trace/src/lambda/handler.js +3 -15
  46. package/packages/dd-trace/src/noop/proxy.js +4 -0
  47. package/packages/dd-trace/src/opentelemetry/context_manager.js +74 -0
  48. package/packages/dd-trace/src/opentelemetry/sampler.js +18 -0
  49. package/packages/dd-trace/src/opentelemetry/span.js +151 -0
  50. package/packages/dd-trace/src/opentelemetry/span_context.js +44 -0
  51. package/packages/dd-trace/src/opentelemetry/span_processor.js +50 -0
  52. package/packages/dd-trace/src/opentelemetry/tracer.js +124 -0
  53. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +72 -0
  54. package/packages/dd-trace/src/opentracing/span.js +14 -4
  55. package/packages/dd-trace/src/plugin_manager.js +10 -7
  56. package/packages/dd-trace/src/plugins/database.js +7 -3
  57. package/packages/dd-trace/src/plugins/plugin.js +3 -1
  58. package/packages/dd-trace/src/plugins/util/exec.js +2 -2
  59. package/packages/dd-trace/src/plugins/util/git.js +51 -24
  60. package/packages/dd-trace/src/profiling/config.js +2 -0
  61. package/packages/dd-trace/src/profiling/profiler.js +13 -4
  62. package/packages/dd-trace/src/proxy.js +4 -0
  63. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +24 -1
  64. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +18 -1
  65. package/packages/dd-trace/src/tracer.js +3 -3
  66. package/packages/dd-trace/src/util.js +1 -1
  67. package/version.js +8 -4
@@ -7,16 +7,26 @@ const Hook = require('./hook')
7
7
  const requirePackageJson = require('../../../dd-trace/src/require-package-json')
8
8
  const log = require('../../../dd-trace/src/log')
9
9
 
10
+ const { DD_TRACE_DISABLED_INSTRUMENTATIONS = '' } = process.env
11
+
10
12
  const hooks = require('./hooks')
11
13
  const instrumentations = require('./instrumentations')
12
14
  const names = Object.keys(hooks)
13
15
  const pathSepExpr = new RegExp(`\\${path.sep}`, 'g')
16
+ const disabledInstrumentations = new Set(
17
+ DD_TRACE_DISABLED_INSTRUMENTATIONS ? DD_TRACE_DISABLED_INSTRUMENTATIONS.split(',') : []
18
+ )
14
19
 
15
20
  const loadChannel = channel('dd-trace:instrumentation:load')
16
21
 
22
+ // Globals
23
+ require('../fetch')
24
+
17
25
  // TODO: make this more efficient
18
26
 
19
27
  for (const packageName of names) {
28
+ if (disabledInstrumentations.has(packageName)) continue
29
+
20
30
  Hook([packageName], (moduleExports, moduleName, moduleBaseDir, moduleVersion) => {
21
31
  moduleName = moduleName.replace(pathSepExpr, '/')
22
32
 
@@ -1,10 +1,8 @@
1
1
  'use strict'
2
- const semver = require('semver')
3
2
 
4
3
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
5
4
  const shimmer = require('../../datadog-shimmer')
6
5
  const log = require('../../dd-trace/src/log')
7
- const { version: ddTraceVersion } = require('../../../package.json')
8
6
  const {
9
7
  getCoveredFilenamesFromCoverage,
10
8
  JEST_WORKER_TRACE_PAYLOAD_CODE,
@@ -18,6 +16,7 @@ const {
18
16
  getJestTestName,
19
17
  getJestSuitesToRun
20
18
  } = require('../../datadog-plugin-jest/src/util')
19
+ const { DD_MAJOR } = require('../../../version')
21
20
 
22
21
  const testSessionStartCh = channel('ci:jest:session:start')
23
22
  const testSessionFinishCh = channel('ci:jest:session:finish')
@@ -481,7 +480,7 @@ function jasmineAsyncInstallWraper (jasmineAsyncInstallExport, jestVersion) {
481
480
  }
482
481
  }
483
482
 
484
- if (semver.lt(ddTraceVersion, '4.0.0')) {
483
+ if (DD_MAJOR < 4) {
485
484
  addHook({
486
485
  name: 'jest-jasmine2',
487
486
  versions: ['>=24.8.0'],
@@ -4,7 +4,7 @@
4
4
 
5
5
  const { channel, addHook, AsyncResource } = require('./helpers/instrument')
6
6
  const shimmer = require('../../datadog-shimmer')
7
- const { MAJOR } = require('../../../version')
7
+ const { DD_MAJOR } = require('../../../version')
8
8
 
9
9
  const startChannel = channel('apm:next:request:start')
10
10
  const finishChannel = channel('apm:next:request:finish')
@@ -171,7 +171,7 @@ addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-serv
171
171
 
172
172
  addHook({
173
173
  name: 'next',
174
- versions: MAJOR >= 4 ? ['>=10.2 <11.1'] : ['>=9.5 <11.1'],
174
+ versions: DD_MAJOR >= 4 ? ['>=10.2 <11.1'] : ['>=9.5 <11.1'],
175
175
  file: 'dist/next-server/server/next-server.js'
176
176
  }, nextServer => {
177
177
  const Server = nextServer.default
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const { addHook } = require('./helpers/instrument')
4
+ const shimmer = require('../../datadog-shimmer')
5
+ const tracer = require('../../dd-trace')
6
+
7
+ if (process.env.DD_TRACE_OTEL_ENABLED) {
8
+ addHook({
9
+ name: '@opentelemetry/sdk-trace-node',
10
+ file: 'build/src/NodeTracerProvider.js',
11
+ versions: ['*']
12
+ }, (mod) => {
13
+ shimmer.wrap(mod, 'NodeTracerProvider', () => {
14
+ return tracer.TracerProvider
15
+ })
16
+ return mod
17
+ })
18
+ }
@@ -133,6 +133,8 @@ module.exports = (on, config) => {
133
133
  'git.branch': branch
134
134
  } = testEnvironmentMetadata
135
135
 
136
+ const finishedTestsByFile = {}
137
+
136
138
  const testConfiguration = {
137
139
  repositoryUrl,
138
140
  sha,
@@ -158,6 +160,44 @@ module.exports = (on, config) => {
158
160
  let isCodeCoverageEnabled = false
159
161
  let testsToSkip = []
160
162
 
163
+ function getTestSpan (testName, testSuite) {
164
+ const testSuiteTags = {
165
+ [TEST_COMMAND]: command,
166
+ [TEST_COMMAND]: command,
167
+ [TEST_MODULE]: TEST_FRAMEWORK_NAME
168
+ }
169
+ if (testSuiteSpan) {
170
+ testSuiteTags[TEST_SUITE_ID] = testSuiteSpan.context().toSpanId()
171
+ }
172
+ if (testSessionSpan && testModuleSpan) {
173
+ testSuiteTags[TEST_SESSION_ID] = testSessionSpan.context().toTraceId()
174
+ testSuiteTags[TEST_MODULE_ID] = testModuleSpan.context().toSpanId()
175
+ }
176
+
177
+ const {
178
+ childOf,
179
+ resource,
180
+ ...testSpanMetadata
181
+ } = getTestSpanMetadata(tracer, testName, testSuite, config)
182
+
183
+ const codeOwners = getCodeOwnersForFilename(testSuite, codeOwnersEntries)
184
+
185
+ if (codeOwners) {
186
+ testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
187
+ }
188
+
189
+ return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
190
+ childOf,
191
+ tags: {
192
+ [COMPONENT]: TEST_FRAMEWORK_NAME,
193
+ [ORIGIN_KEY]: CI_APP_ORIGIN,
194
+ ...testSpanMetadata,
195
+ ...testEnvironmentMetadata,
196
+ ...testSuiteTags
197
+ }
198
+ })
199
+ }
200
+
161
201
  on('before:run', (details) => {
162
202
  return getItrConfig(tracer, testConfiguration).then(({ err, itrConfig }) => {
163
203
  if (err) {
@@ -203,6 +243,58 @@ module.exports = (on, config) => {
203
243
  })
204
244
  })
205
245
  })
246
+ on('after:spec', (spec, { tests, stats }) => {
247
+ const cypressTests = tests || []
248
+ const finishedTests = finishedTestsByFile[spec.relative] || []
249
+
250
+ // Get tests that didn't go through `dd:afterEach` and haven't been skipped by ITR
251
+ // and create a skipped test span for each of them
252
+ cypressTests.filter(({ title }) => {
253
+ const cypressTestName = title.join(' ')
254
+ const isSkippedByItr = testsToSkip.find(test =>
255
+ cypressTestName === test.name && spec.relative === test.suite
256
+ )
257
+ const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
258
+
259
+ return !isSkippedByItr && !isTestFinished
260
+ }).forEach(({ title }) => {
261
+ const skippedTestSpan = getTestSpan(title.join(' '), spec.relative)
262
+ skippedTestSpan.setTag(TEST_STATUS, 'skip')
263
+ skippedTestSpan.finish()
264
+ })
265
+
266
+ // Make sure that reported test statuses are the same as Cypress reports.
267
+ // This is not always the case, such as when an `after` hook fails:
268
+ // Cypress will report the last run test as failed, but we don't know that yet at `dd:afterEach`
269
+ let latestError
270
+ finishedTests.forEach((finishedTest) => {
271
+ const cypressTest = cypressTests.find(test => test.title.join(' ') === finishedTest.testName)
272
+ if (!cypressTest) {
273
+ return
274
+ }
275
+ if (cypressTest.displayError) {
276
+ latestError = new Error(cypressTest.displayError)
277
+ }
278
+ const cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
279
+ // update test status
280
+ if (cypressTestStatus !== finishedTest.testStatus) {
281
+ finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
282
+ finishedTest.testSpan.setTag('error', latestError)
283
+ }
284
+ finishedTest.testSpan.finish(finishedTest.finishTime)
285
+ })
286
+
287
+ if (testSuiteSpan) {
288
+ const status = getSuiteStatus(stats)
289
+ testSuiteSpan.setTag(TEST_STATUS, status)
290
+
291
+ if (latestError) {
292
+ testSuiteSpan.setTag('error', latestError)
293
+ }
294
+ testSuiteSpan.finish()
295
+ testSuiteSpan = null
296
+ }
297
+ })
206
298
 
207
299
  on('after:run', (suiteStats) => {
208
300
  if (testSessionSpan && testModuleSpan) {
@@ -254,15 +346,6 @@ module.exports = (on, config) => {
254
346
  })
255
347
  return null
256
348
  },
257
- 'dd:testSuiteFinish': (stats) => {
258
- if (testSuiteSpan) {
259
- const status = getSuiteStatus(stats)
260
- testSuiteSpan.setTag(TEST_STATUS, status)
261
- testSuiteSpan.finish()
262
- testSuiteSpan = null
263
- }
264
- return null
265
- },
266
349
  'dd:beforeEach': (test) => {
267
350
  const { testName, testSuite } = test
268
351
  // skip test
@@ -272,47 +355,14 @@ module.exports = (on, config) => {
272
355
  return { shouldSkip: true }
273
356
  }
274
357
 
275
- const testSuiteTags = {
276
- [TEST_COMMAND]: command,
277
- [TEST_COMMAND]: command,
278
- [TEST_MODULE]: TEST_FRAMEWORK_NAME
279
- }
280
- if (testSuiteSpan) {
281
- testSuiteTags[TEST_SUITE_ID] = testSuiteSpan.context().toSpanId()
282
- }
283
- if (testSessionSpan && testModuleSpan) {
284
- testSuiteTags[TEST_SESSION_ID] = testSessionSpan.context().toTraceId()
285
- testSuiteTags[TEST_MODULE_ID] = testModuleSpan.context().toSpanId()
286
- }
287
-
288
- const {
289
- childOf,
290
- resource,
291
- ...testSpanMetadata
292
- } = getTestSpanMetadata(tracer, testName, testSuite, config)
293
-
294
- const codeOwners = getCodeOwnersForFilename(testSuite, codeOwnersEntries)
295
-
296
- if (codeOwners) {
297
- testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
298
- }
299
-
300
358
  if (!activeSpan) {
301
- activeSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
302
- childOf,
303
- tags: {
304
- [COMPONENT]: TEST_FRAMEWORK_NAME,
305
- [ORIGIN_KEY]: CI_APP_ORIGIN,
306
- ...testSpanMetadata,
307
- ...testEnvironmentMetadata,
308
- ...testSuiteTags
309
- }
310
- })
359
+ activeSpan = getTestSpan(testName, testSuite)
311
360
  }
361
+
312
362
  return activeSpan ? { traceId: activeSpan.context().toTraceId() } : {}
313
363
  },
314
364
  'dd:afterEach': ({ test, coverage }) => {
315
- const { state, error, isRUMActive, testSourceLine } = test
365
+ const { state, error, isRUMActive, testSourceLine, testSuite, testName } = test
316
366
  if (activeSpan) {
317
367
  if (coverage && tracer._tracer._exporter.exportCoverage && isCodeCoverageEnabled) {
318
368
  const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
@@ -326,8 +376,9 @@ module.exports = (on, config) => {
326
376
  }
327
377
  tracer._tracer._exporter.exportCoverage(formattedCoverage)
328
378
  }
379
+ const testStatus = CYPRESS_STATUS_TO_TEST_STATUS[state]
380
+ activeSpan.setTag(TEST_STATUS, testStatus)
329
381
 
330
- activeSpan.setTag(TEST_STATUS, CYPRESS_STATUS_TO_TEST_STATUS[state])
331
382
  if (error) {
332
383
  activeSpan.setTag('error', error)
333
384
  }
@@ -337,7 +388,18 @@ module.exports = (on, config) => {
337
388
  if (testSourceLine) {
338
389
  activeSpan.setTag(TEST_SOURCE_START, testSourceLine)
339
390
  }
340
- activeSpan.finish()
391
+ const finishedTest = {
392
+ testName,
393
+ testStatus,
394
+ finishTime: activeSpan._getTime(), // we store the finish time here
395
+ testSpan: activeSpan
396
+ }
397
+ if (finishedTestsByFile[testSuite]) {
398
+ finishedTestsByFile[testSuite].push(finishedTest)
399
+ } else {
400
+ finishedTestsByFile[testSuite] = [finishedTest]
401
+ }
402
+ // test spans are finished at after:spec
341
403
  }
342
404
  activeSpan = null
343
405
  return null
@@ -16,9 +16,10 @@ before(() => {
16
16
  })
17
17
 
18
18
  after(() => {
19
- cy.task('dd:testSuiteFinish', Cypress.mocha.getRunner().stats)
20
19
  cy.window().then(win => {
21
- win.dispatchEvent(new Event('beforeunload'))
20
+ if (win.DD_RUM) {
21
+ win.dispatchEvent(new Event('beforeunload'))
22
+ }
22
23
  })
23
24
  })
24
25
 
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ const HttpClientPlugin = require('../../datadog-plugin-http/src/client')
4
+ const { HTTP_HEADERS } = require('../../../ext/formats')
5
+
6
+ class FetchPlugin extends HttpClientPlugin {
7
+ static get id () { return 'fetch' }
8
+
9
+ addTraceSub (eventName, handler) {
10
+ this.addSub(`apm:${this.constructor.id}:${this.operation}:${eventName}`, handler)
11
+ }
12
+
13
+ start (message) {
14
+ const req = message.req
15
+ const options = new URL(req.url)
16
+ const headers = options.headers = Object.fromEntries(req.headers.entries())
17
+
18
+ const args = { options }
19
+
20
+ super.start({ args })
21
+
22
+ message.req = new globalThis.Request(req, { headers })
23
+ }
24
+
25
+ _inject (span, headers) {
26
+ const carrier = {}
27
+
28
+ this.tracer.inject(span, HTTP_HEADERS, carrier)
29
+
30
+ for (const name in carrier) {
31
+ headers.append(name, carrier[name])
32
+ }
33
+ }
34
+ }
35
+
36
+ module.exports = FetchPlugin
@@ -24,15 +24,17 @@ class HttpClientPlugin extends ClientPlugin {
24
24
  this.addSub(`apm:${this.constructor.id}:client:${this.operation}:${eventName}`, handler)
25
25
  }
26
26
 
27
- start ({ args, http }) {
27
+ start ({ args, http = {} }) {
28
28
  const store = storage.getStore()
29
29
  const options = args.options
30
- const agent = options.agent || options._defaultAgent || http.globalAgent
30
+ const agent = options.agent || options._defaultAgent || http.globalAgent || {}
31
31
  const protocol = options.protocol || agent.protocol || 'http:'
32
32
  const hostname = options.hostname || options.host || 'localhost'
33
33
  const host = options.port ? `${hostname}:${options.port}` : hostname
34
- const path = options.path ? options.path.split(/[?#]/)[0] : '/'
34
+ const pathname = options.path || options.pathname
35
+ const path = pathname ? pathname.split(/[?#]/)[0] : '/'
35
36
  const uri = `${protocol}//${host}${path}`
37
+
36
38
  const allowed = this.config.filter(uri)
37
39
 
38
40
  const method = (options.method || 'GET').toUpperCase()
@@ -71,9 +73,11 @@ class HttpClientPlugin extends ClientPlugin {
71
73
  finish ({ req, res }) {
72
74
  const span = storage.getStore().span
73
75
  if (res) {
74
- span.setTag(HTTP_STATUS_CODE, res.statusCode)
76
+ const status = res.status || res.statusCode
77
+
78
+ span.setTag(HTTP_STATUS_CODE, status)
75
79
 
76
- if (!this.config.validateStatus(res.statusCode)) {
80
+ if (!this.config.validateStatus(status)) {
77
81
  span.setTag('error', 1)
78
82
  }
79
83
 
@@ -106,8 +110,14 @@ class HttpClientPlugin extends ClientPlugin {
106
110
  }
107
111
 
108
112
  function addResponseHeaders (res, span, config) {
113
+ if (!res.headers) return
114
+
115
+ const headers = typeof res.headers.entries === 'function'
116
+ ? Object.fromEntries(res.headers.entries())
117
+ : res.headers
118
+
109
119
  config.headers.forEach(key => {
110
- const value = res.headers[key]
120
+ const value = headers[key]
111
121
 
112
122
  if (value) {
113
123
  span.setTag(`${HTTP_RESPONSE_HEADERS}.${key}`, value)
@@ -116,8 +126,12 @@ function addResponseHeaders (res, span, config) {
116
126
  }
117
127
 
118
128
  function addRequestHeaders (req, span, config) {
129
+ const headers = req.headers && typeof req.headers.entries === 'function'
130
+ ? Object.fromEntries(req.headers.entries())
131
+ : req.headers || req.getHeaders()
132
+
119
133
  config.headers.forEach(key => {
120
- const value = req.getHeader(key)
134
+ const value = headers[key]
121
135
 
122
136
  if (value) {
123
137
  span.setTag(`${HTTP_REQUEST_HEADERS}.${key}`, Array.isArray(value) ? value.toString() : value)
@@ -193,7 +207,9 @@ function hasAmazonSignature (options) {
193
207
  }
194
208
  }
195
209
 
196
- return options.path && options.path.toLowerCase().indexOf('x-amz-signature=') !== -1
210
+ const search = options.search || options.path
211
+
212
+ return search && search.toLowerCase().indexOf('x-amz-signature=') !== -1
197
213
  }
198
214
 
199
215
  function getServiceName (tracer, config, options) {
@@ -8,9 +8,8 @@ class MySQLPlugin extends DatabasePlugin {
8
8
  static get system () { return 'mysql' }
9
9
 
10
10
  start (payload) {
11
- const service = getServiceName(this.config, payload.conf)
12
-
13
- this.startSpan(`${this.system}.query`, {
11
+ const service = this.serviceName(this.config, payload.conf, this.system)
12
+ this.startSpan(this.operationName(), {
14
13
  service,
15
14
  resource: payload.sql,
16
15
  type: 'sql',
@@ -27,12 +26,4 @@ class MySQLPlugin extends DatabasePlugin {
27
26
  }
28
27
  }
29
28
 
30
- function getServiceName (config, dbConfig) {
31
- if (typeof config.service === 'function') {
32
- return config.service(dbConfig)
33
- }
34
-
35
- return config.service
36
- }
37
-
38
29
  module.exports = MySQLPlugin
@@ -9,8 +9,8 @@ class TediousPlugin extends DatabasePlugin {
9
9
  static get system () { return 'mssql' }
10
10
 
11
11
  start ({ queryOrProcedure, connectionConfig }) {
12
- this.startSpan('tedious.request', {
13
- service: this.config.service,
12
+ this.startSpan(this.operationName(), {
13
+ service: this.serviceName(this.config, this.system),
14
14
  resource: queryOrProcedure,
15
15
  type: 'sql',
16
16
  kind: 'client',
@@ -4,9 +4,12 @@ module.exports = {
4
4
  'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
5
5
  'INSECURE_COOKIE_ANALYZER': require('./insecure-cookie-analyzer'),
6
6
  'LDAP_ANALYZER': require('./ldap-injection-analyzer'),
7
+ 'NO_HTTPONLY_COOKIE_ANALYZER': require('./no-httponly-cookie-analyzer'),
8
+ 'NO_SAMESITE_COOKIE_ANALYZER': require('./no-samesite-cookie-analyzer'),
7
9
  'PATH_TRAVERSAL_ANALYZER': require('./path-traversal-analyzer'),
8
10
  'SQL_INJECTION_ANALYZER': require('./sql-injection-analyzer'),
9
11
  'SSRF': require('./ssrf-analyzer'),
12
+ 'UNVALIDATED_REDIRECT_ANALYZER': require('./unvalidated-redirect-analyzer'),
10
13
  'WEAK_CIPHER_ANALYZER': require('./weak-cipher-analyzer'),
11
14
  'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer')
12
15
  }
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ const Analyzer = require('./vulnerability-analyzer')
4
+ const { getNodeModulesPaths } = require('../path-line')
5
+
6
+ const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
7
+
8
+ class CookieAnalyzer extends Analyzer {
9
+ constructor (type, propertyToBeSafe) {
10
+ super(type)
11
+ this.propertyToBeSafe = propertyToBeSafe.toLowerCase()
12
+ this.addSub('datadog:iast:set-cookie', (cookieInfo) => this.analyze(cookieInfo))
13
+ }
14
+
15
+ _isVulnerable ({ cookieProperties, cookieValue }) {
16
+ return cookieValue && !(cookieProperties && cookieProperties
17
+ .map(x => x.toLowerCase().trim()).includes(this.propertyToBeSafe))
18
+ }
19
+
20
+ _getEvidence ({ cookieName }) {
21
+ return { value: cookieName }
22
+ }
23
+
24
+ _createHashSource (type, evidence, location) {
25
+ return `${type}:${evidence.value}`
26
+ }
27
+
28
+ _getExcludedPaths () {
29
+ return EXCLUDED_PATHS
30
+ }
31
+ _checkOCE (context, value) {
32
+ if (value && value.location) {
33
+ return true
34
+ }
35
+ return super._checkOCE(context, value)
36
+ }
37
+
38
+ _getLocation (value) {
39
+ if (!value) {
40
+ return super._getLocation()
41
+ }
42
+
43
+ if (value.location) {
44
+ return value.location
45
+ }
46
+ const location = super._getLocation(value)
47
+ value.location = location
48
+ return location
49
+ }
50
+ }
51
+
52
+ module.exports = CookieAnalyzer
@@ -1,30 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const Analyzer = require('./vulnerability-analyzer')
4
3
  const { INSECURE_COOKIE } = require('../vulnerabilities')
4
+ const CookieAnalyzer = require('./cookie-analyzer')
5
5
 
6
- const EXCLUDED_PATHS = ['node_modules/express/lib/response.js', 'node_modules\\express\\lib\\response.js']
7
-
8
- class InsecureCookieAnalyzer extends Analyzer {
6
+ class InsecureCookieAnalyzer extends CookieAnalyzer {
9
7
  constructor () {
10
- super(INSECURE_COOKIE)
11
- this.addSub('datadog:iast:set-cookie', (cookieInfo) => this.analyze(cookieInfo))
12
- }
13
-
14
- _isVulnerable ({ cookieProperties, cookieValue }) {
15
- return cookieValue && !(cookieProperties && cookieProperties.map(x => x.toLowerCase().trim()).includes('secure'))
16
- }
17
-
18
- _getEvidence ({ cookieName }) {
19
- return { value: cookieName }
20
- }
21
-
22
- _createHashSource (type, evidence, location) {
23
- return `${type}:${evidence.value}`
24
- }
25
-
26
- _getExcludedPaths () {
27
- return EXCLUDED_PATHS
8
+ super(INSECURE_COOKIE, 'secure')
28
9
  }
29
10
  }
30
11
 
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ const { NO_HTTPONLY_COOKIE } = require('../vulnerabilities')
4
+ const CookieAnalyzer = require('./cookie-analyzer')
5
+
6
+ class NoHttponlyCookieAnalyzer extends CookieAnalyzer {
7
+ constructor () {
8
+ super(NO_HTTPONLY_COOKIE, 'HttpOnly')
9
+ }
10
+ }
11
+
12
+ module.exports = new NoHttponlyCookieAnalyzer()
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ const { NO_SAMESITE_COOKIE } = require('../vulnerabilities')
4
+ const CookieAnalyzer = require('./cookie-analyzer')
5
+
6
+ class NoSamesiteCookieAnalyzer extends CookieAnalyzer {
7
+ constructor () {
8
+ super(NO_SAMESITE_COOKIE, 'SameSite=strict')
9
+ }
10
+ }
11
+
12
+ module.exports = new NoSamesiteCookieAnalyzer()
@@ -14,24 +14,28 @@ class SetCookiesHeaderInterceptor extends Plugin {
14
14
  allCookies = [value]
15
15
  }
16
16
  const alreadyCheckedCookies = this._getAlreadyCheckedCookiesInResponse(res)
17
+
18
+ let location
17
19
  allCookies.forEach(cookieString => {
18
20
  if (!alreadyCheckedCookies.includes(cookieString)) {
19
21
  alreadyCheckedCookies.push(cookieString)
20
- setCookieChannel.publish(this._parseCookie(cookieString))
22
+ const parsedCookie = this._parseCookie(cookieString, location)
23
+ setCookieChannel.publish(parsedCookie)
24
+ location = parsedCookie.location
21
25
  }
22
26
  })
23
27
  }
24
28
  })
25
29
  }
26
30
 
27
- _parseCookie (cookieString) {
31
+ _parseCookie (cookieString, location) {
28
32
  const cookieParts = cookieString.split(';')
29
33
  const nameValueParts = cookieParts[0].split('=')
30
34
  const cookieName = nameValueParts[0]
31
35
  const cookieValue = nameValueParts.slice(1).join('=')
32
36
  const cookieProperties = cookieParts.slice(1).map(part => part.trim())
33
37
 
34
- return { cookieName, cookieValue, cookieProperties, cookieString }
38
+ return { cookieName, cookieValue, cookieProperties, cookieString, location }
35
39
  }
36
40
 
37
41
  _getAlreadyCheckedCookiesInResponse (res) {
@@ -6,9 +6,9 @@ const { getRanges } = require('../taint-tracking/operations')
6
6
  const { storage } = require('../../../../../datadog-core')
7
7
  const { getIastContext } = require('../iast-context')
8
8
  const { addVulnerability } = require('../vulnerability-reporter')
9
+ const { getNodeModulesPaths } = require('../path-line')
9
10
 
10
- const EXCLUDED_PATHS = ['node_modules/mysql2', 'node_modules/sequelize', 'node_modules\\mysql2',
11
- 'node_modules\\sequelize']
11
+ const EXCLUDED_PATHS = getNodeModulesPaths('mysql2', 'sequelize')
12
12
 
13
13
  class SqlInjectionAnalyzer extends InjectionAnalyzer {
14
14
  constructor () {
@@ -28,7 +28,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
28
28
 
29
29
  this.addSub('datadog:sequelize:query:finish', () => {
30
30
  const store = storage.getStore()
31
- if (store.sequelizeParentStore) {
31
+ if (store && store.sequelizeParentStore) {
32
32
  storage.enterWith(store.sequelizeParentStore)
33
33
  }
34
34
  })