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.
Files changed (27) hide show
  1. package/index.d.ts +16 -0
  2. package/package.json +4 -4
  3. package/packages/datadog-instrumentations/src/jest.js +1 -0
  4. package/packages/datadog-plugin-http/src/client.js +2 -1
  5. package/packages/datadog-plugin-http2/src/client.js +2 -1
  6. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +2 -1
  7. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +2 -1
  8. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +22 -5
  9. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +40 -4
  10. package/packages/dd-trace/src/appsec/iast/analyzers/weak-cipher-analyzer.js +2 -1
  11. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +2 -1
  12. package/packages/dd-trace/src/appsec/iast/path-line.js +2 -1
  13. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/range-utils.js +37 -0
  14. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +29 -0
  15. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +35 -0
  16. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +95 -0
  17. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +144 -0
  18. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +113 -0
  19. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +8 -0
  20. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -76
  21. package/packages/dd-trace/src/config.js +27 -1
  22. package/packages/dd-trace/src/constants.js +3 -1
  23. package/packages/dd-trace/src/git_metadata_tagger.js +17 -0
  24. package/packages/dd-trace/src/plugins/util/ci.js +62 -7
  25. package/packages/dd-trace/src/plugins/util/tags.js +5 -1
  26. package/packages/dd-trace/src/span_processor.js +3 -0
  27. 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-071951e",
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.0",
72
- "@datadog/native-metrics": "^1.6.0",
73
- "@datadog/pprof": "^2.2.0",
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) {
@@ -45,7 +45,8 @@ class HttpClientPlugin extends Plugin {
45
45
  'resource.name': method,
46
46
  'span.type': 'http',
47
47
  'http.method': method,
48
- 'http.url': uri
48
+ 'http.url': uri,
49
+ 'out.host': hostname
49
50
  }
50
51
  })
51
52
 
@@ -52,7 +52,8 @@ class Http2ClientPlugin extends Plugin {
52
52
  'resource.name': method,
53
53
  'span.type': 'http',
54
54
  'http.method': method,
55
- 'http.url': uri
55
+ 'http.url': uri,
56
+ 'out.host': sessionDetails.host
56
57
  }
57
58
  })
58
59
 
@@ -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('COMMAND_INJECTION')
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('LDAP_INJECTION')
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('PATH_TRAVERSAL')
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 = [ path.join('node_modules', 'send') + path.sep ]
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 = false
59
+ let ret = true
48
60
  if (location && location.path) {
49
- ret = this.exclusionList.some(elem => location.path.includes(elem))
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('SQL_INJECTION')
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('WEAK_CIPHER')
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('WEAK_HASH')
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()
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ WEAK_HASH: 'WEAK_HASH',
3
+ WEAK_CIPHER: 'WEAK_CIPHER',
4
+ SQL_INJECTION: 'SQL_INJECTION',
5
+ PATH_TRAVERSAL: 'PATH_TRAVERSAL',
6
+ COMMAND_INJECTION: 'COMMAND_INJECTION',
7
+ LDAP_INJECTION: 'LDAP_INJECTION'
8
+ }
@@ -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 jsonToSend = {
128
- sources: [],
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