dd-trace 3.9.3 → 3.11.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 (67) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/README.md +108 -43
  3. package/ci/init.js +6 -1
  4. package/ext/exporters.d.ts +2 -1
  5. package/ext/exporters.js +2 -1
  6. package/index.d.ts +23 -2
  7. package/package.json +8 -6
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  9. package/packages/datadog-instrumentations/src/http/server.js +11 -2
  10. package/packages/datadog-instrumentations/src/jest.js +24 -10
  11. package/packages/datadog-instrumentations/src/ldapjs.js +91 -0
  12. package/packages/datadog-instrumentations/src/mocha.js +7 -4
  13. package/packages/datadog-instrumentations/src/opensearch.js +1 -1
  14. package/packages/datadog-instrumentations/src/pg.js +1 -2
  15. package/packages/datadog-instrumentations/src/router.js +1 -1
  16. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  17. package/packages/datadog-plugin-http/src/server.js +9 -4
  18. package/packages/datadog-plugin-jest/src/index.js +21 -25
  19. package/packages/datadog-plugin-mocha/src/index.js +11 -18
  20. package/packages/datadog-plugin-mongodb-core/src/index.js +3 -3
  21. package/packages/datadog-plugin-router/src/index.js +6 -3
  22. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  23. package/packages/dd-trace/src/appsec/blocking.js +44 -0
  24. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +5 -1
  25. package/packages/dd-trace/src/appsec/gateway/engine/engine.js +1 -1
  26. package/packages/dd-trace/src/appsec/gateway/engine/index.js +6 -1
  27. package/packages/dd-trace/src/appsec/gateway/engine/runner.js +0 -1
  28. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -1
  29. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +11 -0
  30. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  31. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +40 -2
  32. package/packages/dd-trace/src/appsec/iast/iast-context.js +3 -1
  33. package/packages/dd-trace/src/appsec/iast/index.js +7 -0
  34. package/packages/dd-trace/src/appsec/iast/path-line.js +6 -5
  35. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +12 -0
  36. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -29
  37. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +16 -15
  38. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +85 -0
  39. package/packages/dd-trace/src/appsec/index.js +71 -25
  40. package/packages/dd-trace/src/{plugins/util → appsec}/ip_blocklist.js +0 -0
  41. package/packages/dd-trace/src/appsec/ip_extractor.js +98 -0
  42. package/packages/dd-trace/src/appsec/remote_config/index.js +25 -3
  43. package/packages/dd-trace/src/appsec/remote_config/manager.js +1 -1
  44. package/packages/dd-trace/src/appsec/rule_manager.js +58 -1
  45. package/packages/dd-trace/src/appsec/templates/blocked.html +99 -0
  46. package/packages/dd-trace/src/appsec/templates/blocked.json +8 -0
  47. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +66 -0
  48. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +8 -1
  49. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +19 -51
  50. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +9 -1
  51. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +202 -0
  52. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +20 -7
  53. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +22 -19
  54. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -18
  55. package/packages/dd-trace/src/config.js +34 -10
  56. package/packages/dd-trace/src/exporter.js +3 -0
  57. package/packages/dd-trace/src/exporters/agent/index.js +4 -0
  58. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +82 -0
  59. package/packages/dd-trace/src/plugin_manager.js +0 -18
  60. package/packages/dd-trace/src/plugins/ci_plugin.js +9 -50
  61. package/packages/dd-trace/src/plugins/util/ci.js +35 -2
  62. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  63. package/packages/dd-trace/src/plugins/util/web.js +2 -106
  64. package/packages/dd-trace/src/profiling/exporters/agent.js +4 -0
  65. package/packages/dd-trace/src/proxy.js +3 -17
  66. package/packages/dd-trace/src/ritm.js +18 -13
  67. package/packages/dd-trace/src/telemetry/dependencies.js +11 -1
