dd-trace 3.10.0 → 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 (38) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +17 -1
  3. package/package.json +7 -6
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  5. package/packages/datadog-instrumentations/src/http/server.js +7 -1
  6. package/packages/datadog-instrumentations/src/ldapjs.js +91 -0
  7. package/packages/datadog-instrumentations/src/pg.js +1 -2
  8. package/packages/datadog-plugin-http/src/server.js +7 -3
  9. package/packages/datadog-plugin-router/src/index.js +6 -3
  10. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  11. package/packages/dd-trace/src/appsec/blocking.js +44 -0
  12. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +1 -1
  13. package/packages/dd-trace/src/appsec/gateway/engine/engine.js +1 -1
  14. package/packages/dd-trace/src/appsec/gateway/engine/index.js +6 -1
  15. package/packages/dd-trace/src/appsec/gateway/engine/runner.js +0 -1
  16. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -1
  17. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +11 -0
  18. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  19. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +40 -2
  20. package/packages/dd-trace/src/appsec/iast/iast-context.js +3 -1
  21. package/packages/dd-trace/src/appsec/iast/index.js +7 -0
  22. package/packages/dd-trace/src/appsec/iast/path-line.js +6 -5
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +12 -0
  24. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -29
  25. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +16 -15
  26. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +85 -0
  27. package/packages/dd-trace/src/appsec/index.js +68 -27
  28. package/packages/dd-trace/src/{plugins/util → appsec}/ip_blocklist.js +0 -0
  29. package/packages/dd-trace/src/appsec/ip_extractor.js +98 -0
  30. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -1
  31. package/packages/dd-trace/src/appsec/templates/blocked.html +99 -0
  32. package/packages/dd-trace/src/appsec/templates/blocked.json +8 -0
  33. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +4 -0
  34. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +18 -2
  35. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +12 -6
  36. package/packages/dd-trace/src/config.js +24 -2
  37. package/packages/dd-trace/src/plugin_manager.js +0 -10
  38. package/packages/dd-trace/src/plugins/util/web.js +1 -104
@@ -20,6 +20,7 @@ require,lodash.uniq,MIT,Copyright JS Foundation and other contributors
20
20
  require,lru-cache,ISC,Copyright (c) 2010-2022 Isaac Z. Schlueter and Contributors
21
21
  require,methods,MIT,Copyright 2013-2014 TJ Holowaychuk
22
22
  require,module-details-from-path,MIT,Copyright 2016 Thomas Watson Steen
23
+ require,node-abort-controller,MIT,Copyright (c) 2019 Steve Faulkner
23
24
  require,opentracing,MIT,Copyright 2016 Resonance Labs Inc
24
25
  require,path-to-regexp,MIT,Copyright 2014 Blake Embrey
25
26
  require,protobufjs,BSD-3-Clause,Copyright 2016 Daniel Wirtz
package/index.d.ts CHANGED
@@ -509,7 +509,17 @@ export declare interface TracerOptions {
509
509
  /**
510
510
  * Specifies a regex that will redact sensitive data by its value in attack reports.
511
511
  */
512
- obfuscatorValueRegex?: string
512
+ obfuscatorValueRegex?: string,
513
+
514
+ /**
515
+ * Specifies a path to a custom blocking template html file.
516
+ */
517
+ blockedTemplateHtml?: string,
518
+
519
+ /**
520
+ * Specifies a path to a custom blocking template json file.
521
+ */
522
+ blockedTemplateJson?: string,
513
523
  };
514
524
  }
515
525
 
