dd-trace 2.33.0 → 2.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +8 -1
- package/package.json +4 -3
- package/packages/datadog-instrumentations/src/helpers/register.js +4 -0
- package/packages/datadog-instrumentations/src/jest.js +20 -17
- package/packages/datadog-instrumentations/src/next.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/base.js +3 -0
- package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +4 -2
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +4 -3
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +8 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +7 -1
- package/packages/datadog-plugin-http/src/client.js +2 -1
- package/packages/datadog-plugin-http2/src/client.js +2 -1
- package/packages/datadog-plugin-jest/src/util.js +10 -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/index.js +1 -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 +58 -8
- 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/git_properties.js +32 -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/profiling/constants.js +0 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/proxy.js +4 -0
- package/packages/dd-trace/src/serverless.js +25 -0
- package/packages/dd-trace/src/span_processor.js +3 -0
- package/packages/dd-trace/src/tracer.js +3 -2
- package/version.js +9 -0
|
@@ -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
|
}
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
const os = require('os')
|
|
5
|
+
const uuid = require('crypto-randomuuid')
|
|
5
6
|
const URL = require('url').URL
|
|
6
7
|
const log = require('./log')
|
|
7
8
|
const pkg = require('./pkg')
|
|
8
9
|
const coalesce = require('koalas')
|
|
9
10
|
const tagger = require('./tagger')
|
|
10
11
|
const { isTrue, isFalse } = require('./util')
|
|
11
|
-
const
|
|
12
|
+
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
|
|
13
|
+
const { getGitMetadataFromGitProperties } = require('./git_properties')
|
|
12
14
|
|
|
13
15
|
const fromEntries = Object.fromEntries || (entries =>
|
|
14
16
|
entries.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {}))
|
|
@@ -156,6 +158,8 @@ class Config {
|
|
|
156
158
|
process.env.DD_SERVICE_NAME ||
|
|
157
159
|
this.tags.service ||
|
|
158
160
|
process.env.AWS_LAMBDA_FUNCTION_NAME ||
|
|
161
|
+
process.env.FUNCTION_NAME || // Google Cloud Function Name set by deprecated runtimes
|
|
162
|
+
process.env.K_SERVICE || // Google Cloud Function Name set by newer runtimes
|
|
159
163
|
pkg.name ||
|
|
160
164
|
'node'
|
|
161
165
|
const DD_SERVICE_MAPPING = coalesce(
|
|
@@ -180,9 +184,18 @@ class Config {
|
|
|
180
184
|
process.env.DD_TRACE_STARTUP_LOGS,
|
|
181
185
|
false
|
|
182
186
|
)
|
|
187
|
+
|
|
188
|
+
const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined
|
|
189
|
+
|
|
190
|
+
const isDeprecatedGCPFunction = process.env.FUNCTION_NAME !== undefined && process.env.GCP_PROJECT !== undefined
|
|
191
|
+
const isNewerGCPFunction = process.env.K_SERVICE !== undefined && process.env.FUNCTION_TARGET !== undefined
|
|
192
|
+
const isGCPFunction = isDeprecatedGCPFunction || isNewerGCPFunction
|
|
193
|
+
|
|
194
|
+
const inServerlessEnvironment = inAWSLambda || isGCPFunction
|
|
195
|
+
|
|
183
196
|
const DD_TRACE_TELEMETRY_ENABLED = coalesce(
|
|
184
197
|
process.env.DD_TRACE_TELEMETRY_ENABLED,
|
|
185
|
-
!
|
|
198
|
+
!inServerlessEnvironment
|
|
186
199
|
)
|
|
187
200
|
const DD_TELEMETRY_DEBUG_ENABLED = coalesce(
|
|
188
201
|
process.env.DD_TELEMETRY_DEBUG_ENABLED,
|
|
@@ -266,7 +279,7 @@ class Config {
|
|
|
266
279
|
const DD_TRACE_STATS_COMPUTATION_ENABLED = coalesce(
|
|
267
280
|
options.stats,
|
|
268
281
|
process.env.DD_TRACE_STATS_COMPUTATION_ENABLED,
|
|
269
|
-
|
|
282
|
+
isGCPFunction
|
|
270
283
|
)
|
|
271
284
|
|
|
272
285
|
const DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED = coalesce(
|
|
@@ -333,12 +346,10 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
333
346
|
maybeFile(process.env.DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON)
|
|
334
347
|
)
|
|
335
348
|
|
|
336
|
-
const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined
|
|
337
|
-
|
|
338
349
|
const remoteConfigOptions = options.remoteConfig || {}
|
|
339
350
|
const DD_REMOTE_CONFIGURATION_ENABLED = coalesce(
|
|
340
351
|
process.env.DD_REMOTE_CONFIGURATION_ENABLED && isTrue(process.env.DD_REMOTE_CONFIGURATION_ENABLED),
|
|
341
|
-
!
|
|
352
|
+
!inServerlessEnvironment
|
|
342
353
|
)
|
|
343
354
|
const DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS = coalesce(
|
|
344
355
|
parseInt(remoteConfigOptions.pollInterval),
|
|
@@ -385,11 +396,22 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
385
396
|
true
|
|
386
397
|
)
|
|
387
398
|
|
|
399
|
+
const DD_IAST_REDACTION_ENABLED = coalesce(
|
|
400
|
+
iastOptions && iastOptions.redactionEnabled,
|
|
401
|
+
!isFalse(process.env.DD_IAST_REDACTION_ENABLED),
|
|
402
|
+
true
|
|
403
|
+
)
|
|
404
|
+
|
|
388
405
|
const DD_CIVISIBILITY_GIT_UPLOAD_ENABLED = coalesce(
|
|
389
406
|
process.env.DD_CIVISIBILITY_GIT_UPLOAD_ENABLED,
|
|
390
407
|
true
|
|
391
408
|
)
|
|
392
409
|
|
|
410
|
+
const DD_TRACE_GIT_METADATA_ENABLED = coalesce(
|
|
411
|
+
process.env.DD_TRACE_GIT_METADATA_ENABLED,
|
|
412
|
+
true
|
|
413
|
+
)
|
|
414
|
+
|
|
393
415
|
const ingestion = options.ingestion || {}
|
|
394
416
|
const dogstatsd = coalesce(options.dogstatsd, {})
|
|
395
417
|
const sampler = {
|
|
@@ -421,7 +443,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
421
443
|
})
|
|
422
444
|
}
|
|
423
445
|
|
|
424
|
-
const defaultFlushInterval =
|
|
446
|
+
const defaultFlushInterval = inServerlessEnvironment ? 0 : 2000
|
|
425
447
|
|
|
426
448
|
this.tracing = !isFalse(DD_TRACING_ENABLED)
|
|
427
449
|
this.dbmPropagationMode = DD_DBM_PROPAGATION_MODE
|
|
@@ -494,7 +516,8 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
494
516
|
requestSampling: DD_IAST_REQUEST_SAMPLING,
|
|
495
517
|
maxConcurrentRequests: DD_IAST_MAX_CONCURRENT_REQUESTS,
|
|
496
518
|
maxContextOperations: DD_IAST_MAX_CONTEXT_OPERATIONS,
|
|
497
|
-
deduplicationEnabled: DD_IAST_DEDUPLICATION_ENABLED
|
|
519
|
+
deduplicationEnabled: DD_IAST_DEDUPLICATION_ENABLED,
|
|
520
|
+
redactionEnabled: DD_IAST_REDACTION_ENABLED
|
|
498
521
|
}
|
|
499
522
|
|
|
500
523
|
this.isCiVisibility = isTrue(DD_IS_CIVISIBILITY)
|
|
@@ -503,6 +526,31 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
503
526
|
this.isGitUploadEnabled = this.isCiVisibility &&
|
|
504
527
|
(this.isIntelligentTestRunnerEnabled && !isFalse(DD_CIVISIBILITY_GIT_UPLOAD_ENABLED))
|
|
505
528
|
|
|
529
|
+
this.gitMetadataEnabled = isTrue(DD_TRACE_GIT_METADATA_ENABLED)
|
|
530
|
+
|
|
531
|
+
if (this.gitMetadataEnabled) {
|
|
532
|
+
this.repositoryUrl = coalesce(
|
|
533
|
+
process.env.DD_GIT_REPOSITORY_URL,
|
|
534
|
+
this.tags[GIT_REPOSITORY_URL]
|
|
535
|
+
)
|
|
536
|
+
this.commitSHA = coalesce(
|
|
537
|
+
process.env.DD_GIT_COMMIT_SHA,
|
|
538
|
+
this.tags[GIT_COMMIT_SHA]
|
|
539
|
+
)
|
|
540
|
+
if (!this.repositoryUrl || !this.commitSHA) {
|
|
541
|
+
const DD_GIT_PROPERTIES_FILE = coalesce(
|
|
542
|
+
process.env.DD_GIT_PROPERTIES_FILE,
|
|
543
|
+
`${process.cwd()}/git.properties`
|
|
544
|
+
)
|
|
545
|
+
const gitPropertiesString = maybeFile(DD_GIT_PROPERTIES_FILE)
|
|
546
|
+
if (gitPropertiesString) {
|
|
547
|
+
const { commitSHA, repositoryUrl } = getGitMetadataFromGitProperties(gitPropertiesString)
|
|
548
|
+
this.commitSHA = this.commitSHA || commitSHA
|
|
549
|
+
this.repositoryUrl = this.repositoryUrl || repositoryUrl
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
506
554
|
this.stats = {
|
|
507
555
|
enabled: isTrue(DD_TRACE_STATS_COMPUTATION_ENABLED)
|
|
508
556
|
}
|
|
@@ -510,6 +558,8 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
510
558
|
this.traceId128BitGenerationEnabled = isTrue(DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)
|
|
511
559
|
this.traceId128BitLoggingEnabled = isTrue(DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED)
|
|
512
560
|
|
|
561
|
+
this.isGCPFunction = isGCPFunction
|
|
562
|
+
|
|
513
563
|
tagger.add(this.tags, {
|
|
514
564
|
service: this.service,
|
|
515
565
|
env: this.env,
|
|
@@ -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
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const commitSHARegex = /git\.commit\.sha=([a-f\d]{40})/
|
|
2
|
+
const repositoryUrlRegex = /git\.repository_url=([\w\d:@/.-]+)/
|
|
3
|
+
|
|
4
|
+
function getGitMetadataFromGitProperties (gitPropertiesString) {
|
|
5
|
+
if (!gitPropertiesString) {
|
|
6
|
+
return {}
|
|
7
|
+
}
|
|
8
|
+
const commitSHAMatch = gitPropertiesString.match(commitSHARegex)
|
|
9
|
+
const repositoryUrlMatch = gitPropertiesString.match(repositoryUrlRegex)
|
|
10
|
+
|
|
11
|
+
const repositoryUrl = repositoryUrlMatch ? repositoryUrlMatch[1] : undefined
|
|
12
|
+
let parsedUrl = repositoryUrl
|
|
13
|
+
|
|
14
|
+
if (repositoryUrl) {
|
|
15
|
+
try {
|
|
16
|
+
// repository URLs can contain username and password, so we want to filter those out
|
|
17
|
+
parsedUrl = new URL(repositoryUrl)
|
|
18
|
+
if (parsedUrl.password) {
|
|
19
|
+
parsedUrl = `${parsedUrl.origin}${parsedUrl.pathname}`
|
|
20
|
+
}
|
|
21
|
+
} catch (e) {
|
|
22
|
+
// if protocol isn't https, no password will be used
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
commitSHA: commitSHAMatch ? commitSHAMatch[1] : undefined,
|
|
28
|
+
repositoryUrl: parsedUrl
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { getGitMetadataFromGitProperties }
|