@@ -40,9 +40,8 @@ function wrapQuery (query) {
40
40
  const callbackResource = new AsyncResource('bound-anonymous-fn')
41
41
  const asyncResource = new AsyncResource('bound-anonymous-fn')
42
42
  const processId = this.processID
43
-
44
43
  return asyncResource.runInAsyncScope(() => {
45
- startCh.publish({ params: this.connectionParameters, query: pgQuery, processId })
44
+ startCh.publish({ params: this.connectionParameters, originalQuery: pgQuery.text, query: pgQuery, processId })
46
45
 
47
46
  const finish = asyncResource.bind(function (error) {
48
47
  if (error) {
@@ -90,7 +90,7 @@ function createWrapRouterMethod (name) {
90
90
 
91
91
  function wrapNext (req, next) {
92
92
  return function (error) {
93
- if (error) {
93
+ if (error && error !== 'route' && error !== 'router') {
94
94
  errorChannel.publish({ req, error })
95
95
  }
96
96
 
@@ -21,7 +21,7 @@ class CucumberPlugin extends CiPlugin {
21
21
  super(...args)
22
22
 
23
23
  this.addSub('ci:cucumber:session:finish', () => {
24
- this.tracer._exporter._writer.flush()
24
+ this.tracer._exporter.flush()
25
25
  })
26
26
 
27
27
  this.addSub('ci:cucumber:run:start', ({ testName, fullTestSuite }) => {
@@ -3,7 +3,7 @@
3
3
  const Plugin = require('../../dd-trace/src/plugins/plugin')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const web = require('../../dd-trace/src/plugins/util/web')
6
- const { incomingHttpRequestStart } = require('../../dd-trace/src/appsec/gateway/channels')
6
+ const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/gateway/channels')
7
7
  const { COMPONENT } = require('../../dd-trace/src/constants')
8
8
 
9
9
  class HttpServerPlugin extends Plugin {
@@ -16,7 +16,7 @@ class HttpServerPlugin extends Plugin {
16
16
 
17
17
  this._parentStore = undefined
18
18
 
19
- this.addSub('apm:http:server:request:start', ({ req, res }) => {
19
+ this.addSub('apm:http:server:request:start', ({ req, res, abortController }) => {
20
20
  const store = storage.getStore()
21
21
  const span = web.startSpan(this.tracer, this.config, req, res, 'web.request')
22
22
 
@@ -33,7 +33,7 @@ class HttpServerPlugin extends Plugin {
33
33
  }
34
34
 
35
35
  if (incomingHttpRequestStart.hasSubscribers) {
36
- incomingHttpRequestStart.publish({ req, res })
36
+ incomingHttpRequestStart.publish({ req, res, abortController })
37
37
  }
38
38
  })
39
39
 
@@ -42,7 +42,8 @@ class HttpServerPlugin extends Plugin {
42
42
  })
43
43
 
44
44
  this.addSub('apm:http:server:request:exit', ({ req }) => {
45
- this.enter(this._parentStore)
45
+ const span = this._parentStore && this._parentStore.span
46
+ this.enter(span, this._parentStore)
46
47
  this._parentStore = undefined
47
48
  })
48
49
 
@@ -51,6 +52,10 @@ class HttpServerPlugin extends Plugin {
51
52
 
52
53
  if (!context || !context.res) return // Not created by a http.Server instance.
53
54
 
55
+ if (incomingHttpRequestEnd.hasSubscribers) {
56
+ incomingHttpRequestEnd.publish({ req, res: context.res })
57
+ }
58
+
54
59
  web.finishAll(context)
55
60
  })
56
61
  }
@@ -15,6 +15,8 @@ const {
15
15
  TEST_SUITE_ID,
16
16
  TEST_COMMAND,
17
17
  TEST_ITR_TESTS_SKIPPED,
18
+ TEST_SESSION_ITR_CODE_COVERAGE_ENABLED,
19
+ TEST_SESSION_ITR_SKIPPING_ENABLED,
18
20
  TEST_CODE_COVERAGE_LINES_TOTAL
19
21
  } = require('../../dd-trace/src/plugins/util/test')
20
22
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -33,7 +35,7 @@ class JestPlugin extends CiPlugin {
33
35
  // Used to handle the end of a jest worker to be able to flush
34
36
  const handler = ([message]) => {
35
37
  if (message === CHILD_MESSAGE_END) {
36
- this.tracer._exporter._writer.flush(() => {
38
+ this.tracer._exporter.flush(() => {
37
39
  // eslint-disable-next-line
38
40
  // https://github.com/facebook/jest/blob/24ed3b5ecb419c023ee6fdbc838f07cc028fc007/packages/jest-worker/src/workers/processChild.ts#L118-L133
39
41
  // Only after the flush is done we clean up open handles
@@ -48,9 +50,6 @@ class JestPlugin extends CiPlugin {
48
50
  this.codeOwnersEntries = getCodeOwnersFileEntries()
49
51
 
50
52
  this.addSub('ci:jest:session:start', (command) => {
51
- if (!this.config.isAgentlessEnabled) {
52
- return
53
- }
54
53
  const store = storage.getStore()
55
54
  const childOf = getTestParentSpan(this.tracer)
56
55
  const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
@@ -66,30 +65,32 @@ class JestPlugin extends CiPlugin {
66
65
  this.enter(testSessionSpan, store)
67
66
  })
68
67
 
69
- this.addSub('ci:jest:session:finish', ({ status, isTestsSkipped, testCodeCoverageLinesTotal }) => {
70
- if (!this.config.isAgentlessEnabled) {
71
- return
72
- }
68
+ this.addSub('ci:jest:session:finish', ({
69
+ status,
70
+ isSuitesSkipped,
71
+ isSuitesSkippingEnabled,
72
+ isCodeCoverageEnabled,
73
+ testCodeCoverageLinesTotal
74
+ }) => {
73
75
  const testSessionSpan = storage.getStore().span
76
+
74
77
  testSessionSpan.setTag(TEST_STATUS, status)
75
- if (isTestsSkipped) {
76
- testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, 'true')
77
- }
78
+ testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
79
+ testSessionSpan.setTag(TEST_SESSION_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
80
+ testSessionSpan.setTag(TEST_SESSION_ITR_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
81
+
78
82
  if (testCodeCoverageLinesTotal !== undefined) {
79
83
  testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_TOTAL, testCodeCoverageLinesTotal)
80
84
  }
81
85
  testSessionSpan.finish()
82
86
  finishAllTraceSpans(testSessionSpan)
83
- this.tracer._exporter._writer.flush()
87
+ this.tracer._exporter.flush()
84
88
  })
85
89
 
86
90
  // Test suites can be run in a different process from jest's main one.
87
91
  // This subscriber changes the configuration objects from jest to inject the trace id
88
92
  // of the test session to the processes that run the test suites.
89
93
  this.addSub('ci:jest:session:configuration', configs => {
90
- if (!this.config.isAgentlessEnabled) {
91
- return
92
- }
93
94
  const testSessionSpan = storage.getStore().span
94
95
  configs.forEach(config => {
95
96
  config._ddTestSessionId = testSessionSpan.context()._traceId.toString(10)
@@ -98,10 +99,6 @@ class JestPlugin extends CiPlugin {
98
99
  })
99
100
 
100
101
  this.addSub('ci:jest:test-suite:start', ({ testSuite, testEnvironmentOptions }) => {
101
- if (!this.config.isAgentlessEnabled) {
102
- return
103
- }
104
-
105
102
  const { _ddTestSessionId: testSessionId, _ddTestCommand: testCommand } = testEnvironmentOptions
106
103
 
107
104
  const store = storage.getStore()
@@ -125,9 +122,6 @@ class JestPlugin extends CiPlugin {
125
122
  })
126
123
 
127
124
  this.addSub('ci:jest:test-suite:finish', ({ status, errorMessage }) => {
128
- if (!this.config.isAgentlessEnabled) {
129
- return
130
- }
131
125
  const testSuiteSpan = storage.getStore().span
132
126
  testSuiteSpan.setTag(TEST_STATUS, status)
133
127
  if (errorMessage) {
@@ -139,10 +133,12 @@ class JestPlugin extends CiPlugin {
139
133
  finishAllTraceSpans(testSuiteSpan)
140
134
  })
141
135
 
136
+ /**
137
+ * This can't use `this.itrConfig` like `ci:mocha:test-suite:code-coverage`
138
+ * because this subscription happens in a different process from the one
139
+ * fetching the ITR config.
140
+ */
142
141
  this.addSub('ci:jest:test-suite:code-coverage', (coverageFiles) => {
143
- if (!this.config.isAgentlessEnabled || !this.config.isIntelligentTestRunnerEnabled) {
144
- return
145
- }
146
142
  const testSuiteSpan = storage.getStore().span
147
143
  this.tracer._exporter.exportCoverage({ span: testSuiteSpan, coverageFiles })
148
144
  })
@@ -14,7 +14,10 @@ const {
14
14
  getTestSuiteCommonTags,
15
15
  TEST_SUITE_ID,
16
16
  TEST_SESSION_ID,
17
- TEST_COMMAND
17
+ TEST_COMMAND,
18
+ TEST_ITR_TESTS_SKIPPED,
19
+ TEST_SESSION_ITR_CODE_COVERAGE_ENABLED,
20
+ TEST_SESSION_ITR_SKIPPING_ENABLED
18
21
  } = require('../../dd-trace/src/plugins/util/test')
19
22
  const { COMPONENT } = require('../../dd-trace/src/constants')
20
23
 
@@ -31,9 +34,6 @@ class MochaPlugin extends CiPlugin {
31
34
  this.sourceRoot = process.cwd()
32
35
 
33
36
  this.addSub('ci:mocha:test-suite:code-coverage', ({ coverageFiles, suiteFile }) => {
34
- if (!this.config.isAgentlessEnabled || !this.config.isIntelligentTestRunnerEnabled) {
35
- return
36
- }
37
37
  if (!this.itrConfig || !this.itrConfig.isCodeCoverageEnabled) {
38
38
  return
39
39
  }
@@ -49,9 +49,6 @@ class MochaPlugin extends CiPlugin {
49
49
  })
50
50
 
51
51
  this.addSub('ci:mocha:session:start', (command) => {
52
- if (!this.config.isAgentlessEnabled) {
53
- return
54
- }
55
52
  const childOf = getTestParentSpan(this.tracer)
56
53
  const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
57
54
 
@@ -67,9 +64,6 @@ class MochaPlugin extends CiPlugin {
67
64
  })
68
65
 
69
66
  this.addSub('ci:mocha:test-suite:start', (suite) => {
70
- if (!this.config.isAgentlessEnabled) {
71
- return
72
- }
73
67
  const store = storage.getStore()
74
68
  const testSuiteMetadata = getTestSuiteCommonTags(
75
69
  this.command,
@@ -89,9 +83,6 @@ class MochaPlugin extends CiPlugin {
89
83
  })
90
84
 
91
85
  this.addSub('ci:mocha:test-suite:finish', (status) => {
92
- if (!this.config.isAgentlessEnabled) {
93
- return
94
- }
95
86
  const span = storage.getStore().span
96
87
  // the test status of the suite may have been set in ci:mocha:test-suite:error already
97
88
  if (!span.context()._tags[TEST_STATUS]) {
@@ -101,9 +92,6 @@ class MochaPlugin extends CiPlugin {
101
92
  })
102
93
 
103
94
  this.addSub('ci:mocha:test-suite:error', (err) => {
104
- if (!this.config.isAgentlessEnabled) {
105
- return
106
- }
107
95
  const span = storage.getStore().span
108
96
  span.setTag('error', err)
109
97
  span.setTag(TEST_STATUS, 'fail')
@@ -151,14 +139,19 @@ class MochaPlugin extends CiPlugin {
151
139
  this._testNameToParams[name] = params
152
140
  })
153
141
 
154
- this.addSub('ci:mocha:session:finish', (status) => {
142
+ this.addSub('ci:mocha:session:finish', ({ status, isSuitesSkipped }) => {
155
143
  if (this.testSessionSpan) {
144
+ const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
156
145
  this.testSessionSpan.setTag(TEST_STATUS, status)
146
+ this.testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
147
+ this.testSessionSpan.setTag(TEST_SESSION_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
148
+ this.testSessionSpan.setTag(TEST_SESSION_ITR_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
149
+
157
150
  this.testSessionSpan.finish()
158
151
  finishAllTraceSpans(this.testSessionSpan)
159
152
  }
160
- this.tracer._exporter._writer.flush()
161
153
  this.itrConfig = null
154
+ this.tracer._exporter.flush()
162
155
  })
163
156
  }
164
157
 
@@ -8,7 +8,7 @@ class MongodbCorePlugin extends DatabasePlugin {
8
8
 
9
9
  start ({ ns, ops, options = {}, name }) {
10
10
  const query = getQuery(ops)
11
- const resource = truncate(getResource(ns, query, name))
11
+ const resource = truncate(getResource(this, ns, query, name))
12
12
 
13
13
  this.startSpan('mongodb.query', {
14
14
  service: this.config.service,
@@ -31,10 +31,10 @@ function getQuery (cmd) {
31
31
  if (cmd.filter) return JSON.stringify(limitDepth(cmd.filter))
32
32
  }
33
33
 
34
- function getResource (ns, query, operationName) {
34
+ function getResource (plugin, ns, query, operationName) {
35
35
  const parts = [operationName, ns]
36
36
 
37
- if (query) {
37
+ if (plugin.config.queryInResourceName && query) {
38
38
  parts.push(query)
39
39
  }
40
40
 
@@ -29,8 +29,9 @@ class RouterPlugin extends WebPlugin {
29
29
  context.middleware.push(span)
30
30
  }
31
31
 
32
- this._storeStack.push(storage.getStore())
33
- this.enter(span)
32
+ const store = storage.getStore()
33
+ this._storeStack.push(store)
34
+ this.enter(span, store)
34
35
 
35
36
  web.patch(req)
36
37
  web.setRoute(req, context.route)
@@ -53,7 +54,9 @@ class RouterPlugin extends WebPlugin {
53
54
  })
54
55
 
55
56
  this.addSub(`apm:${this.constructor.name}:middleware:exit`, ({ req }) => {
56
- this.enter(this._storeStack.pop())
57
+ const savedStore = this._storeStack.pop()
58
+ const span = savedStore && savedStore.span
59
+ this.enter(span, savedStore)
57
60
  })
58
61
 
59
62
  this.addSub(`apm:${this.constructor.name}:middleware:error`, ({ req, error }) => {
@@ -14,5 +14,7 @@ module.exports = {
14
14
  HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
15
15
  // TODO: 'server.response.trailers',
16
16
  HTTP_INCOMING_REMOTE_IP: 'server.request.client_ip',
17
- HTTP_INCOMING_REMOTE_PORT: 'server.request.client_port'
17
+ HTTP_INCOMING_REMOTE_PORT: 'server.request.client_port',
18
+
19
+ HTTP_CLIENT_IP: 'http.client_ip'
18
20
  }
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ let templateHtml, templateJson
5
+ function block (req, res, topSpan, abortController) {
6
+ let type
7
+ let body
8
+
9
+ // parse the Accept header, ex: Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
10
+ const accept = req.headers.accept && req.headers.accept.split(',').map((str) => str.split(';', 1)[0].trim())
11
+
12
+ if (accept && accept.includes('text/html') && !accept.includes('application/json')) {
13
+ type = 'text/html'
14
+ body = templateHtml
15
+ } else {
16
+ type = 'application/json'
17
+ body = templateJson
18
+ }
19
+
20
+ topSpan.addTags({
21
+ 'appsec.blocked': 'true'
22
+ })
23
+
24
+ res.statusCode = 403
25
+ res.setHeader('Content-Type', type)
26
+ res.setHeader('Content-Length', Buffer.byteLength(body))
27
+ res.end(body)
28
+
29
+ abortController.abort()
30
+ }
31
+
32
+ function loadTemplates (config) {
33
+ templateHtml = fs.readFileSync(config.appsec.blockedTemplateHtml)
34
+ templateJson = fs.readFileSync(config.appsec.blockedTemplateJson)
35
+ }
36
+
37
+ async function loadTemplatesAsync (config) {
38
+ templateHtml = await fs.promises.readFile(config.appsec.blockedTemplateHtml)
39
+ templateJson = await fs.promises.readFile(config.appsec.blockedTemplateJson)
40
+ }
41
+
42
+ module.exports = {
43
+ block, loadTemplates, loadTemplatesAsync
44
+ }
@@ -61,7 +61,7 @@ class WAFCallback {
61
61
 
62
62
  subscribedAddresses.add(address)
63
63
 
64
- Gateway.manager.addSubscription({ addresses: [ address ], callback })
64
+ Gateway.manager.addSubscription({ addresses: [address], callback })
65
65
  }
66
66
  }
67
67
  }
@@ -123,6 +123,10 @@ class WAFCallback {
123
123
  return result.actions
124
124
  }
125
125
 
126
+ updateRuleData (ruleData) {
127
+ this.ddwaf.updateRuleData(ruleData)
128
+ }
129
+
126
130
  clear () {
127
131
  this.ddwaf.dispose()
128
132
 
@@ -28,7 +28,7 @@ class SubscriptionManager {
28
28
  const list = this.addressToSubscriptions.get(address)
29
29
 
30
30
  if (list === undefined) {
31
- this.addressToSubscriptions.set(address, [ subscription ])
31
+ this.addressToSubscriptions.set(address, [subscription])
32
32
  } else {
33
33
  list.push(subscription)
34
34
  }
@@ -22,6 +22,10 @@ function getContext () {
22
22
  return store && store.get('context')
23
23
  }
24
24
 
25
+ function needsAddress (address) {
26
+ return manager.addresses.has(address)
27
+ }
28
+
25
29
  function propagate (data, context = getContext()) {
26
30
  if (!context) return
27
31
 
@@ -30,7 +34,7 @@ function propagate (data, context = getContext()) {
30
34
  for (let i = 0; i < keys.length; ++i) {
31
35
  const key = keys[i]
32
36
 
33
- if (manager.addresses.has(key)) {
37
+ if (needsAddress(key)) {
34
38
  context.setValue(key, data[key])
35
39
  }
36
40
  }
@@ -42,5 +46,6 @@ module.exports = {
42
46
  manager,
43
47
  startContext,
44
48
  getContext,
49
+ needsAddress,
45
50
  propagate
46
51
  }
@@ -26,7 +26,6 @@ function runSubscriptions (subscriptions, params) {
26
26
  result = subscription.callback.method(params, store)
27
27
  } catch (err) {
28
28
  // TODO: log ?
29
- result = {}
30
29
  }
31
30
 
32
31
  results.push(result)
@@ -2,5 +2,6 @@ module.exports = {
2
2
  'WEAK_CIPHER_ANALYZER': require('./weak-cipher-analyzer'),
3
3
  'WEAK_HASH_ANALYZER': require('./weak-hash-analyzer'),
4
4
  'SQL_INJECTION_ANALYZER': require('./sql-injection-analyzer'),
5
- 'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer')
5
+ 'COMMAND_INJECTION_ANALYZER': require('./command-injection-analyzer'),
6
+ 'LDAP_ANALYZER': require('./ldap-injection-analyzer')
6
7
  }
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+ const InjectionAnalyzer = require('./injection-analyzer')
3
+
4
+ class LdapInjectionAnalyzer extends InjectionAnalyzer {
5
+ constructor () {
6
+ super('LDAP_INJECTION')
7
+ this.addSub('datadog:ldapjs:client:search', ({ base, filter }) => this.analyzeAll(base, filter))
8
+ }
9
+ }
10
+
11
+ module.exports = new LdapInjectionAnalyzer()
@@ -6,7 +6,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
6
6
  super('SQL_INJECTION')
7
7
  this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql))
8
8
  this.addSub('apm:mysql2:query:start', ({ sql }) => this.analyze(sql))
9
- this.addSub('apm:pg:query:start', ({ query }) => query && this.analyze(query.text))
9
+ this.addSub('apm:pg:query:start', ({ originalQuery }) => this.analyze(originalQuery))
10
10
  }
11
11
  }
12
12
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const Plugin = require('../../../../src/plugins/plugin')
4
4
  const { storage } = require('../../../../../datadog-core')
5
+ const log = require('../../../log')
5
6
  const { getFirstNonDDPathAndLine } = require('../path-line')
6
7
  const { createVulnerability, addVulnerability } = require('../vulnerability-reporter')
7
8
  const { getIastContext } = require('../iast-context')
@@ -13,6 +14,20 @@ class Analyzer extends Plugin {
13
14
  this._type = type
14
15
  }
15
16
 
17
+ _wrapHandler (handler) {
18
+ return (message, name) => {
19
+ try {
20
+ handler(message, name)
21
+ } catch (e) {
22
+ log.debug(e)
23
+ }
24
+ }
25
+ }
26
+
27
+ addSub (channelName, handler) {
28
+ super.addSub(channelName, this._wrapHandler(handler))
29
+ }
30
+
16
31
  _isVulnerable (value, context) {
17
32
  return false
18
33
  }
@@ -25,6 +40,14 @@ class Analyzer extends Plugin {
25
40
  addVulnerability(context, vulnerability)
26
41
  }
27
42
 
43
+ _reportIfVulnerable (value, context) {
44
+ if (this._isVulnerable(value, context) && this._checkOCE(context)) {
45
+ this._report(value, context)
46
+ return true
47
+ }
48
+ return false
49
+ }
50
+
28
51
  _getEvidence (value) {
29
52
  return { value }
30
53
  }
@@ -35,8 +58,23 @@ class Analyzer extends Plugin {
35
58
 
36
59
  analyze (value) {
37
60
  const iastContext = getIastContext(storage.getStore())
38
- if (iastContext && this._isVulnerable(value, iastContext) && this._checkOCE(iastContext)) {
39
- this._report(value, iastContext)
61
+ if (!iastContext) return
62
+
63
+ this._reportIfVulnerable(value, iastContext)
64
+ }
65
+
66
+ analyzeAll (...values) {
67
+ const iastContext = getIastContext(storage.getStore())
68
+ if (!iastContext) return
69
+
70
+ for (let i = 0; i < values.length; i++) {
71
+ const value = values[i]
72
+ if (this._isVulnerable(value, iastContext)) {
73
+ if (this._checkOCE(iastContext)) {
74
+ this._report(value, iastContext)
75
+ }
76
+ break
77
+ }
40
78
  }
41
79
  }
42
80
 
@@ -1,4 +1,5 @@
1
1
  const IAST_CONTEXT_KEY = Symbol('_dd.iast.context')
2
+ const IAST_TRANSACTION_ID = Symbol('_dd.iast.transactionId')
2
3
 
3
4
  function getIastContext (store) {
4
5
  return store && store[IAST_CONTEXT_KEY]
@@ -46,5 +47,6 @@ module.exports = {
46
47
  getIastContext,
47
48
  saveIastContext,
48
49
  cleanIastContext,
49
- IAST_CONTEXT_KEY
50
+ IAST_CONTEXT_KEY,
51
+ IAST_TRANSACTION_ID
50
52
  }
@@ -7,6 +7,8 @@ const dc = require('diagnostics_channel')
7
7
  const iastContextFunctions = require('./iast-context')
8
8
  const { enableTaintTracking, disableTaintTracking, createTransaction, removeTransaction } = require('./taint-tracking')
9
9
 
10
+ const IAST_ENABLED_TAG_KEY = '_dd.iast.enabled'
11
+
10
12
  // TODO Change to `apm:http:server:request:[start|close]` when the subscription
11
13
  // order of the callbacks can be enforce
12
14
  const requestStart = dc.channel('dd-trace:incomingHttpRequestStart')
@@ -40,6 +42,11 @@ function onIncomingHttpRequestStart (data) {
40
42
  createTransaction(rootSpan.context().toSpanId(), iastContext)
41
43
  overheadController.initializeRequestContext(iastContext)
42
44
  }
45
+ if (rootSpan.addTags) {
46
+ rootSpan.addTags({
47
+ [IAST_ENABLED_TAG_KEY]: isRequestAcquired ? '1' : '0'
48
+ })
49
+ }
43
50
  }
44
51
  }
45
52
  }
@@ -1,4 +1,5 @@
1
1
  const path = require('path')
2
+ const process = require('process')
2
3
  const pathLine = {
3
4
  getFirstNonDDPathAndLine,
4
5
  getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
@@ -7,7 +8,7 @@ const pathLine = {
7
8
  }
8
9
 
9
10
  const EXCLUDED_PATHS = [
10
- '/node_modules/diagnostics_channel'
11
+ path.join(path.sep, 'node_modules', 'diagnostics_channel')
11
12
  ]
12
13
  const EXCLUDED_PATH_PREFIXES = [
13
14
  'node:diagnostics_channel',
@@ -20,7 +21,7 @@ const EXCLUDED_PATH_PREFIXES = [
20
21
 
21
22
  function calculateDDBasePath (dirname) {
22
23
  const dirSteps = dirname.split(path.sep)
23
- const packagesIndex = dirSteps.indexOf('packages')
24
+ const packagesIndex = dirSteps.lastIndexOf('packages')
24
25
  return dirSteps.slice(0, packagesIndex).join(path.sep) + path.sep
25
26
  }
26
27
 
@@ -43,10 +44,10 @@ function getFirstNonDDPathAndLineFromCallsites (callsites) {
43
44
  if (callsites) {
44
45
  for (let i = 0; i < callsites.length; i++) {
45
46
  const callsite = callsites[i]
46
- const path = callsite.getFileName()
47
- if (!isExcluded(callsite) && path.indexOf(pathLine.ddBasePath) === -1) {
47
+ const filepath = callsite.getFileName()
48
+ if (!isExcluded(callsite) && filepath.indexOf(pathLine.ddBasePath) === -1) {
48
49
  return {
49
- path,
50
+ path: path.relative(process.cwd(), filepath),
50
51
  line: callsite.getLineNumber()
51
52
  }
52
53
  }
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ const csiMethods = [
4
+ { src: 'plusOperator', operator: true },
5
+ { src: 'trim' },
6
+ { src: 'trimStart', dst: 'trim' },
7
+ { src: 'trimEnd' }
8
+ ]
9
+
10
+ module.exports = {
11
+ csiMethods
12
+ }
@@ -1,33 +1,6 @@
1
- const { storage } = require('../../../../../datadog-core')
2
- const iastContextFunctions = require('../iast-context')
3
- const log = require('../../../log')
4
1
  const TaintedUtils = require('@datadog/native-iast-taint-tracking')
5
-
6
- const IAST_TRANSACTION_ID = Symbol('_dd.iast.transactionId')
7
-
8
- function noop (res) { return res }
9
- const TaintTrackingDummy = {
10
- plusOperator: noop
11
- }
12
-
13
- const TaintTracking = {
14
- plusOperator: function (res, op1, op2) {
15
- try {
16
- if (typeof res !== 'string' ||
17
- (typeof op1 !== 'string' && typeof op2 !== 'string')) { return res }
18
-
19
- const store = storage.getStore()
20
- const iastContext = iastContextFunctions.getIastContext(store)
21
- const transactionId = iastContext && iastContext[IAST_TRANSACTION_ID]
22
- if (transactionId) {
23
- return TaintedUtils.concat(transactionId, res, op1, op2)
24
- }
25
- } catch (e) {
26
- log.debug(e)
27
- }
28
- return res
29
- }
30
- }
2
+ const { IAST_TRANSACTION_ID } = require('../iast-context')
3
+ const { TaintTracking, TaintTrackingDummy } = require('./taint-tracking-impl')
31
4
 
32
5
  function createTransaction (id, iastContext) {
33
6
  if (id && iastContext) {