@@ -1208,6 +1218,12 @@ declare namespace plugins {
1208
1218
  */
1209
1219
  interface kafkajs extends Instrumentation {}
1210
1220
 
1221
+ /**
1222
+ * This plugin automatically instruments the
1223
+ * [ldapjs](https://github.com/ldapjs/node-ldapjs/) module.
1224
+ */
1225
+ interface ldapjs extends Instrumentation {}
1226
+
1211
1227
  /**
1212
1228
  * This plugin automatically instruments the
1213
1229
  * [mariadb](https://github.com/mariadb-corporation/mariadb-connector-nodejs) module.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "3.10.0",
3
+ "version": "3.11.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -16,14 +16,14 @@
16
16
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
17
17
  "tdd": "node scripts/tdd.js",
18
18
  "test": "SERVICES=* yarn services && mocha --colors --exit --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'",
19
- "test:trace:core": "mocha --colors --exit --expose-gc --file packages/dd-trace/test/setup/core.js --exclude \"packages/dd-trace/test/profiling/**/*.spec.js\" \"packages/dd-trace/test/**/*.spec.js\"",
20
- "test:trace:core:ci": "nyc --no-clean --include \"packages/dd-trace/src/**/*.js\" --exclude \"packages/dd-trace/src/profiling/**/*.js\" -- npm run test:trace:core -- --reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter.json",
19
+ "test:trace:core": "mocha --colors --exit --expose-gc --file packages/dd-trace/test/setup/core.js --exclude \"packages/dd-trace/test/profiling/**/*.spec.js\" --exclude \"packages/dd-trace/test/appsec/iast/**/*.plugin.spec.js\" \"packages/dd-trace/test/**/*.spec.js\"",
20
+ "test:trace:core:ci": "nyc --no-clean --include \"packages/dd-trace/src/**/*.js\" --exclude \"packages/dd-trace/src/profiling/**/*.js\" --exclude \"packages/dd-trace/test/appsec/iast/**/*.plugin.spec.js\" -- npm run test:trace:core -- --reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter.json",
21
21
  "test:instrumentations": "mocha --colors --file 'packages/dd-trace/test/setup/core.js' 'packages/datadog-instrumentations/test/**/*.spec.js'",
22
22
  "test:instrumentations:ci": "nyc --no-clean --include 'packages/datadog-instrumentations/src/**/*.js' -- npm run test:instrumentations",
23
23
  "test:core": "mocha --colors --file packages/datadog-core/test/setup.js 'packages/datadog-core/test/**/*.spec.js'",
24
24
  "test:core:ci": "nyc --no-clean --include 'packages/datadog-core/src/**/*.js' -- npm run test:core",
25
- "test:plugins": "mocha --colors --exit --file \"packages/dd-trace/test/setup/core.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\"",
26
- "test:plugins:ci": "yarn services && nyc --no-clean --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS)).js\" --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS))/**/*.js\" --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" -- npm run test:plugins",
25
+ "test:plugins": "mocha --colors --exit --file \"packages/dd-trace/test/setup/core.js\" \"packages/datadog-instrumentations/test/@($(echo $PLUGINS)).spec.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\" \"packages/dd-trace/test/appsec/iast/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
26
+ "test:plugins:ci": "yarn services && nyc --no-clean --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS)).js\" --include \"packages/datadog-instrumentations/src/@($(echo $PLUGINS))/**/*.js\" --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" --include \"packages/dd-trace/test/appsec/iast/**/*.@($(echo $PLUGINS)).plugin.spec.js\" -- npm run test:plugins",
27
27
  "test:plugins:upstream": "node ./packages/dd-trace/test/plugins/suite.js",
28
28
  "test:profiler": "mocha --colors --exit --file \"packages/dd-trace/test/setup/core.js\" \"packages/dd-trace/test/profiling/**/*.spec.js\"",
29
29
  "test:profiler:ci": "nyc --no-clean --include \"packages/dd-trace/src/profiling/**/*.js\" -- npm run test:profiler",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@datadog/native-appsec": "2.0.0",
62
- "@datadog/native-iast-rewriter": "1.0.1",
62
+ "@datadog/native-iast-rewriter": "1.1.2",
63
63
  "@datadog/native-iast-taint-tracking": "1.0.0",
64
64
  "@datadog/native-metrics": "^1.5.0",
65
65
  "@datadog/pprof": "^1.1.1",
@@ -79,6 +79,7 @@
79
79
  "lru-cache": "^7.14.0",
80
80
  "methods": "^1.1.2",
81
81
  "module-details-from-path": "^1.0.3",
82
+ "node-abort-controller": "^3.0.1",
82
83
  "opentracing": ">=0.12.1",
83
84
  "path-to-regexp": "^0.1.2",
84
85
  "protobufjs": "^7.1.2",
@@ -46,6 +46,7 @@ module.exports = {
46
46
  'koa': () => require('../koa'),
47
47
  'koa-router': () => require('../koa'),
48
48
  'kafkajs': () => require('../kafkajs'),
49
+ 'ldapjs': () => require('../ldapjs'),
49
50
  'limitd-client': () => require('../limitd-client'),
50
51
  'mariadb': () => require('../mariadb'),
51
52
  'memcached': () => require('../memcached'),
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { AbortController } = require('node-abort-controller') // AbortController is not available in node <15
3
4
  const {
4
5
  channel,
5
6
  addHook
@@ -48,9 +49,14 @@ function wrapEmit (emit) {
48
49
  if (eventName === 'request') {
49
50
  res.req = req
50
51
 
51
- startServerCh.publish({ req, res })
52
+ const abortController = new AbortController()
53
+
54
+ startServerCh.publish({ req, res, abortController })
52
55
 
53
56
  try {
57
+ if (abortController.signal.aborted) {
58
+ return res.end()
59
+ }
54
60
  return emit.apply(this, arguments)
55
61
  } catch (err) {
56
62
  errorServerCh.publish(err)
@@ -0,0 +1,91 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook,
6
+ AsyncResource
7
+ } = require('./helpers/instrument')
8
+ const shimmer = require('../../datadog-shimmer')
9
+
10
+ function isString (value) {
11
+ return typeof value === 'string' || value instanceof String
12
+ }
13
+
14
+ function getCallbackArgIndex (args) {
15
+ let callbackIndex = -1
16
+ for (let i = args.length - 1; i >= 0; i--) {
17
+ if (typeof args[i] === 'function') {
18
+ callbackIndex = i
19
+ break
20
+ }
21
+ }
22
+ return callbackIndex
23
+ }
24
+
25
+ function wrapEmitter (corkedEmitter) {
26
+ const callbackMap = new WeakMap()
27
+
28
+ const addListener = on => function (name, fn) {
29
+ if (typeof fn === 'function') {
30
+ let bindedFn = callbackMap.get(fn)
31
+ if (!bindedFn) {
32
+ const callbackResource = new AsyncResource('bound-anonymous-fn')
33
+ bindedFn = callbackResource.bind(fn)
34
+ callbackMap.set(fn, bindedFn)
35
+ }
36
+ arguments[1] = bindedFn
37
+ }
38
+ on.apply(this, arguments)
39
+ }
40
+ shimmer.wrap(corkedEmitter, 'on', addListener)
41
+ shimmer.wrap(corkedEmitter, 'addListener', addListener)
42
+
43
+ const removeListener = off => function (name, fn) {
44
+ if (typeof fn === 'function') {
45
+ const emitterOn = callbackMap.get(fn)
46
+ if (emitterOn) {
47
+ arguments[1] = emitterOn
48
+ }
49
+ }
50
+ off.apply(this, arguments)
51
+ }
52
+ shimmer.wrap(corkedEmitter, 'off', removeListener)
53
+ shimmer.wrap(corkedEmitter, 'removeListener', removeListener)
54
+ }
55
+
56
+ addHook({ name: 'ldapjs', versions: ['>=2'] }, ldapjs => {
57
+ const ldapSearchCh = channel('datadog:ldapjs:client:search')
58
+
59
+ shimmer.wrap(ldapjs.Client.prototype, 'search', search => function (base, options) {
60
+ if (ldapSearchCh.hasSubscribers) {
61
+ let filter
62
+ if (isString(options)) {
63
+ filter = options
64
+ } else if (typeof options === 'object' && options.filter) {
65
+ if (isString(options.filter)) {
66
+ filter = options.filter
67
+ }
68
+ }
69
+ ldapSearchCh.publish({ base, filter })
70
+ }
71
+
72
+ return search.apply(this, arguments)
73
+ })
74
+
75
+ shimmer.wrap(ldapjs.Client.prototype, '_send', _send => function () {
76
+ const callbackIndex = getCallbackArgIndex(arguments)
77
+ if (callbackIndex > -1) {
78
+ const callback = arguments[callbackIndex]
79
+ arguments[callbackIndex] = shimmer.wrap(callback, function (err, corkedEmitter) {
80
+ if (typeof corkedEmitter === 'object' && typeof corkedEmitter['on'] === 'function') {
81
+ wrapEmitter(corkedEmitter)
82
+ }
83
+ callback.apply(this, arguments)
84
+ })
85
+ }
86
+
87
+ return _send.apply(this, arguments)
88
+ })
89
+
90
+ return ldapjs
91
+ })
@@ -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) {
@@ -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
 
@@ -52,6 +52,10 @@ class HttpServerPlugin extends Plugin {
52
52
 
53
53
  if (!context || !context.res) return // Not created by a http.Server instance.
54
54
 
55
+ if (incomingHttpRequestEnd.hasSubscribers) {
56
+ incomingHttpRequestEnd.publish({ req, res: context.res })
57
+ }
58
+
55
59
  web.finishAll(context)
56
60
  })
57
61
  }
@@ -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
  }
@@ -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) {