dd-trace
Advanced tools
Comparing version 5.3.0 to 6.0.0-pre-442b2e5
{ | ||
"name": "dd-trace", | ||
"version": "5.3.0", | ||
"version": "6.0.0-pre-442b2e5", | ||
"description": "Datadog APM tracing client for JavaScript", | ||
@@ -74,3 +74,3 @@ "main": "index.js", | ||
"@datadog/native-iast-rewriter": "2.2.3", | ||
"@datadog/native-iast-taint-tracking": "1.6.4", | ||
"@datadog/native-iast-taint-tracking": "1.7.0", | ||
"@datadog/native-metrics": "^2.0.0", | ||
@@ -77,0 +77,0 @@ "@datadog/pprof": "5.0.0", |
@@ -12,3 +12,5 @@ 'use strict' | ||
getTestSuitePath, | ||
getTestParametersString | ||
getTestParametersString, | ||
EFD_STRING, | ||
removeEfdStringFromTestName | ||
} = require('../../dd-trace/src/plugins/util/test') | ||
@@ -61,5 +63,2 @@ const { | ||
const EFD_STRING = "Retried by Datadog's Early Flake Detection" | ||
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g') | ||
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn') | ||
@@ -110,6 +109,2 @@ | ||
function removeEfdTestName (testName) { | ||
return testName.replace(EFD_TEST_NAME_REGEX, '') | ||
} | ||
function getWrappedEnvironment (BaseEnvironment, jestVersion) { | ||
@@ -122,3 +117,2 @@ return class DatadogEnvironment extends BaseEnvironment { | ||
this.testSuite = getTestSuitePath(context.testPath, rootDir) | ||
this.testFileAbsolutePath = context.testPath | ||
this.nameToParams = {} | ||
@@ -129,2 +123,8 @@ this.global._ddtrace = global._ddtrace | ||
const repositoryRoot = this.testEnvironmentOptions._ddRepositoryRoot | ||
if (repositoryRoot) { | ||
this.testSourceFile = getTestSuitePath(context.testPath, repositoryRoot) | ||
} | ||
this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled | ||
@@ -160,3 +160,3 @@ | ||
const describeSuffix = getJestTestName(state.currentDescribeBlock) | ||
return removeEfdTestName(`${describeSuffix} ${event.testName}`).trim() | ||
return removeEfdStringFromTestName(`${describeSuffix} ${event.testName}`).trim() | ||
} | ||
@@ -195,3 +195,3 @@ | ||
if (this.isEarlyFlakeDetectionEnabled) { | ||
const originalTestName = removeEfdTestName(testName) | ||
const originalTestName = removeEfdStringFromTestName(testName) | ||
isNewTest = retriedTestsToNumAttempts.has(originalTestName) | ||
@@ -206,5 +206,5 @@ if (isNewTest) { | ||
testStartCh.publish({ | ||
name: removeEfdTestName(testName), | ||
name: removeEfdStringFromTestName(testName), | ||
suite: this.testSuite, | ||
testFileAbsolutePath: this.testFileAbsolutePath, | ||
testSourceFile: this.testSourceFile, | ||
runner: 'jest-circus', | ||
@@ -260,3 +260,3 @@ testParameters, | ||
suite: this.testSuite, | ||
testFileAbsolutePath: this.testFileAbsolutePath, | ||
testSourceFile: this.testSourceFile, | ||
runner: 'jest-circus', | ||
@@ -647,2 +647,3 @@ frameworkVersion: jestVersion, | ||
_ddEarlyFlakeDetectionNumRetries, | ||
_ddRepositoryRoot, | ||
...restOfTestEnvironmentOptions | ||
@@ -649,0 +650,0 @@ } = testEnvironmentOptions |
@@ -14,3 +14,5 @@ const { createCoverageMap } = require('istanbul-lib-coverage') | ||
fromCoverageMapToCoverage, | ||
getCallSites | ||
getCallSites, | ||
addEfdStringToTestName, | ||
removeEfdStringFromTestName | ||
} = require('../../dd-trace/src/plugins/util/test') | ||
@@ -25,2 +27,3 @@ | ||
const libraryConfigurationCh = channel('ci:mocha:library-configuration') | ||
const knownTestsCh = channel('ci:mocha:known-tests') | ||
const skippableSuitesCh = channel('ci:mocha:test-suite:skippable') | ||
@@ -45,2 +48,3 @@ | ||
const testToStartLine = new WeakMap() | ||
const newTests = {} | ||
@@ -60,2 +64,6 @@ // `isWorker` is true if it's a Mocha worker | ||
let itrCorrelationId = '' | ||
let isEarlyFlakeDetectionEnabled = false | ||
let earlyFlakeDetectionNumRetries = 0 | ||
let isSuitesSkippingEnabled = false | ||
let knownTests = [] | ||
@@ -100,2 +108,22 @@ function getSuitesByTestFile (root) { | ||
function getTestFullName (test) { | ||
return `mocha.${getTestSuitePath(test.file, process.cwd())}.${removeEfdStringFromTestName(test.fullTitle())}` | ||
} | ||
function isNewTest (test) { | ||
return !knownTests.includes(getTestFullName(test)) | ||
} | ||
function retryTest (test) { | ||
const originalTestName = test.title | ||
const suite = test.parent | ||
for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) { | ||
const clonedTest = test.clone() | ||
clonedTest.title = addEfdStringToTestName(originalTestName, retryIndex + 1) | ||
suite.addTest(clonedTest) | ||
clonedTest._ddIsNew = true | ||
clonedTest._ddIsEfdRetry = true | ||
} | ||
} | ||
function getTestAsyncResource (test) { | ||
@@ -131,2 +159,15 @@ if (!test.fn) { | ||
shimmer.wrap(Runner.prototype, 'runTests', runTests => function (suite, fn) { | ||
if (isEarlyFlakeDetectionEnabled) { | ||
// by the time we reach `this.on('test')`, it is too late. We need to add retries here | ||
suite.tests.forEach(test => { | ||
if (!test.isPending() && isNewTest(test)) { | ||
test._ddIsNew = true | ||
retryTest(test) | ||
} | ||
}) | ||
} | ||
return runTests.apply(this, arguments) | ||
}) | ||
shimmer.wrap(Runner.prototype, 'run', run => function () { | ||
@@ -153,2 +194,20 @@ if (!testStartCh.hasSubscribers || isWorker) { | ||
if (isEarlyFlakeDetectionEnabled) { | ||
/** | ||
* If Early Flake Detection (EFD) is enabled the logic is as follows: | ||
* - If all attempts for a test are failing, the test has failed and we will let the test process fail. | ||
* - If just a single attempt passes, we will prevent the test process from failing. | ||
* The rationale behind is the following: you may still be able to block your CI pipeline by gating | ||
* on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too. | ||
*/ | ||
for (const tests of Object.values(newTests)) { | ||
const failingNewTests = tests.filter(test => test.isFailed()) | ||
const areAllNewTestsFailing = failingNewTests.length === tests.length | ||
if (failingNewTests.length && !areAllNewTestsFailing) { | ||
this.stats.failures -= failingNewTests.length | ||
this.failures -= failingNewTests.length | ||
} | ||
} | ||
} | ||
if (status === 'fail') { | ||
@@ -178,3 +237,4 @@ error = new Error(`Failed tests: ${this.failures}.`) | ||
hasUnskippableSuites: !!unskippableSuites.length, | ||
error | ||
error, | ||
isEarlyFlakeDetectionEnabled | ||
}) | ||
@@ -264,4 +324,31 @@ })) | ||
testToAr.set(test.fn, asyncResource) | ||
const { | ||
file: testSuiteAbsolutePath, | ||
title, | ||
_ddIsNew: isNew, | ||
_ddIsEfdRetry: isEfdRetry | ||
} = test | ||
const testInfo = { | ||
testName: test.fullTitle(), | ||
testSuiteAbsolutePath, | ||
title, | ||
isNew, | ||
isEfdRetry, | ||
testStartLine | ||
} | ||
// We want to store the result of the new tests | ||
if (isNew) { | ||
const testFullName = getTestFullName(test) | ||
if (newTests[testFullName]) { | ||
newTests[testFullName].push(test) | ||
} else { | ||
newTests[testFullName] = [test] | ||
} | ||
} | ||
asyncResource.runInAsyncScope(() => { | ||
testStartCh.publish({ test, testStartLine }) | ||
testStartCh.publish(testInfo) | ||
}) | ||
@@ -335,6 +422,19 @@ }) | ||
this.on('pending', (test) => { | ||
const testStartLine = testToStartLine.get(test) | ||
const { | ||
file: testSuiteAbsolutePath, | ||
title | ||
} = test | ||
const testInfo = { | ||
testName: test.fullTitle(), | ||
testSuiteAbsolutePath, | ||
title, | ||
testStartLine | ||
} | ||
const asyncResource = getTestAsyncResource(test) | ||
if (asyncResource) { | ||
asyncResource.runInAsyncScope(() => { | ||
skipCh.publish(test) | ||
skipCh.publish(testInfo) | ||
}) | ||
@@ -351,3 +451,3 @@ } else { | ||
skippedTestAsyncResource.runInAsyncScope(() => { | ||
skipCh.publish(test) | ||
skipCh.publish(testInfo) | ||
}) | ||
@@ -372,4 +472,4 @@ } | ||
return { | ||
it: function (name) { | ||
parameterizedTestCh.publish({ name, params }) | ||
it: function (title) { | ||
parameterizedTestCh.publish({ title, params }) | ||
it.apply(this, arguments) | ||
@@ -440,13 +540,39 @@ }, | ||
const onReceivedConfiguration = ({ err }) => { | ||
const onReceivedKnownTests = ({ err, knownTests: receivedKnownTests }) => { | ||
if (err) { | ||
return global.run() | ||
knownTests = [] | ||
isEarlyFlakeDetectionEnabled = false | ||
} else { | ||
knownTests = receivedKnownTests | ||
} | ||
if (!skippableSuitesCh.hasSubscribers) { | ||
if (isSuitesSkippingEnabled) { | ||
skippableSuitesCh.publish({ | ||
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites) | ||
}) | ||
} else { | ||
global.run() | ||
} | ||
} | ||
const onReceivedConfiguration = ({ err, libraryConfig }) => { | ||
if (err || !skippableSuitesCh.hasSubscribers || !knownTestsCh.hasSubscribers) { | ||
return global.run() | ||
} | ||
skippableSuitesCh.publish({ | ||
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites) | ||
}) | ||
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled | ||
isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled | ||
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries | ||
if (isEarlyFlakeDetectionEnabled) { | ||
knownTestsCh.publish({ | ||
onDone: mochaRunAsyncResource.bind(onReceivedKnownTests) | ||
}) | ||
} else if (isSuitesSkippingEnabled) { | ||
skippableSuitesCh.publish({ | ||
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites) | ||
}) | ||
} else { | ||
global.run() | ||
} | ||
} | ||
@@ -453,0 +579,0 @@ |
@@ -118,3 +118,4 @@ const { | ||
} | ||
if (suiteStats.tests !== undefined && suiteStats.tests === suiteStats.pending) { | ||
if (suiteStats.tests !== undefined && | ||
(suiteStats.tests === suiteStats.pending || suiteStats.tests === suiteStats.skipped)) { | ||
return 'skip' | ||
@@ -250,2 +251,6 @@ } | ||
testSuiteTags[TEST_MODULE_ID] = testModuleSpan.context().toSpanId() | ||
// If testSuiteSpan couldn't be created, we'll use the testModuleSpan as the parent | ||
if (!testSuiteSpan) { | ||
testSuiteTags[TEST_SUITE_ID] = testModuleSpan.context().toSpanId() | ||
} | ||
} | ||
@@ -291,2 +296,15 @@ | ||
function getTestSuiteSpan (suite) { | ||
const testSuiteSpanMetadata = getTestSuiteCommonTags(command, frameworkVersion, suite, TEST_FRAMEWORK_NAME) | ||
ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite') | ||
return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_suite`, { | ||
childOf: testModuleSpan, | ||
tags: { | ||
[COMPONENT]: TEST_FRAMEWORK_NAME, | ||
...testEnvironmentMetadata, | ||
...testSuiteSpanMetadata | ||
} | ||
}) | ||
} | ||
on('before:run', (details) => { | ||
@@ -355,2 +373,9 @@ return getLibraryConfiguration(tracer, testConfiguration).then(({ err, libraryConfig }) => { | ||
if (!testSuiteSpan) { | ||
// dd:testSuiteStart hasn't been triggered for whatever reason | ||
// We will create the test suite span on the spot if that's the case | ||
log.warn('There was an error creating the test suite event.') | ||
testSuiteSpan = getTestSuiteSpan(spec.relative) | ||
} | ||
// Get tests that didn't go through `dd:afterEach` | ||
@@ -477,12 +502,3 @@ // and create a skipped test span for each of them | ||
} | ||
const testSuiteSpanMetadata = getTestSuiteCommonTags(command, frameworkVersion, suite, TEST_FRAMEWORK_NAME) | ||
testSuiteSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_suite`, { | ||
childOf: testModuleSpan, | ||
tags: { | ||
[COMPONENT]: TEST_FRAMEWORK_NAME, | ||
...testEnvironmentMetadata, | ||
...testSuiteSpanMetadata | ||
} | ||
}) | ||
ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite') | ||
testSuiteSpan = getTestSuiteSpan(suite) | ||
return null | ||
@@ -489,0 +505,0 @@ }, |
@@ -19,3 +19,2 @@ const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin') | ||
TEST_SOURCE_FILE, | ||
getTestSuitePath, | ||
TEST_IS_NEW, | ||
@@ -145,2 +144,3 @@ TEST_EARLY_FLAKE_IS_RETRY, | ||
config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0 | ||
config._ddRepositoryRoot = this.repositoryRoot | ||
}) | ||
@@ -316,3 +316,3 @@ }) | ||
testStartLine, | ||
testFileAbsolutePath, | ||
testSourceFile, | ||
isNew, | ||
@@ -330,8 +330,4 @@ isEfdRetry | ||
} | ||
if (testFileAbsolutePath) { | ||
extraTags[TEST_SOURCE_FILE] = getTestSuitePath(testFileAbsolutePath, this.repositoryRoot) | ||
} else { | ||
// If for whatever we don't have the full path, we'll set the source file to the suite name | ||
extraTags[TEST_SOURCE_FILE] = suite | ||
} | ||
// If for whatever we don't have the source file, we'll fall back to the suite name | ||
extraTags[TEST_SOURCE_FILE] = testSourceFile || suite | ||
@@ -338,0 +334,0 @@ if (isNew) { |
'use strict' | ||
const dc = require('dc-polyfill') | ||
const { getMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor') | ||
const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer') | ||
const afterStartCh = dc.channel('dd-trace:kafkajs:consumer:afterStart') | ||
const beforeFinishCh = dc.channel('dd-trace:kafkajs:consumer:beforeFinish') | ||
class KafkajsConsumerPlugin extends ConsumerPlugin { | ||
@@ -82,3 +86,15 @@ static get id () { return 'kafkajs' } | ||
} | ||
if (afterStartCh.hasSubscribers) { | ||
afterStartCh.publish({ topic, partition, message, groupId }) | ||
} | ||
} | ||
finish () { | ||
if (beforeFinishCh.hasSubscribers) { | ||
beforeFinishCh.publish() | ||
} | ||
super.finish() | ||
} | ||
} | ||
@@ -85,0 +101,0 @@ |
@@ -19,3 +19,7 @@ 'use strict' | ||
ITR_CORRELATION_ID, | ||
TEST_SOURCE_FILE | ||
TEST_SOURCE_FILE, | ||
removeEfdStringFromTestName, | ||
TEST_IS_NEW, | ||
TEST_EARLY_FLAKE_IS_RETRY, | ||
TEST_EARLY_FLAKE_IS_ENABLED | ||
} = require('../../dd-trace/src/plugins/util/test') | ||
@@ -43,3 +47,3 @@ const { COMPONENT } = require('../../dd-trace/src/constants') | ||
this._testSuites = new Map() | ||
this._testNameToParams = {} | ||
this._testTitleToParams = {} | ||
this.sourceRoot = process.cwd() | ||
@@ -136,5 +140,5 @@ | ||
this.addSub('ci:mocha:test:start', ({ test, testStartLine }) => { | ||
this.addSub('ci:mocha:test:start', (testInfo) => { | ||
const store = storage.getStore() | ||
const span = this.startTestSpan(test, testStartLine) | ||
const span = this.startTestSpan(testInfo) | ||
@@ -162,3 +166,3 @@ this.enter(span, store) | ||
this.addSub('ci:mocha:test:skip', (test) => { | ||
this.addSub('ci:mocha:test:skip', (testInfo) => { | ||
const store = storage.getStore() | ||
@@ -168,3 +172,3 @@ // skipped through it.skip, so the span is not created yet | ||
if (!store) { | ||
const testSpan = this.startTestSpan(test) | ||
const testSpan = this.startTestSpan(testInfo) | ||
this.enter(testSpan, store) | ||
@@ -187,4 +191,4 @@ } | ||
this.addSub('ci:mocha:test:parameterize', ({ name, params }) => { | ||
this._testNameToParams[name] = params | ||
this.addSub('ci:mocha:test:parameterize', ({ title, params }) => { | ||
this._testTitleToParams[title] = params | ||
}) | ||
@@ -199,3 +203,4 @@ | ||
hasUnskippableSuites, | ||
error | ||
error, | ||
isEarlyFlakeDetectionEnabled | ||
}) => { | ||
@@ -227,2 +232,6 @@ if (this.testSessionSpan) { | ||
if (isEarlyFlakeDetectionEnabled) { | ||
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true') | ||
} | ||
this.testModuleSpan.finish() | ||
@@ -239,8 +248,15 @@ this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module') | ||
startTestSpan (test, testStartLine) { | ||
const testName = test.fullTitle() | ||
const { file: testSuiteAbsolutePath, title } = test | ||
startTestSpan (testInfo) { | ||
const { | ||
testSuiteAbsolutePath, | ||
title, | ||
isNew, | ||
isEfdRetry, | ||
testStartLine | ||
} = testInfo | ||
const testName = removeEfdStringFromTestName(testInfo.testName) | ||
const extraTags = {} | ||
const testParametersString = getTestParametersString(this._testNameToParams, title) | ||
const testParametersString = getTestParametersString(this._testTitleToParams, title) | ||
if (testParametersString) { | ||
@@ -257,6 +273,4 @@ extraTags[TEST_PARAMETERS] = testParametersString | ||
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot) | ||
if (testSourceFile) { | ||
extraTags[TEST_SOURCE_FILE] = testSourceFile | ||
if (this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot) { | ||
extraTags[TEST_SOURCE_FILE] = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot) | ||
} else { | ||
@@ -266,2 +280,9 @@ extraTags[TEST_SOURCE_FILE] = testSuite | ||
if (isNew) { | ||
extraTags[TEST_IS_NEW] = 'true' | ||
if (isEfdRetry) { | ||
extraTags[TEST_EARLY_FLAKE_IS_RETRY] = 'true' | ||
} | ||
} | ||
return super.startTestSpan(testName, testSuite, testSuiteSpan, extraTags) | ||
@@ -268,0 +289,0 @@ } |
@@ -104,2 +104,10 @@ 'use strict' | ||
enable () { | ||
this.configure(true) | ||
} | ||
disable () { | ||
this.configure(false) | ||
} | ||
onConfigure () {} | ||
@@ -106,0 +114,0 @@ |
@@ -25,3 +25,3 @@ const vulnerabilityReporter = require('./vulnerability-reporter') | ||
function enable (config, _tracer) { | ||
iastTelemetry.configure(config, config.iast && config.iast.telemetryVerbosity) | ||
iastTelemetry.configure(config, config.iast?.telemetryVerbosity) | ||
enableAllAnalyzers(config) | ||
@@ -47,3 +47,3 @@ enableTaintTracking(config.iast, iastTelemetry.verbosity) | ||
function onIncomingHttpRequestStart (data) { | ||
if (data && data.req) { | ||
if (data?.req) { | ||
const store = storage.getStore() | ||
@@ -73,7 +73,7 @@ if (store) { | ||
function onIncomingHttpRequestEnd (data) { | ||
if (data && data.req) { | ||
if (data?.req) { | ||
const store = storage.getStore() | ||
const topContext = web.getContext(data.req) | ||
const iastContext = iastContextFunctions.getIastContext(store, topContext) | ||
if (iastContext && iastContext.rootSpan) { | ||
if (iastContext?.rootSpan) { | ||
iastResponseEnd.publish(data) | ||
@@ -80,0 +80,0 @@ |
@@ -55,3 +55,3 @@ 'use strict' | ||
function acquireRequest (rootSpan) { | ||
if (availableRequest > 0) { | ||
if (availableRequest > 0 && rootSpan) { | ||
const sampling = config && typeof config.requestSampling === 'number' | ||
@@ -58,0 +58,0 @@ ? config.requestSampling : 30 |
@@ -5,2 +5,3 @@ 'use strict' | ||
{ src: 'concat' }, | ||
{ src: 'parse' }, | ||
{ src: 'plusOperator', operator: true }, | ||
@@ -7,0 +8,0 @@ { src: 'random' }, |
@@ -13,3 +13,6 @@ 'use strict' | ||
const taintTrackingPlugin = require('./plugin') | ||
const kafkaConsumerPlugin = require('./plugins/kafka') | ||
const kafkaContextPlugin = require('../context/kafka-ctx-plugin') | ||
module.exports = { | ||
@@ -20,2 +23,6 @@ enableTaintTracking (config, telemetryVerbosity) { | ||
taintTrackingPlugin.enable() | ||
kafkaContextPlugin.enable() | ||
kafkaConsumerPlugin.enable() | ||
setMaxTransactions(config.maxConcurrentRequests) | ||
@@ -27,2 +34,5 @@ }, | ||
taintTrackingPlugin.disable() | ||
kafkaContextPlugin.disable() | ||
kafkaConsumerPlugin.disable() | ||
}, | ||
@@ -29,0 +39,0 @@ setMaxTransactions: setMaxTransactions, |
@@ -5,3 +5,2 @@ 'use strict' | ||
const { IAST_TRANSACTION_ID } = require('../iast-context') | ||
const iastLog = require('../iast-log') | ||
const iastTelemetry = require('../telemetry') | ||
@@ -11,2 +10,3 @@ const { REQUEST_TAINTED } = require('../telemetry/iast-metric') | ||
const { getTaintTrackingImpl, getTaintTrackingNoop } = require('./taint-tracking-impl') | ||
const { taintObject } = require('./operations-taint-object') | ||
@@ -39,3 +39,3 @@ function createTransaction (id, iastContext) { | ||
function newTaintedString (iastContext, string, name, type) { | ||
let result = string | ||
let result | ||
const transactionId = iastContext?.[IAST_TRANSACTION_ID] | ||
@@ -50,46 +50,9 @@ if (transactionId) { | ||
function taintObject (iastContext, object, type, keyTainting, keyType) { | ||
let result = object | ||
function newTaintedObject (iastContext, obj, name, type) { | ||
let result | ||
const transactionId = iastContext?.[IAST_TRANSACTION_ID] | ||
if (transactionId) { | ||
const queue = [{ parent: null, property: null, value: object }] | ||
const visited = new WeakSet() | ||
while (queue.length > 0) { | ||
const { parent, property, value, key } = queue.pop() | ||
if (value === null) { | ||
continue | ||
} | ||
try { | ||
if (typeof value === 'string') { | ||
const tainted = TaintedUtils.newTaintedString(transactionId, value, property, type) | ||
if (!parent) { | ||
result = tainted | ||
} else { | ||
if (keyTainting && key) { | ||
const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType) | ||
parent[taintedProperty] = tainted | ||
} else { | ||
parent[key] = tainted | ||
} | ||
} | ||
} else if (typeof value === 'object' && !visited.has(value)) { | ||
visited.add(value) | ||
const keys = Object.keys(value) | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i] | ||
queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key], key }) | ||
} | ||
if (parent && keyTainting && key) { | ||
const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType) | ||
parent[taintedProperty] = value | ||
} | ||
} | ||
} catch (e) { | ||
iastLog.error(`Error visiting property : ${property}`).errorAndPublish(e) | ||
} | ||
} | ||
result = TaintedUtils.newTaintedObject(transactionId, obj, name, type) | ||
} else { | ||
result = obj | ||
} | ||
@@ -100,3 +63,3 @@ return result | ||
function isTainted (iastContext, string) { | ||
let result = false | ||
let result | ||
const transactionId = iastContext?.[IAST_TRANSACTION_ID] | ||
@@ -112,3 +75,3 @@ if (transactionId) { | ||
function getRanges (iastContext, string) { | ||
let result = [] | ||
let result | ||
const transactionId = iastContext?.[IAST_TRANSACTION_ID] | ||
@@ -157,2 +120,3 @@ if (transactionId) { | ||
newTaintedString, | ||
newTaintedObject, | ||
taintObject, | ||
@@ -159,0 +123,0 @@ isTainted, |
@@ -6,3 +6,3 @@ 'use strict' | ||
const { storage } = require('../../../../../datadog-core') | ||
const { taintObject, newTaintedString } = require('./operations') | ||
const { taintObject, newTaintedString, getRanges } = require('./operations') | ||
const { | ||
@@ -69,2 +69,14 @@ HTTP_REQUEST_BODY, | ||
this.addSub( | ||
{ channelName: 'apm:graphql:resolve:start', tag: HTTP_REQUEST_BODY }, | ||
(data) => { | ||
const iastContext = getIastContext(storage.getStore()) | ||
const source = data.context?.source | ||
const ranges = source && getRanges(iastContext, source) | ||
if (ranges?.length) { | ||
this._taintTrackingHandler(ranges[0].iinfo.type, data.args, null, iastContext) | ||
} | ||
} | ||
) | ||
// this is a special case to increment INSTRUMENTED_SOURCE metric for header | ||
@@ -109,12 +121,4 @@ this.addInstrumentedSource('http', [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME]) | ||
} | ||
enable () { | ||
this.configure(true) | ||
} | ||
disable () { | ||
this.configure(false) | ||
} | ||
} | ||
module.exports = new TaintTrackingPlugin() |
@@ -12,3 +12,5 @@ 'use strict' | ||
HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter', | ||
HTTP_REQUEST_URI: 'http.request.uri' | ||
HTTP_REQUEST_URI: 'http.request.uri', | ||
KAFKA_MESSAGE_KEY: 'kafka.message.key', | ||
KAFKA_MESSAGE_VALUE: 'kafka.message.value' | ||
} |
@@ -10,5 +10,8 @@ 'use strict' | ||
const { isDebugAllowed } = require('../telemetry/verbosity') | ||
const { taintObject } = require('./operations-taint-object') | ||
const mathRandomCallCh = dc.channel('datadog:random:call') | ||
const JSON_VALUE = 'json.value' | ||
function noop (res) { return res } | ||
@@ -18,4 +21,5 @@ // NOTE: methods of this object must be synchronized with csi-methods.js file definitions! | ||
const TaintTrackingNoop = { | ||
concat: noop, | ||
parse: noop, | ||
plusOperator: noop, | ||
concat: noop, | ||
random: noop, | ||
@@ -31,3 +35,3 @@ replace: noop, | ||
function getTransactionId (iastContext) { | ||
return iastContext && iastContext[iastContextFunctions.IAST_TRANSACTION_ID] | ||
return iastContext?.[iastContextFunctions.IAST_TRANSACTION_ID] | ||
} | ||
@@ -127,2 +131,25 @@ | ||
return res | ||
}, | ||
parse: function (res, fn, target, json) { | ||
if (fn === JSON.parse) { | ||
try { | ||
const iastContext = getContext() | ||
const transactionId = getTransactionId(iastContext) | ||
if (transactionId) { | ||
const ranges = TaintedUtils.getRanges(transactionId, json) | ||
// TODO: first version. | ||
// here we are losing the original source because taintObject always creates a new tainted | ||
if (ranges?.length > 0) { | ||
const range = ranges.find(range => range.iinfo?.type) | ||
res = taintObject(iastContext, res, range?.iinfo.type || JSON_VALUE) | ||
} | ||
} | ||
} catch (e) { | ||
iastLog.error(e) | ||
} | ||
} | ||
return res | ||
} | ||
@@ -129,0 +156,0 @@ } |
@@ -23,3 +23,3 @@ 'use strict' | ||
if (val !== null && typeof val === 'object') { | ||
if (val !== null && typeof val === 'object' && depth > 0) { | ||
iterateObject(val, fn, nextLevelKeys, depth - 1) | ||
@@ -26,0 +26,0 @@ } |
@@ -17,3 +17,4 @@ 'use strict' | ||
APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n, | ||
APM_TRACING_CUSTOM_TAGS: 1n << 15n | ||
APM_TRACING_CUSTOM_TAGS: 1n << 15n, | ||
APM_TRACING_ENABLED: 1n << 19n | ||
} |
@@ -17,2 +17,3 @@ 'use strict' | ||
rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_SAMPLE_RATE, true) | ||
rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_ENABLED, true) | ||
@@ -19,0 +20,0 @@ const activation = Activation.fromConfig(config) |
@@ -54,3 +54,3 @@ const { | ||
this.addSub(`ci:${this.constructor.id}:test-suite:skippable`, ({ onDone }) => { | ||
if (!this.tracer._exporter || !this.tracer._exporter.getSkippableSuites) { | ||
if (!this.tracer._exporter?.getSkippableSuites) { | ||
return onDone({ err: new Error('CI Visibility was not initialized correctly') }) | ||
@@ -57,0 +57,0 @@ } |
@@ -77,2 +77,6 @@ const path = require('path') | ||
// Early flake detection util strings | ||
const EFD_STRING = "Retried by Datadog's Early Flake Detection" | ||
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g') | ||
module.exports = { | ||
@@ -135,3 +139,7 @@ TEST_CODE_OWNERS, | ||
removeInvalidMetadata, | ||
parseAnnotations | ||
parseAnnotations, | ||
EFD_STRING, | ||
EFD_TEST_NAME_REGEX, | ||
removeEfdStringFromTestName, | ||
addEfdStringToTestName | ||
} | ||
@@ -557,1 +565,9 @@ | ||
} | ||
function addEfdStringToTestName (testName, numAttempt) { | ||
return `${EFD_STRING} (#${numAttempt}): ${testName}` | ||
} | ||
function removeEfdStringFromTestName (testName) { | ||
return testName.replace(EFD_TEST_NAME_REGEX, '') | ||
} |
@@ -30,5 +30,2 @@ | ||
} | ||
if (site === 'datadoghq.eu') { | ||
return 'https://instrumentation-telemetry-intake.eu1.datadoghq.com' | ||
} | ||
return `https://instrumentation-telemetry-intake.${site}` | ||
@@ -35,0 +32,0 @@ } |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1646452
511
48005
1
+ Added@datadog/native-iast-taint-tracking@1.7.0(transitive)
- Removed@datadog/native-iast-taint-tracking@1.6.4(transitive)