dd-trace 3.18.0 → 3.20.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/package.json +6 -6
  2. package/packages/datadog-instrumentations/src/body-parser.js +15 -9
  3. package/packages/datadog-instrumentations/src/express.js +32 -0
  4. package/packages/datadog-instrumentations/src/http/server.js +2 -1
  5. package/packages/datadog-instrumentations/src/mocha.js +8 -4
  6. package/packages/datadog-instrumentations/src/pg.js +3 -4
  7. package/packages/datadog-instrumentations/src/playwright.js +14 -1
  8. package/packages/datadog-plugin-http/src/server.js +2 -2
  9. package/packages/datadog-plugin-http2/src/server.js +0 -5
  10. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -0
  11. package/packages/datadog-plugin-pg/src/index.js +5 -5
  12. package/packages/dd-trace/src/appsec/addresses.js +0 -3
  13. package/packages/dd-trace/src/appsec/blocked_templates.js +2 -9
  14. package/packages/dd-trace/src/appsec/blocking.js +1 -1
  15. package/packages/dd-trace/src/appsec/{gateway/channels.js → channels.js} +4 -4
  16. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -1
  17. package/packages/dd-trace/src/appsec/index.js +87 -79
  18. package/packages/dd-trace/src/appsec/recommended.json +448 -121
  19. package/packages/dd-trace/src/appsec/remote_config/apply_states.js +7 -0
  20. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  21. package/packages/dd-trace/src/appsec/remote_config/index.js +29 -10
  22. package/packages/dd-trace/src/appsec/remote_config/manager.js +33 -12
  23. package/packages/dd-trace/src/appsec/reporter.js +27 -58
  24. package/packages/dd-trace/src/appsec/rule_manager.js +160 -32
  25. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +4 -12
  26. package/packages/dd-trace/src/appsec/waf/index.js +75 -0
  27. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +57 -0
  28. package/packages/dd-trace/src/appsec/waf/waf_manager.js +66 -0
  29. package/packages/dd-trace/src/encode/0.4.js +12 -4
  30. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -4
  31. package/packages/dd-trace/src/plugins/util/test.js +34 -17
  32. package/packages/dd-trace/src/telemetry/send-data.js +13 -3
  33. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +0 -137
  34. package/packages/dd-trace/src/appsec/callbacks/index.js +0 -7
  35. package/packages/dd-trace/src/appsec/gateway/als.js +0 -6
  36. package/packages/dd-trace/src/appsec/gateway/engine/engine.js +0 -140
  37. package/packages/dd-trace/src/appsec/gateway/engine/index.js +0 -51
  38. package/packages/dd-trace/src/appsec/gateway/engine/runner.js +0 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "3.18.0",
3
+ "version": "3.20.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -15,10 +15,10 @@
15
15
  "lint": "node scripts/check_licenses.js && eslint . && yarn audit --groups dependencies",
16
16
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
17
17
  "test": "SERVICES=* yarn services && mocha --colors --exit --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'",
18
- "test:appsec": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec/iast/**/*.plugin.spec.js\" \"packages/dd-trace/test/appsec/**/*.spec.js\"",
19
- "test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/iast/**/*.plugin.spec.js\" -- npm run test:appsec",
20
- "test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/iast/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
21
- "test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/test/appsec/iast/**/*.@($(echo $PLUGINS)).plugin.spec.js\" -- npm run test:appsec:plugins",
18
+ "test:appsec": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" \"packages/dd-trace/test/appsec/**/*.spec.js\"",
19
+ "test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" -- npm run test:appsec",
20
+ "test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
21
+ "test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\" -- npm run test:appsec:plugins",
22
22
  "test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentracing,plugins,telemetry}/**/*.spec.js\"",
23
23
  "test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
24
24
  "test:instrumentations": "mocha --colors -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'",
@@ -65,7 +65,7 @@
65
65
  "node": ">=14"
66
66
  },
