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.
- package/LICENSE-3rdparty.csv +2 -0
- package/MIGRATING.md +158 -0
- package/README.md +18 -11
- package/index.d.ts +281 -0
- package/package.json +6 -4
- package/packages/datadog-instrumentations/src/cookie.js +21 -0
- package/packages/datadog-instrumentations/src/fetch.js +48 -0
- package/packages/datadog-instrumentations/src/grpc/server.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -3
- package/packages/datadog-instrumentations/src/next.js +2 -2
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +18 -0
- package/packages/datadog-plugin-cypress/src/plugin.js +109 -47
- package/packages/datadog-plugin-cypress/src/support.js +3 -2
- package/packages/datadog-plugin-fetch/src/index.js +36 -0
- package/packages/datadog-plugin-http/src/client.js +24 -8
- package/packages/datadog-plugin-mysql/src/index.js +2 -11
- package/packages/datadog-plugin-tedious/src/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +52 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +3 -22
- package/packages/dd-trace/src/appsec/iast/analyzers/no-httponly-cookie-analyzer.js +12 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/no-samesite-cookie-analyzer.js +12 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +7 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +48 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
- package/packages/dd-trace/src/appsec/iast/index.js +9 -2
- package/packages/dd-trace/src/appsec/iast/path-line.js +13 -0
- package/packages/dd-trace/src/appsec/iast/tags.js +6 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +2 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +13 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/origin-types.js +5 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +24 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +3 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +7 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -3
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +5 -2
- package/packages/dd-trace/src/config.js +13 -0
- package/packages/dd-trace/src/external-logger/src/index.js +126 -0
- package/packages/dd-trace/src/external-logger/test/index.spec.js +147 -0
- package/packages/dd-trace/src/lambda/handler.js +3 -15
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +74 -0
- package/packages/dd-trace/src/opentelemetry/sampler.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span.js +151 -0
- package/packages/dd-trace/src/opentelemetry/span_context.js +44 -0
- package/packages/dd-trace/src/opentelemetry/span_processor.js +50 -0
- package/packages/dd-trace/src/opentelemetry/tracer.js +124 -0
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +72 -0
- package/packages/dd-trace/src/opentracing/span.js +14 -4
- package/packages/dd-trace/src/plugin_manager.js +10 -7
- package/packages/dd-trace/src/plugins/database.js +7 -3
- package/packages/dd-trace/src/plugins/plugin.js +3 -1
- package/packages/dd-trace/src/plugins/util/exec.js +2 -2
- package/packages/dd-trace/src/plugins/util/git.js +51 -24
- package/packages/dd-trace/src/profiling/config.js +2 -0
- package/packages/dd-trace/src/profiling/profiler.js +13 -4
- package/packages/dd-trace/src/proxy.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +24 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +18 -1
- package/packages/dd-trace/src/tracer.js +3 -3
- package/packages/dd-trace/src/util.js +1 -1
- 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 (
|
|
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 {
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
76
|
+
const status = res.status || res.statusCode
|
|
77
|
+
|
|
78
|
+
span.setTag(HTTP_STATUS_CODE, status)
|
|
75
79
|
|
|
76
|
-
if (!this.config.validateStatus(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
13
|
-
service: this.config.
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
})
|