dd-trace 4.0.0-pre-071951e → 4.0.0-pre-4e7da80
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/index.d.ts +16 -0
- package/package.json +4 -4
- package/packages/datadog-instrumentations/src/jest.js +1 -0
- package/packages/datadog-plugin-http/src/client.js +2 -1
- package/packages/datadog-plugin-http2/src/client.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +22 -5
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +40 -4
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js +37 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +29 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +35 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +95 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +144 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +113 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +8 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -76
- package/packages/dd-trace/src/config.js +27 -1
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/git_metadata_tagger.js +17 -0
- package/packages/dd-trace/src/plugins/util/ci.js +62 -7
- package/packages/dd-trace/src/plugins/util/tags.js +5 -1
- package/packages/dd-trace/src/span_processor.js +3 -0
- package/packages/dd-trace/src/tracer.js +9 -0
package/index.d.ts
CHANGED
|
@@ -77,6 +77,10 @@ export declare interface Tracer extends opentracing.Tracer {
|
|
|
77
77
|
* span will finish when that callback is called.
|
|
78
78
|
* * The function doesn't accept a callback and doesn't return a promise, in
|
|
79
79
|
* which case the span will finish at the end of the function execution.
|
|
80
|
+
*
|
|
81
|
+
* If the `orphanable` option is set to false, the function will not be traced
|
|
82
|
+
* unless there is already an active span or `childOf` option. Note that this
|
|
83
|
+
* option is deprecated and has been removed in version 4.0.
|
|
80
84
|
*/
|
|
81
85
|
trace<T> (name: string, fn: (span?: Span, fn?: (error?: Error) => any) => T): T;
|
|
82
86
|
trace<T> (name: string, options: TraceOptions & SpanOptions, fn: (span?: Span, done?: (error?: Error) => string) => T): T;
|
|
@@ -440,6 +444,11 @@ export declare interface TracerOptions {
|
|
|
440
444
|
* Whether to enable vulnerability deduplication
|
|
441
445
|
*/
|
|
442
446
|
deduplicationEnabled?: boolean
|
|
447
|
+
/**
|
|
448
|
+
* Whether to enable vulnerability redaction
|
|
449
|
+
* @default true
|
|
450
|
+
*/
|
|
451
|
+
redactionEnabled?: boolean
|
|
443
452
|
}
|
|
444
453
|
};
|
|
445
454
|
|
|
@@ -485,6 +494,13 @@ export declare interface TracerOptions {
|
|
|
485
494
|
*/
|
|
486
495
|
logLevel?: 'error' | 'debug'
|
|
487
496
|
|
|
497
|
+
/**
|
|
498
|
+
* If false, require a parent in order to trace.
|
|
499
|
+
* @default true
|
|
500
|
+
* @deprecated since version 4.0
|
|
501
|
+
*/
|
|
502
|
+
orphanable?: boolean
|
|
503
|
+
|
|
488
504
|
/**
|
|
489
505
|
* Enables DBM to APM link using tag injection.
|
|
490
506
|
* @default 'disabled'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "4.0.0-pre-
|
|
3
|
+
"version": "4.0.0-pre-4e7da80",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@datadog/native-appsec": "^3.1.0",
|
|
70
70
|
"@datadog/native-iast-rewriter": "2.0.1",
|
|
71
|
-
"@datadog/native-iast-taint-tracking": "^1.4.
|
|
72
|
-
"@datadog/native-metrics": "^
|
|
73
|
-
"@datadog/pprof": "^2.2.
|
|
71
|
+
"@datadog/native-iast-taint-tracking": "^1.4.1",
|
|
72
|
+
"@datadog/native-metrics": "^2.0.0",
|
|
73
|
+
"@datadog/pprof": "^2.2.1",
|
|
74
74
|
"@datadog/sketches-js": "^2.1.0",
|
|
75
75
|
"crypto-randomuuid": "^1.0.0",
|
|
76
76
|
"diagnostics_channel": "^1.1.0",
|
|
@@ -454,6 +454,7 @@ addHook({
|
|
|
454
454
|
}, jestConfigSyncWrapper)
|
|
455
455
|
|
|
456
456
|
function jasmineAsyncInstallWraper (jasmineAsyncInstallExport, jestVersion) {
|
|
457
|
+
log.warn('jest-jasmine2 support is removed from dd-trace@v4. Consider changing to jest-circus as `testRunner`.')
|
|
457
458
|
return function (globalConfig, globalInput) {
|
|
458
459
|
globalInput._ddtrace = global._ddtrace
|
|
459
460
|
shimmer.wrap(globalInput.jasmine.Spec.prototype, 'execute', execute => function (onComplete) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
3
|
+
const { COMMAND_INJECTION } = require('../vulnerabilities')
|
|
3
4
|
|
|
4
5
|
class CommandInjectionAnalyzer extends InjectionAnalyzer {
|
|
5
6
|
constructor () {
|
|
6
|
-
super(
|
|
7
|
+
super(COMMAND_INJECTION)
|
|
7
8
|
this.addSub('datadog:child_process:execution:start', ({ command }) => this.analyze(command))
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
3
|
+
const { LDAP_INJECTION } = require('../vulnerabilities')
|
|
3
4
|
|
|
4
5
|
class LdapInjectionAnalyzer extends InjectionAnalyzer {
|
|
5
6
|
constructor () {
|
|
6
|
-
super(
|
|
7
|
+
super(LDAP_INJECTION)
|
|
7
8
|
this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -4,10 +4,11 @@ const path = require('path')
|
|
|
4
4
|
const { getIastContext } = require('../iast-context')
|
|
5
5
|
const { storage } = require('../../../../../datadog-core')
|
|
6
6
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
7
|
+
const { PATH_TRAVERSAL } = require('../vulnerabilities')
|
|
7
8
|
|
|
8
9
|
class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
9
10
|
constructor () {
|
|
10
|
-
super(
|
|
11
|
+
super(PATH_TRAVERSAL)
|
|
11
12
|
this.addSub('apm:fs:operation:start', obj => {
|
|
12
13
|
const pathArguments = []
|
|
13
14
|
if (obj.dest) {
|
|
@@ -40,13 +41,29 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
|
40
41
|
this.analyze(pathArguments)
|
|
41
42
|
})
|
|
42
43
|
|
|
43
|
-
this.exclusionList = [
|
|
44
|
+
this.exclusionList = [
|
|
45
|
+
path.join('node_modules', 'send') + path.sep
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
this.internalExclusionList = [
|
|
49
|
+
'node:fs',
|
|
50
|
+
'node:internal/fs',
|
|
51
|
+
'node:internal\\fs',
|
|
52
|
+
'fs.js',
|
|
53
|
+
'internal/fs',
|
|
54
|
+
'internal\\fs'
|
|
55
|
+
]
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
_isExcluded (location) {
|
|
47
|
-
let ret =
|
|
59
|
+
let ret = true
|
|
48
60
|
if (location && location.path) {
|
|
49
|
-
|
|
61
|
+
// Exclude from reporting those vulnerabilities which location is from an internal fs call
|
|
62
|
+
if (location.isInternal) {
|
|
63
|
+
ret = this.internalExclusionList.some(elem => location.path.includes(elem))
|
|
64
|
+
} else {
|
|
65
|
+
ret = this.exclusionList.some(elem => location.path.includes(elem))
|
|
66
|
+
}
|
|
50
67
|
}
|
|
51
68
|
return ret
|
|
52
69
|
}
|
|
@@ -59,7 +76,7 @@ class PathTraversalAnalyzer extends InjectionAnalyzer {
|
|
|
59
76
|
|
|
60
77
|
if (value && value.constructor === Array) {
|
|
61
78
|
for (const val of value) {
|
|
62
|
-
if (this._isVulnerable(val, iastContext)) {
|
|
79
|
+
if (this._isVulnerable(val, iastContext) && this._checkOCE(iastContext)) {
|
|
63
80
|
this._report(val, iastContext)
|
|
64
81
|
// no support several evidences in the same vulnerability, just report the 1st one
|
|
65
82
|
break
|
|
@@ -1,12 +1,48 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
|
|
2
3
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { SQL_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
const { getRanges } = require('../taint-tracking/operations')
|
|
6
|
+
const { storage } = require('../../../../../datadog-core')
|
|
7
|
+
const { getIastContext } = require('../iast-context')
|
|
8
|
+
const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
|
|
3
9
|
|
|
4
10
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
5
11
|
constructor () {
|
|
6
|
-
super(
|
|
7
|
-
this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql))
|
|
8
|
-
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql))
|
|
9
|
-
this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text))
|
|
12
|
+
super(SQL_INJECTION)
|
|
13
|
+
this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
|
|
14
|
+
this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql, 'MYSQL'))
|
|
15
|
+
this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, 'POSTGRES'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_getEvidence (value, iastContext, dialect) {
|
|
19
|
+
const ranges = getRanges(iastContext, value)
|
|
20
|
+
return { value, ranges, dialect }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
analyze (value, dialect) {
|
|
24
|
+
const store = storage.getStore()
|
|
25
|
+
const iastContext = getIastContext(store)
|
|
26
|
+
if (store && !iastContext) return
|
|
27
|
+
this._reportIfVulnerable(value, iastContext, dialect)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_reportIfVulnerable (value, context, dialect) {
|
|
31
|
+
if (this._isVulnerable(value, context) && this._checkOCE(context)) {
|
|
32
|
+
this._report(value, context, dialect)
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_report (value, context, dialect) {
|
|
39
|
+
const evidence = this._getEvidence(value, context, dialect)
|
|
40
|
+
const location = this._getLocation()
|
|
41
|
+
if (!this._isExcluded(location)) {
|
|
42
|
+
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
43
|
+
const vulnerability = createVulnerability(this._type, evidence, spanId, location)
|
|
44
|
+
addVulnerability(context, vulnerability)
|
|
45
|
+
}
|
|
10
46
|
}
|
|
11
47
|
}
|
|
12
48
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const Analyzer = require('./vulnerability-analyzer')
|
|
3
|
+
const { WEAK_CIPHER } = require('../vulnerabilities')
|
|
3
4
|
|
|
4
5
|
const INSECURE_CIPHERS = new Set([
|
|
5
6
|
'des', 'des-cbc', 'des-cfb', 'des-cfb1', 'des-cfb8', 'des-ecb', 'des-ede', 'des-ede-cbc', 'des-ede-cfb',
|
|
@@ -12,7 +13,7 @@ const INSECURE_CIPHERS = new Set([
|
|
|
12
13
|
|
|
13
14
|
class WeakCipherAnalyzer extends Analyzer {
|
|
14
15
|
constructor () {
|
|
15
|
-
super(
|
|
16
|
+
super(WEAK_CIPHER)
|
|
16
17
|
this.addSub('datadog:crypto:cipher:start', ({ algorithm }) => this.analyze(algorithm))
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const Analyzer = require('./vulnerability-analyzer')
|
|
3
|
+
const { WEAK_HASH } = require('../vulnerabilities')
|
|
3
4
|
|
|
4
5
|
const INSECURE_HASH_ALGORITHMS = new Set([
|
|
5
6
|
'md4', 'md4WithRSAEncryption', 'RSA-MD4',
|
|
@@ -9,7 +10,7 @@ const INSECURE_HASH_ALGORITHMS = new Set([
|
|
|
9
10
|
|
|
10
11
|
class WeakHashAnalyzer extends Analyzer {
|
|
11
12
|
constructor () {
|
|
12
|
-
super(
|
|
13
|
+
super(WEAK_HASH)
|
|
13
14
|
this.addSub('datadog:crypto:hashing:start', ({ algorithm }) => this.analyze(algorithm))
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -45,7 +45,8 @@ function getFirstNonDDPathAndLineFromCallsites (callsites) {
|
|
|
45
45
|
if (!isExcluded(callsite) && filepath.indexOf(pathLine.ddBasePath) === -1) {
|
|
46
46
|
return {
|
|
47
47
|
path: path.relative(process.cwd(), filepath),
|
|
48
|
-
line: callsite.getLineNumber()
|
|
48
|
+
line: callsite.getLineNumber(),
|
|
49
|
+
isInternal: !path.isAbsolute(filepath)
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function contains (rangeContainer, rangeContained) {
|
|
4
|
+
if (rangeContainer.start > rangeContained.start) {
|
|
5
|
+
return false
|
|
6
|
+
}
|
|
7
|
+
return rangeContainer.end >= rangeContained.end
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function intersects (rangeA, rangeB) {
|
|
11
|
+
return rangeB.start < rangeA.end && rangeB.end > rangeA.start
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function remove (range, rangeToRemove) {
|
|
15
|
+
if (!intersects(range, rangeToRemove)) {
|
|
16
|
+
return [range]
|
|
17
|
+
} else if (contains(rangeToRemove, range)) {
|
|
18
|
+
return []
|
|
19
|
+
} else {
|
|
20
|
+
const result = []
|
|
21
|
+
if (rangeToRemove.start > range.start) {
|
|
22
|
+
const offset = rangeToRemove.start - range.start
|
|
23
|
+
result.push({ start: range.start, end: range.start + offset })
|
|
24
|
+
}
|
|
25
|
+
if (rangeToRemove.end < range.end) {
|
|
26
|
+
const offset = range.end - rangeToRemove.end
|
|
27
|
+
result.push({ start: rangeToRemove.end, end: rangeToRemove.end + offset })
|
|
28
|
+
}
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
contains,
|
|
35
|
+
intersects,
|
|
36
|
+
remove
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const iastLog = require('../../../iast-log')
|
|
4
|
+
|
|
5
|
+
const COMMAND_PATTERN = '^(?:\\s*(?:sudo|doas)\\s+)?\\b\\S+\\b(.*)'
|
|
6
|
+
|
|
7
|
+
class CommandSensitiveAnalyzer {
|
|
8
|
+
constructor () {
|
|
9
|
+
this._pattern = new RegExp(COMMAND_PATTERN, 'gmi')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
extractSensitiveRanges (evidence) {
|
|
13
|
+
try {
|
|
14
|
+
this._pattern.lastIndex = 0
|
|
15
|
+
|
|
16
|
+
const regexResult = this._pattern.exec(evidence.value)
|
|
17
|
+
if (regexResult && regexResult.length > 1) {
|
|
18
|
+
const start = regexResult.index + (regexResult[0].length - regexResult[1].length)
|
|
19
|
+
const end = start + regexResult[1].length
|
|
20
|
+
return [{ start, end }]
|
|
21
|
+
}
|
|
22
|
+
} catch (e) {
|
|
23
|
+
iastLog.debug(e)
|
|
24
|
+
}
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = CommandSensitiveAnalyzer
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const iastLog = require('../../../iast-log')
|
|
4
|
+
|
|
5
|
+
const LDAP_PATTERN = '\\(.*?(?:~=|=|<=|>=)(?<LITERAL>[^)]+)\\)'
|
|
6
|
+
|
|
7
|
+
class LdapSensitiveAnalyzer {
|
|
8
|
+
constructor () {
|
|
9
|
+
this._pattern = new RegExp(LDAP_PATTERN, 'gmi')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
extractSensitiveRanges (evidence) {
|
|
13
|
+
try {
|
|
14
|
+
this._pattern.lastIndex = 0
|
|
15
|
+
const tokens = []
|
|
16
|
+
|
|
17
|
+
let regexResult = this._pattern.exec(evidence.value)
|
|
18
|
+
while (regexResult != null) {
|
|
19
|
+
if (!regexResult.groups.LITERAL) continue
|
|
20
|
+
// Computing indices manually since NodeJs 12 does not support d flag on regular expressions
|
|
21
|
+
// TODO Get indices from group by adding d flag in regular expression
|
|
22
|
+
const start = regexResult.index + (regexResult[0].length - regexResult.groups.LITERAL.length - 1)
|
|
23
|
+
const end = start + regexResult.groups.LITERAL.length
|
|
24
|
+
tokens.push({ start, end })
|
|
25
|
+
regexResult = this._pattern.exec(evidence.value)
|
|
26
|
+
}
|
|
27
|
+
return tokens
|
|
28
|
+
} catch (e) {
|
|
29
|
+
iastLog.debug(e)
|
|
30
|
+
}
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = LdapSensitiveAnalyzer
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const iastLog = require('../../../iast-log')
|
|
4
|
+
|
|
5
|
+
const STRING_LITERAL = '\'(?:\'\'|[^\'])*\''
|
|
6
|
+
const POSTGRESQL_ESCAPED_LITERAL = '\\$([^$]*)\\$.*?\\$\\1\\$'
|
|
7
|
+
const MYSQL_STRING_LITERAL = '"(?:\\\\"|[^"])*"|\'(?:\\\\\'|[^\'])*\''
|
|
8
|
+
const LINE_COMMENT = '--.*$'
|
|
9
|
+
const BLOCK_COMMENT = '/\\*[\\s\\S]*\\*/'
|
|
10
|
+
const EXPONENT = '(?:E[-+]?\\d+[fd]?)?'
|
|
11
|
+
const INTEGER_NUMBER = '(?<!\\w)\\d+'
|
|
12
|
+
const DECIMAL_NUMBER = '\\d*\\.\\d+'
|
|
13
|
+
const HEX_NUMBER = 'x\'[0-9a-f]+\'|0x[0-9a-f]+'
|
|
14
|
+
const BIN_NUMBER = 'b\'[0-9a-f]+\'|0b[0-9a-f]+'
|
|
15
|
+
const NUMERIC_LITERAL =
|
|
16
|
+
`[-+]?(?:${
|
|
17
|
+
[
|
|
18
|
+
HEX_NUMBER,
|
|
19
|
+
BIN_NUMBER,
|
|
20
|
+
DECIMAL_NUMBER + EXPONENT,
|
|
21
|
+
INTEGER_NUMBER + EXPONENT
|
|
22
|
+
].join('|')
|
|
23
|
+
})`
|
|
24
|
+
|
|
25
|
+
class SqlSensitiveAnalyzer {
|
|
26
|
+
constructor () {
|
|
27
|
+
this._patterns = {
|
|
28
|
+
MYSQL: new RegExp(
|
|
29
|
+
[
|
|
30
|
+
NUMERIC_LITERAL,
|
|
31
|
+
MYSQL_STRING_LITERAL,
|
|
32
|
+
LINE_COMMENT,
|
|
33
|
+
BLOCK_COMMENT
|
|
34
|
+
].join('|'),
|
|
35
|
+
'gmi'
|
|
36
|
+
),
|
|
37
|
+
POSTGRES: new RegExp(
|
|
38
|
+
[
|
|
39
|
+
NUMERIC_LITERAL,
|
|
40
|
+
POSTGRESQL_ESCAPED_LITERAL,
|
|
41
|
+
STRING_LITERAL,
|
|
42
|
+
LINE_COMMENT,
|
|
43
|
+
BLOCK_COMMENT
|
|
44
|
+
].join('|'),
|
|
45
|
+
'gmi'
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
extractSensitiveRanges (evidence) {
|
|
51
|
+
try {
|
|
52
|
+
const pattern = this._patterns[evidence.dialect]
|
|
53
|
+
pattern.lastIndex = 0
|
|
54
|
+
const tokens = []
|
|
55
|
+
|
|
56
|
+
let regexResult = pattern.exec(evidence.value)
|
|
57
|
+
while (regexResult != null) {
|
|
58
|
+
let start = regexResult.index
|
|
59
|
+
let end = regexResult.index + regexResult[0].length
|
|
60
|
+
const startChar = evidence.value.charAt(start)
|
|
61
|
+
if (startChar === '\'' || startChar === '"') {
|
|
62
|
+
start++
|
|
63
|
+
end--
|
|
64
|
+
} else if (end > start + 1) {
|
|
65
|
+
const nextChar = evidence.value.charAt(start + 1)
|
|
66
|
+
if (startChar === '/' && nextChar === '*') {
|
|
67
|
+
start += 2
|
|
68
|
+
end -= 2
|
|
69
|
+
} else if (startChar === '-' && startChar === nextChar) {
|
|
70
|
+
start += 2
|
|
71
|
+
} else if (startChar.toLowerCase() === 'q' && nextChar === '\'') {
|
|
72
|
+
start += 3
|
|
73
|
+
end -= 2
|
|
74
|
+
} else if (startChar === '$') {
|
|
75
|
+
const match = regexResult[0]
|
|
76
|
+
const size = match.indexOf('$', 1) + 1
|
|
77
|
+
if (size > 1) {
|
|
78
|
+
start += size
|
|
79
|
+
end -= size
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
tokens.push({ start, end })
|
|
85
|
+
regexResult = pattern.exec(evidence.value)
|
|
86
|
+
}
|
|
87
|
+
return tokens
|
|
88
|
+
} catch (e) {
|
|
89
|
+
iastLog.debug(e)
|
|
90
|
+
}
|
|
91
|
+
return []
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = SqlSensitiveAnalyzer
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const vulnerabilities = require('../../vulnerabilities')
|
|
4
|
+
|
|
5
|
+
const { contains, intersects, remove } = require('./range-utils')
|
|
6
|
+
|
|
7
|
+
const CommandSensitiveAnalyzer = require('./sensitive-analyzers/command-sensitive-analyzer')
|
|
8
|
+
const LdapSensitiveAnalyzer = require('./sensitive-analyzers/ldap-sensitive-analyzer')
|
|
9
|
+
const SqlSensitiveAnalyzer = require('./sensitive-analyzers/sql-sensitive-analyzer')
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line max-len
|
|
12
|
+
const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)'
|
|
13
|
+
// eslint-disable-next-line max-len
|
|
14
|
+
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
|
|
15
|
+
|
|
16
|
+
class SensitiveHandler {
|
|
17
|
+
constructor () {
|
|
18
|
+
this._namePattern = new RegExp(DEFAULT_IAST_REDACTION_NAME_PATTERN, 'gmi')
|
|
19
|
+
this._valuePattern = new RegExp(DEFAULT_IAST_REDACTION_VALUE_PATTERN, 'gmi')
|
|
20
|
+
|
|
21
|
+
this._sensitiveAnalyzers = new Map()
|
|
22
|
+
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
|
|
23
|
+
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
|
|
24
|
+
this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
isSensibleName (name) {
|
|
28
|
+
this._namePattern.lastIndex = 0
|
|
29
|
+
return this._namePattern.test(name)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isSensibleValue (value) {
|
|
33
|
+
this._valuePattern.lastIndex = 0
|
|
34
|
+
return this._valuePattern.test(value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isSensibleSource (source) {
|
|
38
|
+
return source != null && (this.isSensibleName(source.name) || this.isSensibleValue(source.value))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
scrubEvidence (vulnerabilityType, evidence, sourcesIndexes, sources) {
|
|
42
|
+
const sensitiveAnalyzer = this._sensitiveAnalyzers.get(vulnerabilityType)
|
|
43
|
+
if (sensitiveAnalyzer) {
|
|
44
|
+
const sensitiveRanges = sensitiveAnalyzer.extractSensitiveRanges(evidence)
|
|
45
|
+
return this.toRedactedJson(evidence, sensitiveRanges, sourcesIndexes, sources)
|
|
46
|
+
}
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
toRedactedJson (evidence, sensitive, sourcesIndexes, sources) {
|
|
51
|
+
const valueParts = []
|
|
52
|
+
const redactedSources = []
|
|
53
|
+
|
|
54
|
+
const { value, ranges } = evidence
|
|
55
|
+
|
|
56
|
+
let start = 0
|
|
57
|
+
let nextTaintedIndex = 0
|
|
58
|
+
let sourceIndex
|
|
59
|
+
|
|
60
|
+
let nextTainted = ranges.shift()
|
|
61
|
+
let nextSensitive = sensitive.shift()
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < value.length; i++) {
|
|
64
|
+
if (nextTainted != null && nextTainted.start === i) {
|
|
65
|
+
this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
|
|
66
|
+
|
|
67
|
+
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
68
|
+
|
|
69
|
+
while (nextSensitive != null && contains(nextTainted, nextSensitive)) {
|
|
70
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
71
|
+
nextSensitive = sensitive.shift()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (nextSensitive != null && intersects(nextSensitive, nextTainted)) {
|
|
75
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
76
|
+
|
|
77
|
+
const entries = remove(nextSensitive, nextTainted)
|
|
78
|
+
nextSensitive = entries.length > 0 ? entries[0] : null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.isSensibleSource(sources[sourceIndex]) && redactedSources.push(sourceIndex)
|
|
82
|
+
|
|
83
|
+
if (redactedSources.indexOf(sourceIndex) > -1) {
|
|
84
|
+
this.writeRedactedValuePart(valueParts, sourceIndex)
|
|
85
|
+
} else {
|
|
86
|
+
const substringEnd = Math.min(nextTainted.end, value.length)
|
|
87
|
+
this.writeValuePart(valueParts, value.substring(nextTainted.start, substringEnd), sourceIndex)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
start = i + (nextTainted.end - nextTainted.start)
|
|
91
|
+
i = start - 1
|
|
92
|
+
nextTainted = ranges.shift()
|
|
93
|
+
nextTaintedIndex++
|
|
94
|
+
sourceIndex = null
|
|
95
|
+
} else if (nextSensitive != null && nextSensitive.start === i) {
|
|
96
|
+
this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
|
|
97
|
+
if (nextTainted != null && intersects(nextSensitive, nextTainted)) {
|
|
98
|
+
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
99
|
+
sourceIndex != null && redactedSources.push(sourceIndex)
|
|
100
|
+
|
|
101
|
+
for (const entry of remove(nextSensitive, nextTainted)) {
|
|
102
|
+
if (entry.start === i) {
|
|
103
|
+
nextSensitive = entry
|
|
104
|
+
} else {
|
|
105
|
+
sensitive.push(entry)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.writeRedactedValuePart(valueParts)
|
|
111
|
+
|
|
112
|
+
start = i + (nextSensitive.end - nextSensitive.start)
|
|
113
|
+
i = start - 1
|
|
114
|
+
nextSensitive = sensitive.shift()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (start < value.length) {
|
|
119
|
+
this.writeValuePart(valueParts, value.substring(start))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { redactedValueParts: valueParts, redactedSources }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
writeValuePart (valueParts, value, source) {
|
|
126
|
+
if (value.length > 0) {
|
|
127
|
+
if (source != null) {
|
|
128
|
+
valueParts.push({ value, source })
|
|
129
|
+
} else {
|
|
130
|
+
valueParts.push({ value })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
writeRedactedValuePart (valueParts, source) {
|
|
136
|
+
if (source != null) {
|
|
137
|
+
valueParts.push({ redacted: true, source })
|
|
138
|
+
} else {
|
|
139
|
+
valueParts.push({ redacted: true })
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = new SensitiveHandler()
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const sensitiveHandler = require('./evidence-redaction/sensitive-handler')
|
|
2
|
+
|
|
3
|
+
class VulnerabilityFormatter {
|
|
4
|
+
constructor () {
|
|
5
|
+
this._redactVulnearbilities = true
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
setRedactVulnerabilities (shouldRedactVulnerabilities) {
|
|
9
|
+
this._redactVulnearbilities = shouldRedactVulnerabilities
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
extractSourcesFromVulnerability (vulnerability) {
|
|
13
|
+
if (!vulnerability.evidence.ranges) {
|
|
14
|
+
return []
|
|
15
|
+
}
|
|
16
|
+
return vulnerability.evidence.ranges.map(range => (
|
|
17
|
+
{
|
|
18
|
+
origin: range.iinfo.type,
|
|
19
|
+
name: range.iinfo.parameterName,
|
|
20
|
+
value: range.iinfo.parameterValue
|
|
21
|
+
}
|
|
22
|
+
))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getRedactedValueParts (type, evidence, sourcesIndexes, sources) {
|
|
26
|
+
const scrubbingResult = sensitiveHandler.scrubEvidence(type, evidence, sourcesIndexes, sources)
|
|
27
|
+
if (scrubbingResult) {
|
|
28
|
+
const { redactedValueParts, redactedSources } = scrubbingResult
|
|
29
|
+
redactedSources.forEach(i => {
|
|
30
|
+
delete sources[i].value
|
|
31
|
+
sources[i].redacted = true
|
|
32
|
+
})
|
|
33
|
+
return { valueParts: redactedValueParts }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return this.getUnredactedValueParts(evidence, sourcesIndexes)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getUnredactedValueParts (evidence, sourcesIndexes) {
|
|
40
|
+
const valueParts = []
|
|
41
|
+
let fromIndex = 0
|
|
42
|
+
evidence.ranges.forEach((range, rangeIndex) => {
|
|
43
|
+
if (fromIndex < range.start) {
|
|
44
|
+
valueParts.push({ value: evidence.value.substring(fromIndex, range.start) })
|
|
45
|
+
}
|
|
46
|
+
valueParts.push({ value: evidence.value.substring(range.start, range.end), source: sourcesIndexes[rangeIndex] })
|
|
47
|
+
fromIndex = range.end
|
|
48
|
+
})
|
|
49
|
+
if (fromIndex < evidence.value.length) {
|
|
50
|
+
valueParts.push({ value: evidence.value.substring(fromIndex) })
|
|
51
|
+
}
|
|
52
|
+
return { valueParts }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
formatEvidence (type, evidence, sourcesIndexes, sources) {
|
|
56
|
+
if (!evidence.ranges) {
|
|
57
|
+
return { value: evidence.value }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return this._redactVulnearbilities
|
|
61
|
+
? this.getRedactedValueParts(type, evidence, sourcesIndexes, sources)
|
|
62
|
+
: this.getUnredactedValueParts(evidence, sourcesIndexes)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
formatVulnerability (vulnerability, sourcesIndexes, sources) {
|
|
66
|
+
const formattedVulnerability = {
|
|
67
|
+
type: vulnerability.type,
|
|
68
|
+
hash: vulnerability.hash,
|
|
69
|
+
evidence: this.formatEvidence(vulnerability.type, vulnerability.evidence, sourcesIndexes, sources),
|
|
70
|
+
location: {
|
|
71
|
+
spanId: vulnerability.location.spanId
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (vulnerability.location.path) {
|
|
75
|
+
formattedVulnerability.location.path = vulnerability.location.path
|
|
76
|
+
}
|
|
77
|
+
if (vulnerability.location.line) {
|
|
78
|
+
formattedVulnerability.location.line = vulnerability.location.line
|
|
79
|
+
}
|
|
80
|
+
return formattedVulnerability
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
toJson (vulnerabilitiesToFormat) {
|
|
84
|
+
const sources = []
|
|
85
|
+
|
|
86
|
+
const vulnerabilities = vulnerabilitiesToFormat.map(vulnerability => {
|
|
87
|
+
const vulnerabilitySources = this.extractSourcesFromVulnerability(vulnerability)
|
|
88
|
+
const sourcesIndexes = []
|
|
89
|
+
vulnerabilitySources.forEach((source) => {
|
|
90
|
+
let sourceIndex = sources.findIndex(
|
|
91
|
+
existingSource =>
|
|
92
|
+
existingSource.origin === source.origin &&
|
|
93
|
+
existingSource.name === source.name &&
|
|
94
|
+
existingSource.value === source.value
|
|
95
|
+
)
|
|
96
|
+
if (sourceIndex === -1) {
|
|
97
|
+
sourceIndex = sources.length
|
|
98
|
+
sources.push(source)
|
|
99
|
+
}
|
|
100
|
+
sourcesIndexes.push(sourceIndex)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
return this.formatVulnerability(vulnerability, sourcesIndexes, sources)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
sources,
|
|
108
|
+
vulnerabilities
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = new VulnerabilityFormatter()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { MANUAL_KEEP } = require('../../../../../ext/tags')
|
|
2
2
|
const LRU = require('lru-cache')
|
|
3
|
+
const vulnerabilitiesFormatter = require('./vulnerabilities-formatter')
|
|
3
4
|
const VULNERABILITIES_KEY = 'vulnerabilities'
|
|
4
5
|
const IAST_JSON_TAG_KEY = '_dd.iast.json'
|
|
5
6
|
const VULNERABILITY_HASHES_MAX_SIZE = 1000
|
|
@@ -60,57 +61,6 @@ function isValidVulnerability (vulnerability) {
|
|
|
60
61
|
vulnerability.location && vulnerability.location.spanId
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
function formatEvidence (evidence, sourcesIndexes) {
|
|
64
|
-
if (!evidence.ranges) {
|
|
65
|
-
return { value: evidence.value }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const valueParts = []
|
|
69
|
-
let fromIndex = 0
|
|
70
|
-
evidence.ranges.forEach((range, rangeIndex) => {
|
|
71
|
-
if (fromIndex < range.start) {
|
|
72
|
-
valueParts.push({ value: evidence.value.substring(fromIndex, range.start) })
|
|
73
|
-
}
|
|
74
|
-
valueParts.push({ value: evidence.value.substring(range.start, range.end), source: sourcesIndexes[rangeIndex] })
|
|
75
|
-
fromIndex = range.end
|
|
76
|
-
})
|
|
77
|
-
if (fromIndex < evidence.value.length) {
|
|
78
|
-
valueParts.push({ value: evidence.value.substring(fromIndex) })
|
|
79
|
-
}
|
|
80
|
-
return { valueParts }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function extractSourcesFromVulnerability (vulnerability) {
|
|
84
|
-
if (!vulnerability.evidence.ranges) {
|
|
85
|
-
return []
|
|
86
|
-
}
|
|
87
|
-
return vulnerability.evidence.ranges.map(range => (
|
|
88
|
-
{
|
|
89
|
-
origin: range.iinfo.type,
|
|
90
|
-
name: range.iinfo.parameterName,
|
|
91
|
-
value: range.iinfo.parameterValue
|
|
92
|
-
}
|
|
93
|
-
))
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function jsonVulnerabilityFromVulnerability (vulnerability, sourcesIndexes) {
|
|
97
|
-
const jsonVulnerability = {
|
|
98
|
-
type: vulnerability.type,
|
|
99
|
-
hash: vulnerability.hash,
|
|
100
|
-
evidence: formatEvidence(vulnerability.evidence, sourcesIndexes),
|
|
101
|
-
location: {
|
|
102
|
-
spanId: vulnerability.location.spanId
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (vulnerability.location.path) {
|
|
106
|
-
jsonVulnerability.location.path = vulnerability.location.path
|
|
107
|
-
}
|
|
108
|
-
if (vulnerability.location.line) {
|
|
109
|
-
jsonVulnerability.location.line = vulnerability.location.line
|
|
110
|
-
}
|
|
111
|
-
return jsonVulnerability
|
|
112
|
-
}
|
|
113
|
-
|
|
114
64
|
function sendVulnerabilities (vulnerabilities, rootSpan) {
|
|
115
65
|
if (vulnerabilities && vulnerabilities.length) {
|
|
116
66
|
let span = rootSpan
|
|
@@ -124,31 +74,8 @@ function sendVulnerabilities (vulnerabilities, rootSpan) {
|
|
|
124
74
|
}
|
|
125
75
|
|
|
126
76
|
if (span && span.addTags) {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
vulnerabilities: []
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
deduplicateVulnerabilities(vulnerabilities).forEach((vulnerability) => {
|
|
133
|
-
if (isValidVulnerability(vulnerability)) {
|
|
134
|
-
const sourcesIndexes = []
|
|
135
|
-
const vulnerabilitySources = extractSourcesFromVulnerability(vulnerability)
|
|
136
|
-
vulnerabilitySources.forEach((source) => {
|
|
137
|
-
let sourceIndex = jsonToSend.sources.findIndex(
|
|
138
|
-
existingSource =>
|
|
139
|
-
existingSource.origin === source.origin &&
|
|
140
|
-
existingSource.name === source.name &&
|
|
141
|
-
existingSource.value === source.value
|
|
142
|
-
)
|
|
143
|
-
if (sourceIndex === -1) {
|
|
144
|
-
sourceIndex = jsonToSend.sources.length
|
|
145
|
-
jsonToSend.sources.push(source)
|
|
146
|
-
}
|
|
147
|
-
sourcesIndexes.push(sourceIndex)
|
|
148
|
-
})
|
|
149
|
-
jsonToSend.vulnerabilities.push(jsonVulnerabilityFromVulnerability(vulnerability, sourcesIndexes))
|
|
150
|
-
}
|
|
151
|
-
})
|
|
77
|
+
const validAndDedupVulnerabilities = deduplicateVulnerabilities(vulnerabilities).filter(isValidVulnerability)
|
|
78
|
+
const jsonToSend = vulnerabilitiesFormatter.toJson(validAndDedupVulnerabilities)
|
|
152
79
|
|
|
153
80
|
if (jsonToSend.vulnerabilities.length > 0) {
|
|
154
81
|
const tags = {}
|
|
@@ -194,6 +121,7 @@ function deduplicateVulnerabilities (vulnerabilities) {
|
|
|
194
121
|
|
|
195
122
|
function start (config, _tracer) {
|
|
196
123
|
deduplicationEnabled = config.iast.deduplicationEnabled
|
|
124
|
+
vulnerabilitiesFormatter.setRedactVulnerabilities(config.iast.redactionEnabled)
|
|
197
125
|
if (deduplicationEnabled) {
|
|
198
126
|
startClearCacheTimer()
|
|
199
127
|
}
|
|
@@ -9,6 +9,7 @@ const coalesce = require('koalas')
|
|
|
9
9
|
const tagger = require('./tagger')
|
|
10
10
|
const { isTrue, isFalse } = require('./util')
|
|
11
11
|
const uuid = require('crypto-randomuuid')
|
|
12
|
+
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
|
|
12
13
|
|
|
13
14
|
const fromEntries = Object.fromEntries || (entries =>
|
|
14
15
|
entries.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {}))
|
|
@@ -397,11 +398,22 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
397
398
|
true
|
|
398
399
|
)
|
|
399
400
|
|
|
401
|
+
const DD_IAST_REDACTION_ENABLED = coalesce(
|
|
402
|
+
iastOptions && iastOptions.redactionEnabled,
|
|
403
|
+
!isFalse(process.env.DD_IAST_REDACTION_ENABLED),
|
|
404
|
+
true
|
|
405
|
+
)
|
|
406
|
+
|
|
400
407
|
const DD_CIVISIBILITY_GIT_UPLOAD_ENABLED = coalesce(
|
|
401
408
|
process.env.DD_CIVISIBILITY_GIT_UPLOAD_ENABLED,
|
|
402
409
|
true
|
|
403
410
|
)
|
|
404
411
|
|
|
412
|
+
const DD_TRACE_GIT_METADATA_ENABLED = coalesce(
|
|
413
|
+
process.env.DD_TRACE_GIT_METADATA_ENABLED,
|
|
414
|
+
true
|
|
415
|
+
)
|
|
416
|
+
|
|
405
417
|
const ingestion = options.ingestion || {}
|
|
406
418
|
const dogstatsd = coalesce(options.dogstatsd, {})
|
|
407
419
|
const sampler = {
|
|
@@ -506,7 +518,8 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
506
518
|
requestSampling: DD_IAST_REQUEST_SAMPLING,
|
|
507
519
|
maxConcurrentRequests: DD_IAST_MAX_CONCURRENT_REQUESTS,
|
|
508
520
|
maxContextOperations: DD_IAST_MAX_CONTEXT_OPERATIONS,
|
|
509
|
-
deduplicationEnabled: DD_IAST_DEDUPLICATION_ENABLED
|
|
521
|
+
deduplicationEnabled: DD_IAST_DEDUPLICATION_ENABLED,
|
|
522
|
+
redactionEnabled: DD_IAST_REDACTION_ENABLED
|
|
510
523
|
}
|
|
511
524
|
|
|
512
525
|
this.isCiVisibility = isTrue(DD_IS_CIVISIBILITY)
|
|
@@ -515,6 +528,19 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
515
528
|
this.isGitUploadEnabled = this.isCiVisibility &&
|
|
516
529
|
(this.isIntelligentTestRunnerEnabled && !isFalse(DD_CIVISIBILITY_GIT_UPLOAD_ENABLED))
|
|
517
530
|
|
|
531
|
+
this.gitMetadataEnabled = isTrue(DD_TRACE_GIT_METADATA_ENABLED)
|
|
532
|
+
|
|
533
|
+
if (this.gitMetadataEnabled) {
|
|
534
|
+
this.repositoryUrl = coalesce(
|
|
535
|
+
process.env.DD_GIT_REPOSITORY_URL,
|
|
536
|
+
this.tags[GIT_REPOSITORY_URL]
|
|
537
|
+
)
|
|
538
|
+
this.commitSHA = coalesce(
|
|
539
|
+
process.env.DD_GIT_COMMIT_SHA,
|
|
540
|
+
this.tags[GIT_COMMIT_SHA]
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
|
|
518
544
|
this.stats = {
|
|
519
545
|
enabled: isTrue(DD_TRACE_STATS_COMPUTATION_ENABLED)
|
|
520
546
|
}
|
|
@@ -25,5 +25,7 @@ module.exports = {
|
|
|
25
25
|
ERROR_MESSAGE: 'error.message',
|
|
26
26
|
ERROR_STACK: 'error.stack',
|
|
27
27
|
COMPONENT: 'component',
|
|
28
|
-
CLIENT_PORT_KEY: 'network.destination.port'
|
|
28
|
+
CLIENT_PORT_KEY: 'network.destination.port',
|
|
29
|
+
SCI_REPOSITORY_URL: '_dd.git.repository_url',
|
|
30
|
+
SCI_COMMIT_SHA: '_dd.git.commit.sha'
|
|
29
31
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { SCI_COMMIT_SHA, SCI_REPOSITORY_URL } = require('./constants')
|
|
2
|
+
|
|
3
|
+
class GitMetadataTagger {
|
|
4
|
+
constructor (config) {
|
|
5
|
+
this._config = config
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
tagGitMetadata (spanContext) {
|
|
9
|
+
if (this._config.gitMetadataEnabled) {
|
|
10
|
+
// These tags are added only to the local root span
|
|
11
|
+
spanContext._trace.tags[SCI_COMMIT_SHA] = this._config.commitSHA
|
|
12
|
+
spanContext._trace.tags[SCI_REPOSITORY_URL] = this._config.repositoryUrl
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = GitMetadataTagger
|
|
@@ -20,7 +20,9 @@ const {
|
|
|
20
20
|
CI_STAGE_NAME,
|
|
21
21
|
CI_ENV_VARS,
|
|
22
22
|
GIT_COMMIT_COMMITTER_NAME,
|
|
23
|
-
GIT_COMMIT_COMMITTER_EMAIL
|
|
23
|
+
GIT_COMMIT_COMMITTER_EMAIL,
|
|
24
|
+
CI_NODE_LABELS,
|
|
25
|
+
CI_NODE_NAME
|
|
24
26
|
} = require('./tags')
|
|
25
27
|
|
|
26
28
|
// Receives a string with the form 'John Doe <john.doe@gmail.com>'
|
|
@@ -108,7 +110,9 @@ module.exports = {
|
|
|
108
110
|
GIT_COMMIT: JENKINS_GIT_COMMIT,
|
|
109
111
|
GIT_URL: JENKINS_GIT_REPOSITORY_URL,
|
|
110
112
|
GIT_URL_1: JENKINS_GIT_REPOSITORY_URL_1,
|
|
111
|
-
DD_CUSTOM_TRACE_ID
|
|
113
|
+
DD_CUSTOM_TRACE_ID,
|
|
114
|
+
NODE_NAME,
|
|
115
|
+
NODE_LABELS
|
|
112
116
|
} = env
|
|
113
117
|
|
|
114
118
|
tags = {
|
|
@@ -119,7 +123,18 @@ module.exports = {
|
|
|
119
123
|
[GIT_COMMIT_SHA]: JENKINS_GIT_COMMIT,
|
|
120
124
|
[GIT_REPOSITORY_URL]: JENKINS_GIT_REPOSITORY_URL || JENKINS_GIT_REPOSITORY_URL_1,
|
|
121
125
|
[CI_WORKSPACE_PATH]: WORKSPACE,
|
|
122
|
-
[CI_ENV_VARS]: JSON.stringify({ DD_CUSTOM_TRACE_ID })
|
|
126
|
+
[CI_ENV_VARS]: JSON.stringify({ DD_CUSTOM_TRACE_ID }),
|
|
127
|
+
[CI_NODE_NAME]: NODE_NAME
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (NODE_LABELS) {
|
|
131
|
+
let nodeLabels
|
|
132
|
+
try {
|
|
133
|
+
nodeLabels = JSON.stringify(NODE_LABELS.split(' '))
|
|
134
|
+
tags[CI_NODE_LABELS] = nodeLabels
|
|
135
|
+
} catch (e) {
|
|
136
|
+
// ignore errors
|
|
137
|
+
}
|
|
123
138
|
}
|
|
124
139
|
|
|
125
140
|
const isTag = JENKINS_GIT_BRANCH && JENKINS_GIT_BRANCH.includes('tags/')
|
|
@@ -159,7 +174,9 @@ module.exports = {
|
|
|
159
174
|
CI_COMMIT_TIMESTAMP,
|
|
160
175
|
CI_COMMIT_AUTHOR,
|
|
161
176
|
CI_PROJECT_URL: GITLAB_PROJECT_URL,
|
|
162
|
-
CI_JOB_ID: GITLAB_CI_JOB_ID
|
|
177
|
+
CI_JOB_ID: GITLAB_CI_JOB_ID,
|
|
178
|
+
CI_RUNNER_ID,
|
|
179
|
+
CI_RUNNER_TAGS
|
|
163
180
|
} = env
|
|
164
181
|
|
|
165
182
|
const { name, email } = parseEmailAndName(CI_COMMIT_AUTHOR)
|
|
@@ -186,7 +203,9 @@ module.exports = {
|
|
|
186
203
|
CI_PROJECT_URL: GITLAB_PROJECT_URL,
|
|
187
204
|
CI_PIPELINE_ID: GITLAB_PIPELINE_ID,
|
|
188
205
|
CI_JOB_ID: GITLAB_CI_JOB_ID
|
|
189
|
-
})
|
|
206
|
+
}),
|
|
207
|
+
[CI_NODE_LABELS]: CI_RUNNER_TAGS,
|
|
208
|
+
[CI_NODE_NAME]: CI_RUNNER_ID
|
|
190
209
|
}
|
|
191
210
|
}
|
|
192
211
|
|
|
@@ -448,9 +467,17 @@ module.exports = {
|
|
|
448
467
|
BUILDKITE_BUILD_CHECKOUT_PATH,
|
|
449
468
|
BUILDKITE_BUILD_AUTHOR,
|
|
450
469
|
BUILDKITE_BUILD_AUTHOR_EMAIL,
|
|
451
|
-
BUILDKITE_MESSAGE
|
|
470
|
+
BUILDKITE_MESSAGE,
|
|
471
|
+
BUILDKITE_AGENT_ID
|
|
452
472
|
} = env
|
|
453
473
|
|
|
474
|
+
const extraTags = Object.keys(env).filter(envVar =>
|
|
475
|
+
envVar.startsWith('BUILDKITE_AGENT_META_DATA_')
|
|
476
|
+
).map((metadataKey) => {
|
|
477
|
+
const key = metadataKey.replace('BUILDKITE_AGENT_META_DATA_', '').toLowerCase()
|
|
478
|
+
return `${key}:${env[metadataKey]}`
|
|
479
|
+
})
|
|
480
|
+
|
|
454
481
|
tags = {
|
|
455
482
|
[CI_PROVIDER_NAME]: 'buildkite',
|
|
456
483
|
[CI_PIPELINE_ID]: BUILDKITE_BUILD_ID,
|
|
@@ -469,7 +496,9 @@ module.exports = {
|
|
|
469
496
|
[CI_ENV_VARS]: JSON.stringify({
|
|
470
497
|
BUILDKITE_BUILD_ID,
|
|
471
498
|
BUILDKITE_JOB_ID
|
|
472
|
-
})
|
|
499
|
+
}),
|
|
500
|
+
[CI_NODE_NAME]: BUILDKITE_AGENT_ID,
|
|
501
|
+
[CI_NODE_LABELS]: JSON.stringify(extraTags)
|
|
473
502
|
}
|
|
474
503
|
}
|
|
475
504
|
|
|
@@ -546,6 +575,32 @@ module.exports = {
|
|
|
546
575
|
}
|
|
547
576
|
}
|
|
548
577
|
|
|
578
|
+
if (env.CF_BUILD_ID) {
|
|
579
|
+
const {
|
|
580
|
+
CF_BUILD_ID,
|
|
581
|
+
CF_PIPELINE_NAME,
|
|
582
|
+
CF_BUILD_URL,
|
|
583
|
+
CF_STEP_NAME,
|
|
584
|
+
CF_BRANCH
|
|
585
|
+
} = env
|
|
586
|
+
tags = {
|
|
587
|
+
[CI_PROVIDER_NAME]: 'codefresh',
|
|
588
|
+
[CI_PIPELINE_ID]: CF_BUILD_ID,
|
|
589
|
+
[CI_PIPELINE_NAME]: CF_PIPELINE_NAME,
|
|
590
|
+
[CI_PIPELINE_URL]: CF_BUILD_URL,
|
|
591
|
+
[CI_JOB_NAME]: CF_STEP_NAME,
|
|
592
|
+
[CI_ENV_VARS]: JSON.stringify({
|
|
593
|
+
CF_BUILD_ID
|
|
594
|
+
})
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const isTag = CF_BRANCH && CF_BRANCH.includes('tags/')
|
|
598
|
+
const refKey = isTag ? GIT_TAG : GIT_BRANCH
|
|
599
|
+
const ref = normalizeRef(CF_BRANCH)
|
|
600
|
+
|
|
601
|
+
tags[refKey] = ref
|
|
602
|
+
}
|
|
603
|
+
|
|
549
604
|
normalizeTag(tags, CI_WORKSPACE_PATH, resolveTilde)
|
|
550
605
|
normalizeTag(tags, GIT_REPOSITORY_URL, filterSensitiveInfoFromRepository)
|
|
551
606
|
normalizeTag(tags, GIT_BRANCH, normalizeRef)
|
|
@@ -19,6 +19,8 @@ const CI_WORKSPACE_PATH = 'ci.workspace_path'
|
|
|
19
19
|
const CI_JOB_URL = 'ci.job.url'
|
|
20
20
|
const CI_JOB_NAME = 'ci.job.name'
|
|
21
21
|
const CI_STAGE_NAME = 'ci.stage.name'
|
|
22
|
+
const CI_NODE_NAME = 'ci.node.name'
|
|
23
|
+
const CI_NODE_LABELS = 'ci.node.labels'
|
|
22
24
|
|
|
23
25
|
const CI_ENV_VARS = '_dd.ci.env_vars'
|
|
24
26
|
|
|
@@ -43,5 +45,7 @@ module.exports = {
|
|
|
43
45
|
CI_JOB_URL,
|
|
44
46
|
CI_JOB_NAME,
|
|
45
47
|
CI_STAGE_NAME,
|
|
46
|
-
CI_ENV_VARS
|
|
48
|
+
CI_ENV_VARS,
|
|
49
|
+
CI_NODE_NAME,
|
|
50
|
+
CI_NODE_LABELS
|
|
47
51
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const log = require('./log')
|
|
4
4
|
const format = require('./format')
|
|
5
5
|
const SpanSampler = require('./span_sampler')
|
|
6
|
+
const GitMetadataTagger = require('./git_metadata_tagger')
|
|
6
7
|
|
|
7
8
|
const { SpanStatsProcessor } = require('./span_stats')
|
|
8
9
|
|
|
@@ -18,6 +19,7 @@ class SpanProcessor {
|
|
|
18
19
|
|
|
19
20
|
this._stats = new SpanStatsProcessor(config)
|
|
20
21
|
this._spanSampler = new SpanSampler(config.sampler)
|
|
22
|
+
this._gitMetadataTagger = new GitMetadataTagger(config)
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
process (span) {
|
|
@@ -32,6 +34,7 @@ class SpanProcessor {
|
|
|
32
34
|
if (started.length === finished.length || finished.length >= flushMinSpans) {
|
|
33
35
|
this._prioritySampler.sample(spanContext)
|
|
34
36
|
this._spanSampler.sample(spanContext)
|
|
37
|
+
this._gitMetadataTagger.tagGitMetadata(spanContext)
|
|
35
38
|
|
|
36
39
|
for (const span of started) {
|
|
37
40
|
if (span._duration !== undefined) {
|
|
@@ -7,6 +7,7 @@ const { storage } = require('../../datadog-core')
|
|
|
7
7
|
const { isError } = require('./util')
|
|
8
8
|
const { setStartupLogConfig } = require('./startup-log')
|
|
9
9
|
const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants')
|
|
10
|
+
const { MAJOR } = require('../../../version')
|
|
10
11
|
|
|
11
12
|
const SPAN_TYPE = tags.SPAN_TYPE
|
|
12
13
|
const RESOURCE_NAME = tags.RESOURCE_NAME
|
|
@@ -26,6 +27,10 @@ class DatadogTracer extends Tracer {
|
|
|
26
27
|
childOf: this.scope().active()
|
|
27
28
|
}, options)
|
|
28
29
|
|
|
30
|
+
if (!options.childOf && options.orphanable === false && MAJOR < 4) {
|
|
31
|
+
return fn(null, () => {})
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
const span = this.startSpan(name, options)
|
|
30
35
|
|
|
31
36
|
addTags(span, options)
|
|
@@ -77,6 +82,10 @@ class DatadogTracer extends Tracer {
|
|
|
77
82
|
optionsObj = optionsObj.apply(this, arguments)
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
if (optionsObj && optionsObj.orphanable === false && !tracer.scope().active() && MAJOR < 4) {
|
|
86
|
+
return fn.apply(this, arguments)
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
const lastArgId = arguments.length - 1
|
|
81
90
|
const cb = arguments[lastArgId]
|
|
82
91
|
|