67
67
  "dependencies": {
68
- "@datadog/native-appsec": "2.0.0",
68
+ "@datadog/native-appsec": "^3.1.0",
69
69
  "@datadog/native-iast-rewriter": "2.0.1",
70
70
  "@datadog/native-iast-taint-tracking": "^1.4.0",
71
71
  "@datadog/native-metrics": "^1.6.0",
@@ -1,14 +1,21 @@
1
1
  'use strict'
2
2
 
3
- const { channel, addHook, AsyncResource } = require('./helpers/instrument')
3
+ const { AbortController } = require('node-abort-controller') // AbortController is not available in node <15
4
+ const shimmer = require('../../datadog-shimmer')
5
+ const { channel, addHook } = require('./helpers/instrument')
4
6
 
5
7
  const bodyParserReadCh = channel('datadog:body-parser:read:finish')
6
8
 
7
- function publishRequestBodyAndNext (request, next) {
9
+ function publishRequestBodyAndNext (req, res, next) {
8
10
  return function () {
9
- if (bodyParserReadCh.hasSubscribers && request) {
10
- bodyParserReadCh.publish({ request })
11
+ if (bodyParserReadCh.hasSubscribers && req) {
12
+ const abortController = new AbortController()
13
+
14
+ bodyParserReadCh.publish({ req, res, abortController })
15
+
16
+ if (abortController.signal.aborted) return
11
17
  }
18
+
12
19
  next.apply(this, arguments)
13
20
  }
14
21
  }
@@ -16,11 +23,10 @@ function publishRequestBodyAndNext (request, next) {
16
23
  addHook({
17
24
  name: 'body-parser',
18
25
  file: 'lib/read.js',
19
- versions: ['>=1']
26
+ versions: ['>=1.4.0']
20
27
  }, read => {
21
- return function (req, res, next) {
22
- const nextResource = new AsyncResource('bound-anonymous-fn')
23
- arguments[2] = nextResource.bind(publishRequestBodyAndNext(req, next))
28
+ return shimmer.wrap(read, function (req, res, next) {
29
+ arguments[2] = publishRequestBodyAndNext(req, res, next)
24
30
  read.apply(this, arguments)
25
- }
31
+ })
26
32
  })
@@ -3,6 +3,7 @@
3
3
  const { createWrapRouterMethod } = require('./router')
4
4
  const shimmer = require('../../datadog-shimmer')
5
5
  const { addHook, channel } = require('./helpers/instrument')
6
+ const { AbortController } = require('node-abort-controller')
6
7
 
7
8
  const handleChannel = channel('apm:express:request:handle')
8
9
 
@@ -25,3 +26,34 @@ addHook({ name: 'express', versions: ['>=4'] }, express => {
25
26
 
26
27
  return express
27
28
  })
29
+
30
+ const queryParserReadCh = channel('datadog:query:read:finish')
31
+
32
+ function publishQueryParsedAndNext (req, res, next) {
33
+ return function () {
34
+ if (queryParserReadCh.hasSubscribers && req) {
35
+ const abortController = new AbortController()
36
+
37
+ queryParserReadCh.publish({ req, res, abortController })
38
+
39
+ if (abortController.signal.aborted) return
40
+ }
41
+
42
+ next.apply(this, arguments)
43
+ }
44
+ }
45
+
46
+ addHook({
47
+ name: 'express',
48
+ versions: ['>=4'],
49
+ file: 'lib/middleware/query.js'
50
+ }, query => {
51
+ return shimmer.wrap(query, function () {
52
+ const queryMiddleware = query.apply(this, arguments)
53
+
54
+ return shimmer.wrap(queryMiddleware, function (req, res, next) {
55
+ arguments[2] = publishQueryParsedAndNext(req, res, next)
56
+ return queryMiddleware.apply(this, arguments)
57
+ })
58
+ })
59
+ })
@@ -55,7 +55,8 @@ function wrapEmit (emit) {
55
55
 
56
56
  try {
57
57
  if (abortController.signal.aborted) {
58
- return res.end()
58
+ // TODO: should this always return true ?
59
+ return this.listenerCount(eventName) > 0
59
60
  }
60
61
  return emit.apply(this, arguments)
61
62
  } catch (err) {
@@ -10,7 +10,7 @@ const {
10
10
  mergeCoverage,
11
11
  getTestSuitePath,
12
12
  fromCoverageMapToCoverage,
13
- getTestLineStart
13
+ getCallSites
14
14
  } = require('../../dd-trace/src/plugins/util/test')
15
15
 
16
16
  const testStartCh = channel('ci:mocha:test:start')
@@ -393,9 +393,13 @@ addHook({
393
393
  file: 'lib/suite.js'
394
394
  }, (Suite) => {
395
395
  shimmer.wrap(Suite.prototype, 'addTest', addTest => function (test) {
396
- // In here, the test definition is in the stack, so we search for it.
397
- const startLine = getTestLineStart(new Error(), test.file)
398
- testToStartLine.set(test, startLine)
396
+ const callSites = getCallSites()
397
+ let startLine
398
+ const testCallSite = callSites.find(site => site.getFileName() === test.file)
399
+ if (testCallSite) {
400
+ startLine = testCallSite.getLineNumber()
401
+ testToStartLine.set(test, startLine)
402
+ }
399
403
  return addTest.apply(this, arguments)
400
404
  })
401
405
  return Suite
@@ -30,9 +30,8 @@ function wrapQuery (query) {
30
30
  const callbackResource = new AsyncResource('bound-anonymous-fn')
31
31
  const asyncResource = new AsyncResource('bound-anonymous-fn')
32
32
  const processId = this.processID
33
- let pgQuery = {
34
- text: arguments[0]
35
- }
33
+
34
+ let pgQuery = arguments[0] && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
36
35
 
37
36
  return asyncResource.runInAsyncScope(() => {
38
37
  startCh.publish({
@@ -41,7 +40,7 @@ function wrapQuery (query) {
41
40
  processId
42
41
  })
43
42
 
44
- arguments[0] = pgQuery.text
43
+ arguments[0] = pgQuery
45
44
 
46
45
  const finish = asyncResource.bind(function (error) {
47
46
  if (error) {
@@ -72,6 +72,9 @@ function getRootDir (playwrightRunner) {
72
72
  if (playwrightRunner._configDir) {
73
73
  return playwrightRunner._configDir
74
74
  }
75
+ if (playwrightRunner._config && playwrightRunner._config.config) {
76
+ return playwrightRunner._config.config.rootDir
77
+ }
75
78
  return process.cwd()
76
79
  }
77
80
 
@@ -211,10 +214,20 @@ function runnerHook (runnerExport, playwrightVersion) {
211
214
  })
212
215
 
213
216
  const runAllTestsReturn = await runAllTests.apply(this, arguments)
217
+
218
+ Object.values(remainingTestsByFile).forEach(tests => {
219
+ // `tests` should normally be empty, but if it isn't,
220
+ // there were tests that did not go through `testBegin` or `testEnd`,
221
+ // because they were skipped
222
+ tests.forEach(test => {
223
+ testBeginHandler(test)
224
+ testEndHandler(test, 'skip')
225
+ })
226
+ })
227
+
214
228
  const sessionStatus = runAllTestsReturn.status || runAllTestsReturn
215
229
 
216
230
  let onDone
217
-
218
231
  const flushWait = new Promise(resolve => {
219
232
  onDone = resolve
220
233
  })
@@ -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, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/gateway/channels')
6
+ const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('../../dd-trace/src/appsec/channels')
7
7
  const { COMPONENT } = require('../../dd-trace/src/constants')
8
8
 
9
9
  class HttpServerPlugin extends Plugin {
@@ -33,7 +33,7 @@ class HttpServerPlugin extends Plugin {
33
33
  }
34
34
 
35
35
  if (incomingHttpRequestStart.hasSubscribers) {
36
- incomingHttpRequestStart.publish({ req, res, abortController })
36
+ incomingHttpRequestStart.publish({ req, res, abortController }) // TODO: no need to make a new object here
37
37
  }
38
38
  })
39
39
 
@@ -5,7 +5,6 @@
5
5
  const Plugin = require('../../dd-trace/src/plugins/plugin')
6
6
  const { storage } = require('../../datadog-core')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
- const { incomingHttpRequestStart } = require('../../dd-trace/src/appsec/gateway/channels')
9
8
  const { COMPONENT } = require('../../dd-trace/src/constants')
10
9
 
11
10
  class Http2ServerPlugin extends Plugin {
@@ -30,10 +29,6 @@ class Http2ServerPlugin extends Plugin {
30
29
  context.res.writeHead = web.wrapWriteHead(context)
31
30
  context.instrumented = true
32
31
  }
33
-
34
- if (incomingHttpRequestStart.hasSubscribers) {
35
- incomingHttpRequestStart.publish({ req, res })
36
- }
37
32
  })
38
33
 
39
34
  this.addSub('apm:http2:server:request:error', (error) => {
@@ -33,6 +33,8 @@ function extract (tracer, bufferMap) {
33
33
  const textMap = {}
34
34
 
35
35
  for (const key of Object.keys(bufferMap)) {
36
+ if (bufferMap[key] === null || bufferMap[key] === undefined) continue
37
+
36
38
  textMap[key] = bufferMap[key].toString()
37
39
  }
38
40
 
@@ -9,7 +9,7 @@ class PGPlugin extends DatabasePlugin {
9
9
  static get system () { return 'postgres' }
10
10
 
11
11
  start ({ params = {}, query, processId }) {
12
- const service = getServiceName(this.config, params)
12
+ const service = getServiceName(this, params)
13
13
  const originalStatement = query.text
14
14
 
15
15
  this.startSpan('pg.query', {
@@ -31,12 +31,12 @@ class PGPlugin extends DatabasePlugin {
31
31
  }
32
32
  }
33
33
 
34
- function getServiceName (config, params) {
35
- if (typeof config.service === 'function') {
36
- return config.service(params)
34
+ function getServiceName (tracer, params) {
35
+ if (typeof tracer.config.service === 'function') {
36
+ return tracer.config.service(params)
37
37
  }
38
38
 
39
- return config.service
39
+ return tracer.config.service || `${tracer._tracer._tracer._service}-postgres`
40
40
  }
41
41
 
42
42
  module.exports = PGPlugin
@@ -7,14 +7,11 @@ module.exports = {
7
7
  // TODO: 'server.request.trailers',
8
8
  HTTP_INCOMING_URL: 'server.request.uri.raw',
9
9
  HTTP_INCOMING_METHOD: 'server.request.method',
10
- HTTP_INCOMING_ENDPOINT: 'server.request.framework_endpoint',
11
10
  HTTP_INCOMING_PARAMS: 'server.request.path_params',
12
11
  HTTP_INCOMING_COOKIES: 'server.request.cookies',
13
12
  HTTP_INCOMING_RESPONSE_CODE: 'server.response.status',
14
13
  HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
15
14
  // TODO: 'server.response.trailers',
16
- HTTP_INCOMING_REMOTE_IP: 'server.request.client_ip',
17
- HTTP_INCOMING_REMOTE_PORT: 'server.request.client_port',
18
15
 
19
16
  HTTP_CLIENT_IP: 'http.client_ip',
20
17
 
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable max-len */
2
2
  'use strict'
3
3
 
4
- const html = `<!-- Sorry, you've been blocked -->
4
+ const html = `<!-- Sorry, youve been blocked -->
5
5
  <!DOCTYPE html>
6
6
  <html lang="en">
7
7
 
@@ -102,14 +102,7 @@ const html = `<!-- Sorry, you've been blocked -->
102
102
  </html>
103
103
  `
104
104
 
105
- const json = `{
106
- "errors": [
107
- {
108
- "title": "You've been blocked",
109
- "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."
110
- }
111
- ]
112
- }`
105
+ const json = `{"errors": [{"title": "You've been blocked", "detail": "Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}`
113
106
 
114
107
  module.exports = {
115
108
  html,
@@ -19,7 +19,7 @@ function block (req, res, rootSpan, abortController) {
19
19
  const accept = req.headers.accept && req.headers.accept.split(',').map((str) => str.split(';', 1)[0].trim())
20
20
 
21
21
  if (accept && accept.includes('text/html') && !accept.includes('application/json')) {
22
- type = 'text/html'
22
+ type = 'text/html; charset=utf-8'
23
23
  body = templateHtml
24
24
  } else {
25
25
  type = 'application/json'
@@ -1,11 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('../../../../diagnostics_channel')
3
+ const dc = require('../../../diagnostics_channel')
4
4
 
5
5
  // TODO: use TBD naming convention
6
- // or directly use http plugin's channels
7
- // when it gets converted to new plugin system
8
6
  module.exports = {
9
7
  incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
10
- incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd')
8
+ incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
9
+ bodyParser: dc.channel('datadog:body-parser:read:finish'),
10
+ queryParser: dc.channel('datadog:query:read:finish')
11
11
  }
@@ -12,7 +12,7 @@ class TaintTrackingPlugin extends Plugin {
12
12
  this._type = 'taint-tracking'
13
13
  this.addSub(
14
14
  'datadog:body-parser:read:finish',
15
- ({ request }) => this._taintTrackingHandler(HTTP_REQUEST_BODY, request, 'body')
15
+ ({ req }) => this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body')
16
16
  )
17
17
  this.addSub(
18
18
  'datadog:qs:parse:finish',
@@ -3,8 +3,13 @@
3
3
  const log = require('../log')
4
4
  const RuleManager = require('./rule_manager')
5
5
  const remoteConfig = require('./remote_config')
6
- const { incomingHttpRequestStart, incomingHttpRequestEnd } = require('./gateway/channels')
7
- const Gateway = require('./gateway/engine')
6
+ const {
7
+ incomingHttpRequestStart,
8
+ incomingHttpRequestEnd,
9
+ bodyParser,
10
+ queryParser
11
+ } = require('./channels')
12
+ const waf = require('./waf')
8
13
  const addresses = require('./addresses')
9
14
  const Reporter = require('./reporter')
10
15
  const web = require('../plugins/util/web')
@@ -21,39 +26,25 @@ function enable (_config) {
21
26
  try {
22
27
  setTemplates(_config)
23
28
 
24
- // TODO: inline this function
25
- enableFromRules(_config, _config.appsec.rules)
26
- } catch (err) {
27
- abortEnable(err)
28
- }
29
- }
29
+ RuleManager.applyRules(_config.appsec.rules, _config.appsec)
30
30
 
31
- function enableFromRules (_config, rules) {
32
- RuleManager.applyRules(rules, _config.appsec)
33
- remoteConfig.enableAsmData(_config.appsec)
31
+ remoteConfig.enableWafUpdate(_config.appsec)
34
32
 
35
- Reporter.setRateLimit(_config.appsec.rateLimit)
33
+ Reporter.setRateLimit(_config.appsec.rateLimit)
36
34
 
37
- incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
38
- incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
39
-
40
- // add fields needed for HTTP context reporting
41
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_HEADERS)
42
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_ENDPOINT)
43
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_RESPONSE_HEADERS)
44
- Gateway.manager.addresses.add(addresses.HTTP_INCOMING_REMOTE_IP)
45
-
46
- isEnabled = true
47
- config = _config
48
- }
35
+ incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
36
+ incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
37
+ bodyParser.subscribe(onRequestBodyParsed)
38
+ queryParser.subscribe(onRequestQueryParsed)
49
39
 
50
- function abortEnable (err) {
51
- log.error('Unable to start AppSec')
52
- log.error(err)
40
+ isEnabled = true
41
+ config = _config
42
+ } catch (err) {
43
+ log.error('Unable to start AppSec')
44
+ log.error(err)
53
45
 
54
- // abort AppSec start
55
- RuleManager.clearAllRules()
56
- remoteConfig.disableAsmData()
46
+ disable()
47
+ }
57
48
  }
58
49
 
59
50
  function incomingHttpStartTranslator ({ req, res, abortController }) {
@@ -68,78 +59,92 @@ function incomingHttpStartTranslator ({ req, res, abortController }) {
68
59
  [HTTP_CLIENT_IP]: clientIp
69
60
  })
70
61
 
71
- const store = Gateway.startContext()
72
-
73
- store.set('req', req)
74
- store.set('res', res)
62
+ const requestHeaders = Object.assign({}, req.headers)
63
+ delete requestHeaders.cookie
75
64
 
76
- const context = store.get('context')
65
+ const payload = {
66
+ [addresses.HTTP_INCOMING_URL]: req.url,
67
+ [addresses.HTTP_INCOMING_HEADERS]: requestHeaders,
68
+ [addresses.HTTP_INCOMING_METHOD]: req.method
69
+ }
77
70
 
78
71
  if (clientIp) {
79
- const results = Gateway.propagate({
80
- [addresses.HTTP_CLIENT_IP]: clientIp
81
- }, context)
82
-
83
- if (!results || !abortController) return
84
-
85
- for (const entry of results) {
86
- if (entry && entry.includes('block')) {
87
- block(req, res, rootSpan, abortController)
88
- break
89
- }
90
- }
72
+ payload[addresses.HTTP_CLIENT_IP] = clientIp
91
73
  }
92
- }
93
74
 
94
- function incomingHttpEndTranslator (data) {
95
- const context = Gateway.getContext()
96
- if (!context) return
75
+ const actions = waf.run(payload, req)
97
76
 
98
- const requestHeaders = Object.assign({}, data.req.headers)
99
- delete requestHeaders.cookie
77
+ handleResults(actions, req, res, rootSpan, abortController)
78
+ }
100
79
 
80
+ function incomingHttpEndTranslator ({ req, res }) {
101
81
  // TODO: this doesn't support headers sent with res.writeHead()
102
- const responseHeaders = Object.assign({}, data.res.getHeaders())
82
+ const responseHeaders = Object.assign({}, res.getHeaders())
103
83
  delete responseHeaders['set-cookie']
104
84
 
105
85
  const payload = {
106
- [addresses.HTTP_INCOMING_URL]: data.req.url,
107
- [addresses.HTTP_INCOMING_HEADERS]: requestHeaders,
108
- [addresses.HTTP_INCOMING_METHOD]: data.req.method,
109
- [addresses.HTTP_INCOMING_REMOTE_IP]: data.req.socket.remoteAddress,
110
- [addresses.HTTP_INCOMING_REMOTE_PORT]: data.req.socket.remotePort,
111
- [addresses.HTTP_INCOMING_RESPONSE_CODE]: data.res.statusCode,
86
+ [addresses.HTTP_INCOMING_RESPONSE_CODE]: res.statusCode,
112
87
  [addresses.HTTP_INCOMING_RESPONSE_HEADERS]: responseHeaders
113
88
  }
114
89
 
115
- // TODO: temporary express instrumentation, will use express plugin later
116
- if (data.req.body !== undefined && data.req.body !== null) {
117
- payload[addresses.HTTP_INCOMING_BODY] = data.req.body
118
- }
119
-
120
- if (data.req.query && typeof data.req.query === 'object') {
121
- payload[addresses.HTTP_INCOMING_QUERY] = data.req.query
90
+ // we need to keep this to support other body parsers
91
+ // TODO: no need to analyze it if it was already done by the body-parser hook
92
+ if (req.body !== undefined && req.body !== null) {
93
+ payload[addresses.HTTP_INCOMING_BODY] = req.body
122
94
  }
123
95
 
124
- if (data.req.route && typeof data.req.route.path === 'string') {
125
- payload[addresses.HTTP_INCOMING_ENDPOINT] = data.req.route.path
126
- }
127
-
128
- if (data.req.params && typeof data.req.params === 'object') {
129
- payload[addresses.HTTP_INCOMING_PARAMS] = data.req.params
96
+ // TODO: temporary express instrumentation, will use express plugin later
97
+ if (req.params && typeof req.params === 'object') {
98
+ payload[addresses.HTTP_INCOMING_PARAMS] = req.params
130
99
  }
131
100
 
132
- if (data.req.cookies && typeof data.req.cookies === 'object') {
101
+ if (req.cookies && typeof req.cookies === 'object') {
133
102
  payload[addresses.HTTP_INCOMING_COOKIES] = {}
134
103
 
135
- for (const k of Object.keys(data.req.cookies)) {
136
- payload[addresses.HTTP_INCOMING_COOKIES][k] = [data.req.cookies[k]]
104
+ for (const k of Object.keys(req.cookies)) {
105
+ payload[addresses.HTTP_INCOMING_COOKIES][k] = [req.cookies[k]]
137
106
  }
138
107
  }
139
108
 
140
- Gateway.propagate(payload, context)
109
+ waf.run(payload, req)
110
+
111
+ waf.disposeContext(req)
112
+
113
+ Reporter.finishRequest(req, res)
114
+ }
115
+
116
+ function onRequestBodyParsed ({ req, res, abortController }) {
117
+ const rootSpan = web.root(req)
118
+ if (!rootSpan) return
119
+
120
+ if (req.body === undefined || req.body === null) return
121
+
122
+ const results = waf.run({
123
+ [addresses.HTTP_INCOMING_BODY]: req.body
124
+ }, req)
141
125
 
142
- Reporter.finishRequest(data.req, context)
126
+ handleResults(results, req, res, rootSpan, abortController)
127
+ }
128
+
129
+ function onRequestQueryParsed ({ req, res, abortController }) {
130
+ const rootSpan = web.root(req)
131
+ if (!rootSpan) return
132
+
133
+ if (!req.query || typeof req.query !== 'object') return
134
+
135
+ const results = waf.run({
136
+ [addresses.HTTP_INCOMING_QUERY]: req.query
137
+ }, req)
138
+
139
+ handleResults(results, req, res, rootSpan, abortController)
140
+ }
141
+
142
+ function handleResults (actions, req, res, rootSpan, abortController) {
143
+ if (!actions || !req || !res || !rootSpan || !abortController) return
144
+
145
+ if (actions.includes('block')) {
146
+ block(req, res, rootSpan, abortController)
147
+ }
143
148
  }
144
149
 
145
150
  function disable () {
@@ -147,11 +152,14 @@ function disable () {
147
152
  config = null
148
153
 
149
154
  RuleManager.clearAllRules()
150
- remoteConfig.disableAsmData()
155
+
156
+ remoteConfig.disableWafUpdate()
151
157
 
152
158
  // Channel#unsubscribe() is undefined for non active channels
153
159
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
154
160
  if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
161
+ if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
162
+ if (queryParser.hasSubscribers) queryParser.unsubscribe(onRequestQueryParsed)
155
163
  }
156
164
 
157
165
  module.exports = {