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.
Files changed (55) hide show
  1. package/MIGRATING.md +158 -0
  2. package/README.md +18 -11
  3. package/index.d.ts +7 -0
  4. package/package.json +4 -4
  5. package/packages/datadog-instrumentations/src/cookie.js +21 -0
  6. package/packages/datadog-instrumentations/src/fetch.js +48 -0
  7. package/packages/datadog-instrumentations/src/grpc/server.js +1 -1
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  9. package/packages/datadog-instrumentations/src/helpers/register.js +10 -0
  10. package/packages/datadog-instrumentations/src/jest.js +2 -3
  11. package/packages/datadog-instrumentations/src/next.js +2 -2
  12. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +18 -0
  13. package/packages/datadog-plugin-fetch/src/index.js +36 -0
  14. package/packages/datadog-plugin-http/src/client.js +24 -8
  15. package/packages/datadog-plugin-mysql/src/index.js +2 -11
  16. package/packages/datadog-plugin-tedious/src/index.js +2 -2
  17. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -0
  18. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +52 -0
  19. package/packages/dd-trace/src/appsec/iast/analyzers/insecure-cookie-analyzer.js +3 -22
  20. package/packages/dd-trace/src/appsec/iast/analyzers/no-httponly-cookie-analyzer.js +12 -0
  21. package/packages/dd-trace/src/appsec/iast/analyzers/no-samesite-cookie-analyzer.js +12 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +7 -3
  23. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +3 -3
  24. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +48 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +3 -3
  26. package/packages/dd-trace/src/appsec/iast/index.js +9 -2
  27. package/packages/dd-trace/src/appsec/iast/path-line.js +13 -0
  28. package/packages/dd-trace/src/appsec/iast/tags.js +6 -0
  29. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +2 -1
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +13 -4
  31. package/packages/dd-trace/src/appsec/iast/taint-tracking/origin-types.js +5 -1
  32. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +24 -4
  33. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -1
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +3 -0
  35. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +7 -1
  36. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -3
  37. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +5 -2
  38. package/packages/dd-trace/src/config.js +13 -0
  39. package/packages/dd-trace/src/external-logger/src/index.js +126 -0
  40. package/packages/dd-trace/src/external-logger/test/index.spec.js +147 -0
  41. package/packages/dd-trace/src/lambda/handler.js +3 -15
  42. package/packages/dd-trace/src/noop/proxy.js +4 -0
  43. package/packages/dd-trace/src/opentelemetry/context_manager.js +1 -1
  44. package/packages/dd-trace/src/plugin_manager.js +10 -7
  45. package/packages/dd-trace/src/plugins/database.js +7 -3
  46. package/packages/dd-trace/src/plugins/plugin.js +3 -1
  47. package/packages/dd-trace/src/plugins/util/exec.js +2 -2
  48. package/packages/dd-trace/src/plugins/util/git.js +51 -24
  49. package/packages/dd-trace/src/profiling/config.js +2 -0
  50. package/packages/dd-trace/src/profiling/profiler.js +13 -4
  51. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +24 -1
  52. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +18 -1
  53. package/packages/dd-trace/src/tracer.js +3 -3
  54. package/packages/dd-trace/src/util.js +1 -1
  55. 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
- const EXCLUDED_PATHS = ['node_modules/express/lib/response.js', 'node_modules\\express\\lib\\response.js']
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
- setCookieChannel.publish(this._parseCookie(cookieString))
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 = ['node_modules/mysql2', 'node_modules/sequelize', 'node_modules\\mysql2',
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 { enableTaintTracking, disableTaintTracking, createTransaction, removeTransaction } = require('./taint-tracking')
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
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ IAST_ENABLED_TAG_KEY: '_dd.iast.enabled',
5
+ IAST_JSON_TAG_KEY: '_dd.iast.json'
6
+ }
@@ -23,5 +23,6 @@ module.exports = {
23
23
  },
24
24
  setMaxTransactions: setMaxTransactions,
25
25
  createTransaction: createTransaction,
26
- removeTransaction: removeTransaction
26
+ removeTransaction: removeTransaction,
27
+ taintTrackingPlugin
27
28
  }
@@ -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
- parent[property] = tainted
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
- this._sensitiveAnalyzers.set(vulnerabilities.SSRF, new UrlSensitiveAnalyzer())
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
- this.sendGitMetadata()
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
- const repositoryUrl = getRepositoryUrl()
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