dd-trace 2.36.0 → 2.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATING.md +158 -0
- package/README.md +18 -11
- package/index.d.ts +7 -0
- package/package.json +4 -4
- package/packages/datadog-instrumentations/src/cookie.js +21 -0
- package/packages/datadog-instrumentations/src/fetch.js +48 -0
- package/packages/datadog-instrumentations/src/grpc/server.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -3
- package/packages/datadog-instrumentations/src/next.js +2 -2
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +18 -0
- package/packages/datadog-plugin-fetch/src/index.js +36 -0
- package/packages/datadog-plugin-http/src/client.js +24 -8
- package/packages/datadog-plugin-mysql/src/index.js +2 -11
- package/packages/datadog-plugin-tedious/src/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +52 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +3 -22
- package/packages/dd-trace/src/appsec/iast/analyzers/no-httponly-cookie-analyzer.js +12 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/no-samesite-cookie-analyzer.js +12 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +7 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +48 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/index.js +9 -2
- package/packages/dd-trace/src/appsec/iast/path-line.js +13 -0
- package/packages/dd-trace/src/appsec/iast/tags.js +6 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +2 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +13 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/origin-types.js +5 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +24 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +3 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +7 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -3
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +5 -2
- package/packages/dd-trace/src/config.js +13 -0
- package/packages/dd-trace/src/external-logger/src/index.js +126 -0
- package/packages/dd-trace/src/external-logger/test/index.spec.js +147 -0
- package/packages/dd-trace/src/lambda/handler.js +3 -15
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +1 -1
- package/packages/dd-trace/src/plugin_manager.js +10 -7
- package/packages/dd-trace/src/plugins/database.js +7 -3
- package/packages/dd-trace/src/plugins/plugin.js +3 -1
- package/packages/dd-trace/src/plugins/util/exec.js +2 -2
- package/packages/dd-trace/src/plugins/util/git.js +51 -24
- package/packages/dd-trace/src/profiling/config.js +2 -0
- package/packages/dd-trace/src/profiling/profiler.js +13 -4
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +24 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +18 -1
- package/packages/dd-trace/src/tracer.js +3 -3
- package/packages/dd-trace/src/util.js +1 -1
- package/version.js +8 -4
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Analyzer = require('./vulnerability-analyzer')
|
|
4
|
+
const { getNodeModulesPaths } = require('../path-line')
|
|
5
|
+
|
|
6
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
|
|
7
|
+
|
|
8
|
+
class CookieAnalyzer extends Analyzer {
|
|
9
|
+
constructor (type, propertyToBeSafe) {
|
|
10
|
+
super(type)
|
|
11
|
+
this.propertyToBeSafe = propertyToBeSafe.toLowerCase()
|
|
12
|
+
this.addSub('datadog:iast:set-cookie', (cookieInfo) => this.analyze(cookieInfo))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_isVulnerable ({ cookieProperties, cookieValue }) {
|
|
16
|
+
return cookieValue && !(cookieProperties && cookieProperties
|
|
17
|
+
.map(x => x.toLowerCase().trim()).includes(this.propertyToBeSafe))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_getEvidence ({ cookieName }) {
|
|
21
|
+
return { value: cookieName }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_createHashSource (type, evidence, location) {
|
|
25
|
+
return `${type}:${evidence.value}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_getExcludedPaths () {
|
|
29
|
+
return EXCLUDED_PATHS
|
|
30
|
+
}
|
|
31
|
+
_checkOCE (context, value) {
|
|
32
|
+
if (value && value.location) {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
return super._checkOCE(context, value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_getLocation (value) {
|
|
39
|
+
if (!value) {
|
|
40
|
+
return super._getLocation()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (value.location) {
|
|
44
|
+
return value.location
|
|
45
|
+
}
|
|
46
|
+
const location = super._getLocation(value)
|
|
47
|
+
value.location = location
|
|
48
|
+
return location
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = CookieAnalyzer
|
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const Analyzer = require('./vulnerability-analyzer')
|
|
4
3
|
const { INSECURE_COOKIE } = require('../vulnerabilities')
|
|
4
|
+
const CookieAnalyzer = require('./cookie-analyzer')
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class InsecureCookieAnalyzer extends Analyzer {
|
|
6
|
+
class InsecureCookieAnalyzer extends CookieAnalyzer {
|
|
9
7
|
constructor () {
|
|
10
|
-
super(INSECURE_COOKIE)
|
|
11
|
-
this.addSub('datadog:iast:set-cookie', (cookieInfo) => this.analyze(cookieInfo))
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
_isVulnerable ({ cookieProperties, cookieValue }) {
|
|
15
|
-
return cookieValue && !(cookieProperties && cookieProperties.map(x => x.toLowerCase().trim()).includes('secure'))
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
_getEvidence ({ cookieName }) {
|
|
19
|
-
return { value: cookieName }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
_createHashSource (type, evidence, location) {
|
|
23
|
-
return `${type}:${evidence.value}`
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
_getExcludedPaths () {
|
|
27
|
-
return EXCLUDED_PATHS
|
|
8
|
+
super(INSECURE_COOKIE, 'secure')
|
|
28
9
|
}
|
|
29
10
|
}
|
|
30
11
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { NO_HTTPONLY_COOKIE } = require('../vulnerabilities')
|
|
4
|
+
const CookieAnalyzer = require('./cookie-analyzer')
|
|
5
|
+
|
|
6
|
+
class NoHttponlyCookieAnalyzer extends CookieAnalyzer {
|
|
7
|
+
constructor () {
|
|
8
|
+
super(NO_HTTPONLY_COOKIE, 'HttpOnly')
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = new NoHttponlyCookieAnalyzer()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { NO_SAMESITE_COOKIE } = require('../vulnerabilities')
|
|
4
|
+
const CookieAnalyzer = require('./cookie-analyzer')
|
|
5
|
+
|
|
6
|
+
class NoSamesiteCookieAnalyzer extends CookieAnalyzer {
|
|
7
|
+
constructor () {
|
|
8
|
+
super(NO_SAMESITE_COOKIE, 'SameSite=strict')
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = new NoSamesiteCookieAnalyzer()
|
|
@@ -14,24 +14,28 @@ class SetCookiesHeaderInterceptor extends Plugin {
|
|
|
14
14
|
allCookies = [value]
|
|
15
15
|
}
|
|
16
16
|
const alreadyCheckedCookies = this._getAlreadyCheckedCookiesInResponse(res)
|
|
17
|
+
|
|
18
|
+
let location
|
|
17
19
|
allCookies.forEach(cookieString => {
|
|
18
20
|
if (!alreadyCheckedCookies.includes(cookieString)) {
|
|
19
21
|
alreadyCheckedCookies.push(cookieString)
|
|
20
|
-
|
|
22
|
+
const parsedCookie = this._parseCookie(cookieString, location)
|
|
23
|
+
setCookieChannel.publish(parsedCookie)
|
|
24
|
+
location = parsedCookie.location
|
|
21
25
|
}
|
|
22
26
|
})
|
|
23
27
|
}
|
|
24
28
|
})
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
_parseCookie (cookieString) {
|
|
31
|
+
_parseCookie (cookieString, location) {
|
|
28
32
|
const cookieParts = cookieString.split(';')
|
|
29
33
|
const nameValueParts = cookieParts[0].split('=')
|
|
30
34
|
const cookieName = nameValueParts[0]
|
|
31
35
|
const cookieValue = nameValueParts.slice(1).join('=')
|
|
32
36
|
const cookieProperties = cookieParts.slice(1).map(part => part.trim())
|
|
33
37
|
|
|
34
|
-
return { cookieName, cookieValue, cookieProperties, cookieString }
|
|
38
|
+
return { cookieName, cookieValue, cookieProperties, cookieString, location }
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
_getAlreadyCheckedCookiesInResponse (res) {
|
|
@@ -6,9 +6,9 @@ const { getRanges } = require('../taint-tracking/operations')
|
|
|
6
6
|
const { storage } = require('../../../../../datadog-core')
|
|
7
7
|
const { getIastContext } = require('../iast-context')
|
|
8
8
|
const { addVulnerability } = require('../vulnerability-reporter')
|
|
9
|
+
const { getNodeModulesPaths } = require('../path-line')
|
|
9
10
|
|
|
10
|
-
const EXCLUDED_PATHS =
|
|
11
|
-
'node_modules\\sequelize']
|
|
11
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('mysql2', 'sequelize')
|
|
12
12
|
|
|
13
13
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
14
14
|
constructor () {
|
|
@@ -28,7 +28,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
28
28
|
|
|
29
29
|
this.addSub('datadog:sequelize:query:finish', () => {
|
|
30
30
|
const store = storage.getStore()
|
|
31
|
-
if (store.sequelizeParentStore) {
|
|
31
|
+
if (store && store.sequelizeParentStore) {
|
|
32
32
|
storage.enterWith(store.sequelizeParentStore)
|
|
33
33
|
}
|
|
34
34
|
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
|
+
const { UNVALIDATED_REDIRECT } = require('../vulnerabilities')
|
|
5
|
+
const { getNodeModulesPaths } = require('../path-line')
|
|
6
|
+
const { getRanges } = require('../taint-tracking/operations')
|
|
7
|
+
const { HTTP_REQUEST_HEADER_VALUE } = require('../taint-tracking/origin-types')
|
|
8
|
+
|
|
9
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('express/lib/response.js')
|
|
10
|
+
|
|
11
|
+
class UnvalidatedRedirectAnalyzer extends InjectionAnalyzer {
|
|
12
|
+
constructor () {
|
|
13
|
+
super(UNVALIDATED_REDIRECT)
|
|
14
|
+
|
|
15
|
+
this.addSub('datadog:http:server:response:set-header:finish', ({ name, value }) => this.analyze(name, value))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// TODO: In case the location header value is tainted, this analyzer should check the ranges of the tainted.
|
|
19
|
+
// And do not report a vulnerability if source of the ranges (range.iinfo.type) are exclusively url or path params
|
|
20
|
+
// to avoid false positives.
|
|
21
|
+
analyze (name, value) {
|
|
22
|
+
if (!this.isLocationHeader(name) || typeof value !== 'string') return
|
|
23
|
+
|
|
24
|
+
super.analyze(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
isLocationHeader (name) {
|
|
28
|
+
return name && name.trim().toLowerCase() === 'location'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_isVulnerable (value, iastContext) {
|
|
32
|
+
if (!value) return false
|
|
33
|
+
|
|
34
|
+
const ranges = getRanges(iastContext, value)
|
|
35
|
+
return ranges && ranges.length > 0 && !this._isRefererHeader(ranges)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_isRefererHeader (ranges) {
|
|
39
|
+
return ranges && ranges.every(range => range.iinfo.type === HTTP_REQUEST_HEADER_VALUE &&
|
|
40
|
+
range.iinfo.parameterName && range.iinfo.parameterName.toLowerCase() === 'referer')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_getExcludedPaths () {
|
|
44
|
+
return EXCLUDED_PATHS
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = new UnvalidatedRedirectAnalyzer()
|
|
@@ -38,7 +38,7 @@ class Analyzer extends Plugin {
|
|
|
38
38
|
|
|
39
39
|
_report (value, context) {
|
|
40
40
|
const evidence = this._getEvidence(value, context)
|
|
41
|
-
const location = this._getLocation()
|
|
41
|
+
const location = this._getLocation(value)
|
|
42
42
|
if (!this._isExcluded(location)) {
|
|
43
43
|
const spanId = context && context.rootSpan && context.rootSpan.context().toSpanId()
|
|
44
44
|
const vulnerability = this._createVulnerability(this._type, evidence, spanId, location)
|
|
@@ -47,7 +47,7 @@ class Analyzer extends Plugin {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
_reportIfVulnerable (value, context) {
|
|
50
|
-
if (this._isVulnerable(value, context) && this._checkOCE(context)) {
|
|
50
|
+
if (this._isVulnerable(value, context) && this._checkOCE(context, value)) {
|
|
51
51
|
this._report(value, context)
|
|
52
52
|
return true
|
|
53
53
|
}
|
|
@@ -78,7 +78,7 @@ class Analyzer extends Plugin {
|
|
|
78
78
|
for (let i = 0; i < values.length; i++) {
|
|
79
79
|
const value = values[i]
|
|
80
80
|
if (this._isVulnerable(value, iastContext)) {
|
|
81
|
-
if (this._checkOCE(iastContext)) {
|
|
81
|
+
if (this._checkOCE(iastContext, value)) {
|
|
82
82
|
this._report(value, iastContext)
|
|
83
83
|
}
|
|
84
84
|
break
|
|
@@ -5,10 +5,16 @@ const { storage } = require('../../../../datadog-core')
|
|
|
5
5
|
const overheadController = require('./overhead-controller')
|
|
6
6
|
const dc = require('../../../../diagnostics_channel')
|
|
7
7
|
const iastContextFunctions = require('./iast-context')
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
enableTaintTracking,
|
|
10
|
+
disableTaintTracking,
|
|
11
|
+
createTransaction,
|
|
12
|
+
removeTransaction,
|
|
13
|
+
taintTrackingPlugin
|
|
14
|
+
} = require('./taint-tracking')
|
|
15
|
+
const { IAST_ENABLED_TAG_KEY } = require('./tags')
|
|
9
16
|
|
|
10
17
|
const telemetryLogs = require('./telemetry/logs')
|
|
11
|
-
const IAST_ENABLED_TAG_KEY = '_dd.iast.enabled'
|
|
12
18
|
|
|
13
19
|
// TODO Change to `apm:http:server:request:[start|close]` when the subscription
|
|
14
20
|
// order of the callbacks can be enforce
|
|
@@ -48,6 +54,7 @@ function onIncomingHttpRequestStart (data) {
|
|
|
48
54
|
const iastContext = iastContextFunctions.saveIastContext(store, topContext, { rootSpan, req: data.req })
|
|
49
55
|
createTransaction(rootSpan.context().toSpanId(), iastContext)
|
|
50
56
|
overheadController.initializeRequestContext(iastContext)
|
|
57
|
+
taintTrackingPlugin.taintHeaders(data.req.headers, iastContext)
|
|
51
58
|
}
|
|
52
59
|
if (rootSpan.addTags) {
|
|
53
60
|
rootSpan.addTags({
|
|
@@ -5,6 +5,7 @@ const process = require('process')
|
|
|
5
5
|
const { calculateDDBasePath } = require('../../util')
|
|
6
6
|
const pathLine = {
|
|
7
7
|
getFirstNonDDPathAndLine,
|
|
8
|
+
getNodeModulesPaths,
|
|
8
9
|
getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
|
|
9
10
|
calculateDDBasePath, // Exported only for test purposes
|
|
10
11
|
ddBasePath: calculateDDBasePath(__dirname) // Only for test purposes
|
|
@@ -83,4 +84,16 @@ function isExcluded (callsite, externallyExcludedPaths) {
|
|
|
83
84
|
function getFirstNonDDPathAndLine (externallyExcludedPaths) {
|
|
84
85
|
return getFirstNonDDPathAndLineFromCallsites(getCallSiteInfo(), externallyExcludedPaths)
|
|
85
86
|
}
|
|
87
|
+
|
|
88
|
+
function getNodeModulesPaths (...paths) {
|
|
89
|
+
const nodeModulesPaths = []
|
|
90
|
+
|
|
91
|
+
paths.forEach(p => {
|
|
92
|
+
const pathParts = p.split('/')
|
|
93
|
+
nodeModulesPaths.push(path.join('node_modules', ...pathParts))
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return nodeModulesPaths
|
|
97
|
+
}
|
|
98
|
+
|
|
86
99
|
module.exports = pathLine
|
|
@@ -30,14 +30,14 @@ function newTaintedString (iastContext, string, name, type) {
|
|
|
30
30
|
return result
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function taintObject (iastContext, object, type) {
|
|
33
|
+
function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
34
34
|
let result = object
|
|
35
35
|
if (iastContext && iastContext[IAST_TRANSACTION_ID]) {
|
|
36
36
|
const transactionId = iastContext[IAST_TRANSACTION_ID]
|
|
37
37
|
const queue = [{ parent: null, property: null, value: object }]
|
|
38
38
|
const visited = new WeakSet()
|
|
39
39
|
while (queue.length > 0) {
|
|
40
|
-
const { parent, property, value } = queue.pop()
|
|
40
|
+
const { parent, property, value, key } = queue.pop()
|
|
41
41
|
if (value === null) {
|
|
42
42
|
continue
|
|
43
43
|
}
|
|
@@ -47,14 +47,23 @@ function taintObject (iastContext, object, type) {
|
|
|
47
47
|
if (!parent) {
|
|
48
48
|
result = tainted
|
|
49
49
|
} else {
|
|
50
|
-
|
|
50
|
+
if (keyTainting && key) {
|
|
51
|
+
const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
|
|
52
|
+
parent[taintedProperty] = tainted
|
|
53
|
+
} else {
|
|
54
|
+
parent[property] = tainted
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
} else if (typeof value === 'object' && !visited.has(value)) {
|
|
53
58
|
visited.add(value)
|
|
54
59
|
const keys = Object.keys(value)
|
|
55
60
|
for (let i = 0; i < keys.length; i++) {
|
|
56
61
|
const key = keys[i]
|
|
57
|
-
queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key] })
|
|
62
|
+
queue.push({ parent: value, property: property ? `${property}.${key}` : key, value: value[key], key })
|
|
63
|
+
}
|
|
64
|
+
if (parent && keyTainting && key) {
|
|
65
|
+
const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
|
|
66
|
+
parent[taintedProperty] = value
|
|
58
67
|
}
|
|
59
68
|
}
|
|
60
69
|
} catch (e) {
|
|
@@ -2,5 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
HTTP_REQUEST_BODY: 'http.request.body',
|
|
5
|
-
HTTP_REQUEST_PARAMETER: 'http.request.parameter'
|
|
5
|
+
HTTP_REQUEST_PARAMETER: 'http.request.parameter',
|
|
6
|
+
HTTP_REQUEST_COOKIE_VALUE: 'http.request.cookie.value',
|
|
7
|
+
HTTP_REQUEST_COOKIE_NAME: 'http.request.cookie.name',
|
|
8
|
+
HTTP_REQUEST_HEADER_NAME: 'http.request.header.name',
|
|
9
|
+
HTTP_REQUEST_HEADER_VALUE: 'http.request.header'
|
|
6
10
|
}
|
|
@@ -3,8 +3,15 @@
|
|
|
3
3
|
const Plugin = require('../../../plugins/plugin')
|
|
4
4
|
const { getIastContext } = require('../iast-context')
|
|
5
5
|
const { storage } = require('../../../../../datadog-core')
|
|
6
|
-
const { HTTP_REQUEST_PARAMETER, HTTP_REQUEST_BODY } = require('./origin-types')
|
|
7
6
|
const { taintObject } = require('./operations')
|
|
7
|
+
const {
|
|
8
|
+
HTTP_REQUEST_PARAMETER,
|
|
9
|
+
HTTP_REQUEST_BODY,
|
|
10
|
+
HTTP_REQUEST_COOKIE_VALUE,
|
|
11
|
+
HTTP_REQUEST_COOKIE_NAME,
|
|
12
|
+
HTTP_REQUEST_HEADER_VALUE,
|
|
13
|
+
HTTP_REQUEST_HEADER_NAME
|
|
14
|
+
} = require('./origin-types')
|
|
8
15
|
|
|
9
16
|
class TaintTrackingPlugin extends Plugin {
|
|
10
17
|
constructor () {
|
|
@@ -22,8 +29,8 @@ class TaintTrackingPlugin extends Plugin {
|
|
|
22
29
|
)
|
|
23
30
|
this.addSub(
|
|
24
31
|
'datadog:qs:parse:finish',
|
|
25
|
-
({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs)
|
|
26
|
-
|
|
32
|
+
({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs)
|
|
33
|
+
)
|
|
27
34
|
this.addSub('apm:express:middleware:next', ({ req }) => {
|
|
28
35
|
if (req && req.body && typeof req.body === 'object') {
|
|
29
36
|
const iastContext = getIastContext(storage.getStore())
|
|
@@ -33,16 +40,29 @@ class TaintTrackingPlugin extends Plugin {
|
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
})
|
|
43
|
+
this.addSub(
|
|
44
|
+
'datadog:cookie:parse:finish',
|
|
45
|
+
({ cookies }) => this._cookiesTaintTrackingHandler(cookies)
|
|
46
|
+
)
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
_taintTrackingHandler (type, target, property, iastContext = getIastContext(storage.getStore())) {
|
|
39
50
|
if (!property) {
|
|
40
51
|
taintObject(iastContext, target, type)
|
|
41
|
-
} else {
|
|
52
|
+
} else if (target[property]) {
|
|
42
53
|
target[property] = taintObject(iastContext, target[property], type)
|
|
43
54
|
}
|
|
44
55
|
}
|
|
45
56
|
|
|
57
|
+
_cookiesTaintTrackingHandler (target) {
|
|
58
|
+
const iastContext = getIastContext(storage.getStore())
|
|
59
|
+
taintObject(iastContext, target, HTTP_REQUEST_COOKIE_VALUE, true, HTTP_REQUEST_COOKIE_NAME)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
taintHeaders (headers, iastContext) {
|
|
63
|
+
taintObject(iastContext, headers, HTTP_REQUEST_HEADER_VALUE, true, HTTP_REQUEST_HEADER_NAME)
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
enable () {
|
|
47
67
|
this.configure(true)
|
|
48
68
|
}
|
|
@@ -23,7 +23,9 @@ class SensitiveHandler {
|
|
|
23
23
|
this._sensitiveAnalyzers.set(vulnerabilities.COMMAND_INJECTION, new CommandSensitiveAnalyzer())
|
|
24
24
|
this._sensitiveAnalyzers.set(vulnerabilities.LDAP_INJECTION, new LdapSensitiveAnalyzer())
|
|
25
25
|
this._sensitiveAnalyzers.set(vulnerabilities.SQL_INJECTION, new SqlSensitiveAnalyzer())
|
|
26
|
-
|
|
26
|
+
const urlSensitiveAnalyzer = new UrlSensitiveAnalyzer()
|
|
27
|
+
this._sensitiveAnalyzers.set(vulnerabilities.SSRF, urlSensitiveAnalyzer)
|
|
28
|
+
this._sensitiveAnalyzers.set(vulnerabilities.UNVALIDATED_REDIRECT, urlSensitiveAnalyzer)
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
isSensibleName (name) {
|
|
@@ -2,9 +2,12 @@ module.exports = {
|
|
|
2
2
|
COMMAND_INJECTION: 'COMMAND_INJECTION',
|
|
3
3
|
INSECURE_COOKIE: 'INSECURE_COOKIE',
|
|
4
4
|
LDAP_INJECTION: 'LDAP_INJECTION',
|
|
5
|
+
NO_HTTPONLY_COOKIE: 'NO_HTTPONLY_COOKIE',
|
|
6
|
+
NO_SAMESITE_COOKIE: 'NO_SAMESITE_COOKIE',
|
|
5
7
|
PATH_TRAVERSAL: 'PATH_TRAVERSAL',
|
|
6
8
|
SQL_INJECTION: 'SQL_INJECTION',
|
|
7
9
|
SSRF: 'SSRF',
|
|
10
|
+
UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
|
|
8
11
|
WEAK_CIPHER: 'WEAK_CIPHER',
|
|
9
12
|
WEAK_HASH: 'WEAK_HASH'
|
|
10
13
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const { MANUAL_KEEP } = require('../../../../../ext/tags')
|
|
2
4
|
const LRU = require('lru-cache')
|
|
3
5
|
const vulnerabilitiesFormatter = require('./vulnerabilities-formatter')
|
|
6
|
+
const { IAST_ENABLED_TAG_KEY, IAST_JSON_TAG_KEY } = require('./tags')
|
|
7
|
+
|
|
4
8
|
const VULNERABILITIES_KEY = 'vulnerabilities'
|
|
5
|
-
const IAST_JSON_TAG_KEY = '_dd.iast.json'
|
|
6
9
|
const VULNERABILITY_HASHES_MAX_SIZE = 1000
|
|
7
10
|
const VULNERABILITY_HASHES = new LRU({ max: VULNERABILITY_HASHES_MAX_SIZE })
|
|
8
11
|
const RESET_VULNERABILITY_CACHE_INTERVAL = 60 * 60 * 1000 // 1 hour
|
|
@@ -39,6 +42,9 @@ function sendVulnerabilities (vulnerabilities, rootSpan) {
|
|
|
39
42
|
vulnerabilities.forEach((vulnerability) => {
|
|
40
43
|
vulnerability.location.spanId = span.context().toSpanId()
|
|
41
44
|
})
|
|
45
|
+
span.addTags({
|
|
46
|
+
[IAST_ENABLED_TAG_KEY]: 1
|
|
47
|
+
})
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
if (span && span.addTags) {
|
|
@@ -120,7 +120,8 @@ class CiVisibilityExporter extends AgentInfoExporter {
|
|
|
120
120
|
* CI Visibility Protocol, hence the this._canUseCiVisProtocol promise.
|
|
121
121
|
*/
|
|
122
122
|
getItrConfiguration (testConfiguration, callback) {
|
|
123
|
-
|
|
123
|
+
const { repositoryUrl } = testConfiguration
|
|
124
|
+
this.sendGitMetadata(repositoryUrl)
|
|
124
125
|
if (!this.shouldRequestItrConfiguration()) {
|
|
125
126
|
return callback(null, {})
|
|
126
127
|
}
|
|
@@ -147,7 +148,7 @@ class CiVisibilityExporter extends AgentInfoExporter {
|
|
|
147
148
|
})
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
sendGitMetadata () {
|
|
151
|
+
sendGitMetadata (repositoryUrl) {
|
|
151
152
|
if (!this._config.isGitUploadEnabled) {
|
|
152
153
|
return
|
|
153
154
|
}
|
|
@@ -155,7 +156,7 @@ class CiVisibilityExporter extends AgentInfoExporter {
|
|
|
155
156
|
if (!canUseCiVisProtocol) {
|
|
156
157
|
return
|
|
157
158
|
}
|
|
158
|
-
sendGitMetadataRequest(this._getApiUrl(), !!this._isUsingEvpProxy, (err) => {
|
|
159
|
+
sendGitMetadataRequest(this._getApiUrl(), !!this._isUsingEvpProxy, repositoryUrl, (err) => {
|
|
159
160
|
if (err) {
|
|
160
161
|
log.error(`Error uploading git metadata: ${err.message}`)
|
|
161
162
|
} else {
|
|
@@ -152,8 +152,11 @@ function uploadPackFile ({ url, isEvpProxy, packFileToUpload, repositoryUrl, hea
|
|
|
152
152
|
/**
|
|
153
153
|
* This function uploads git metadata to CI Visibility's backend.
|
|
154
154
|
*/
|
|
155
|
-
function sendGitMetadata (url, isEvpProxy, callback) {
|
|
156
|
-
|
|
155
|
+
function sendGitMetadata (url, isEvpProxy, configRepositoryUrl, callback) {
|
|
156
|
+
let repositoryUrl = configRepositoryUrl
|
|
157
|
+
if (!repositoryUrl) {
|
|
158
|
+
repositoryUrl = getRepositoryUrl()
|
|
159
|
+
}
|
|
157
160
|
|
|
158
161
|
if (!repositoryUrl) {
|
|
159
162
|
return callback(new Error('Repository URL is empty'))
|
|
@@ -201,6 +201,17 @@ class Config {
|
|
|
201
201
|
false
|
|
202
202
|
)
|
|
203
203
|
|
|
204
|
+
const DD_OPENAI_LOGS_ENABLED = coalesce(
|
|
205
|
+
options.openAiLogsEnabled,
|
|
206
|
+
process.env.DD_OPENAI_LOGS_ENABLED,
|
|
207
|
+
false
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
const DD_API_KEY = coalesce(
|
|
211
|
+
process.env.DATADOG_API_KEY,
|
|
212
|
+
process.env.DD_API_KEY
|
|
213
|
+
)
|
|
214
|
+
|
|
204
215
|
const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined
|
|
205
216
|
|
|
206
217
|
const isDeprecatedGCPFunction = process.env.FUNCTION_NAME !== undefined && process.env.GCP_PROJECT !== undefined
|
|
@@ -468,6 +479,8 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
468
479
|
|
|
469
480
|
this.tracing = !isFalse(DD_TRACING_ENABLED)
|
|
470
481
|
this.dbmPropagationMode = DD_DBM_PROPAGATION_MODE
|
|
482
|
+
this.openAiLogsEnabled = DD_OPENAI_LOGS_ENABLED
|
|
483
|
+
this.apiKey = DD_API_KEY
|
|
471
484
|
this.logInjection = isTrue(DD_LOGS_INJECTION)
|
|
472
485
|
this.env = DD_ENV
|
|
473
486
|
this.url = DD_CIVISIBILITY_AGENTLESS_URL ? new URL(DD_CIVISIBILITY_AGENTLESS_URL)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const tracerLogger = require('../../log')// path to require tracer logger
|
|
2
|
+
|
|
3
|
+
const https = require('https')
|
|
4
|
+
|
|
5
|
+
class ExternalLogger {
|
|
6
|
+
// Note: these attribute names match the corresponding entry in the JSON payload.
|
|
7
|
+
constructor ({
|
|
8
|
+
ddsource, hostname, service, apiKey, site = 'datadoghq.com', interval = 10000, timeout = 2000, limit = 1000
|
|
9
|
+
}) {
|
|
10
|
+
this.ddsource = ddsource
|
|
11
|
+
this.hostname = hostname
|
|
12
|
+
this.service = service
|
|
13
|
+
this.interval = interval
|
|
14
|
+
this.timeout = timeout
|
|
15
|
+
this.queue = []
|
|
16
|
+
this.limit = limit
|
|
17
|
+
this.endpoint = '/api/v2/logs'
|
|
18
|
+
this.site = site
|
|
19
|
+
this.intake = `http-intake.logs.${this.site}`
|
|
20
|
+
this.headers = {
|
|
21
|
+
'DD-API-KEY': apiKey,
|
|
22
|
+
'Content-Type': 'application/json'
|
|
23
|
+
}
|
|
24
|
+
this.timer = setInterval(() => {
|
|
25
|
+
this.flush()
|
|
26
|
+
}, this.interval).unref()
|
|
27
|
+
|
|
28
|
+
tracerLogger.debug(`started log writer to https://${this.intake}${this.endpoint}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static tagString (tags) {
|
|
32
|
+
const tagArray = []
|
|
33
|
+
for (const key in tags) {
|
|
34
|
+
tagArray.push(key + ':' + tags[key])
|
|
35
|
+
}
|
|
36
|
+
return tagArray.join(',')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Parses and enqueues a log
|
|
40
|
+
log (log, span, tags) {
|
|
41
|
+
const logTags = ExternalLogger.tagString(tags)
|
|
42
|
+
|
|
43
|
+
if (span) {
|
|
44
|
+
log['dd.trace_id'] = String(span.trace_id)
|
|
45
|
+
log['dd.span_id'] = String(span.span_id)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const payload = {
|
|
49
|
+
...log,
|
|
50
|
+
'timestamp': Date.now(),
|
|
51
|
+
'hostname': log.hostname || this.hostname,
|
|
52
|
+
'ddsource': log.ddsource || this.ddsource,
|
|
53
|
+
'service': log.service || this.service,
|
|
54
|
+
'ddtags': logTags || undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.enqueue(payload)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Enqueues a raw, non-formatted log object
|
|
61
|
+
enqueue (log) {
|
|
62
|
+
if (this.queue.length >= this.limit) {
|
|
63
|
+
this.flush()
|
|
64
|
+
}
|
|
65
|
+
this.queue.push(log)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
shutdown () {
|
|
69
|
+
clearInterval(this.timer)
|
|
70
|
+
this.flush()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Flushes logs with optional callback for when the call is complete
|
|
74
|
+
flush (cb = () => {}) {
|
|
75
|
+
let logs
|
|
76
|
+
let numLogs
|
|
77
|
+
let encodedLogs
|
|
78
|
+
|
|
79
|
+
if (!this.queue.length) {
|
|
80
|
+
setImmediate(() => cb())
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
logs = this.queue
|
|
86
|
+
this.queue = []
|
|
87
|
+
|
|
88
|
+
numLogs = logs.length
|
|
89
|
+
encodedLogs = JSON.stringify(logs)
|
|
90
|
+
} catch (error) {
|
|
91
|
+
tracerLogger.error(`failed to encode ${numLogs} logs`)
|
|
92
|
+
setImmediate(() => cb(error))
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const options = {
|
|
97
|
+
hostname: this.intake,
|
|
98
|
+
port: 443,
|
|
99
|
+
path: this.endpoint,
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: this.headers,
|
|
102
|
+
timeout: this.timeout
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const req = https.request(options, (res) => {
|
|
106
|
+
tracerLogger.info(`statusCode: ${res.statusCode}`)
|
|
107
|
+
})
|
|
108
|
+
req.once('error', (e) => {
|
|
109
|
+
tracerLogger.error(`failed to send ${numLogs} log(s), with error ${e.message}`)
|
|
110
|
+
cb(e)
|
|
111
|
+
})
|
|
112
|
+
req.write(encodedLogs)
|
|
113
|
+
req.end()
|
|
114
|
+
req.once('response', (res) => {
|
|
115
|
+
if (res.statusCode >= 400) {
|
|
116
|
+
const error = new Error(`failed to send ${numLogs} logs, received response code ${res.statusCode}`)
|
|
117
|
+
tracerLogger.error(error.message)
|
|
118
|
+
cb(error)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
cb()
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = ExternalLogger
|