dd-trace 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -428,7 +428,23 @@ export declare interface TracerOptions {
428
428
  * Controls the maximum amount of traces sampled by AppSec attacks, per second.
429
429
  * @default 100
430
430
  */
431
- rateLimit?: number
431
+ rateLimit?: number,
432
+
433
+ /**
434
+ * Controls the maximum amount of time in microseconds the WAF is allowed to run synchronously for.
435
+ * @default 5000
436
+ */
437
+ wafTimeout?: number,
438
+
439
+ /**
440
+ * Specifies a regex that will redact sensitive data by its key in attack reports.
441
+ */
442
+ obfuscatorKeyRegex?: string,
443
+
444
+ /**
445
+ * Specifies a regex that will redact sensitive data by its value in attack reports.
446
+ */
447
+ obfuscatorValueRegex?: string
432
448
  };
433
449
  }
434
450
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -61,9 +61,9 @@
61
61
  "node": ">=12"
62
62
  },
63
63
  "dependencies": {
64
- "@datadog/native-appsec": "^1.0.1",
65
- "@datadog/native-metrics": "^1.1.0",
66
- "@datadog/pprof": "^0.3.0",
64
+ "@datadog/native-appsec": "^1.1.1",
65
+ "@datadog/native-metrics": "^1.2.0",
66
+ "@datadog/pprof": "^0.4.0",
67
67
  "@datadog/sketches-js": "^1.0.4",
68
68
  "@types/node": ">=12",
69
69
  "crypto-randomuuid": "^1.0.0",
@@ -12,6 +12,7 @@ require('./src/elasticsearch')
12
12
  require('./src/generic-pool')
13
13
  require('./src/http')
14
14
  require('./src/ioredis')
15
+ require('./src/jest')
15
16
  require('./src/memcached')
16
17
  require('./src/mongodb-core')
17
18
  require('./src/mongoose')
@@ -0,0 +1,170 @@
1
+ 'use strict'
2
+
3
+ const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
+ const shimmer = require('../../datadog-shimmer')
5
+
6
+ const testStartCh = channel('ci:jest:test:start')
7
+ const testSkippedCh = channel('ci:jest:test:skip')
8
+ const testRunEndCh = channel('ci:jest:test:end')
9
+ const testErrCh = channel('ci:jest:test:err')
10
+ const testSuiteEnd = channel('ci:jest:test-suite:end')
11
+
12
+ const {
13
+ getTestSuitePath,
14
+ getTestParametersString
15
+ } = require('../../dd-trace/src/plugins/util/test')
16
+
17
+ const { getFormattedJestTestParameters } = require('../../datadog-plugin-jest/src/util')
18
+
19
+ const specStatusToTestStatus = {
20
+ 'pending': 'skip',
21
+ 'disabled': 'skip',
22
+ 'todo': 'skip',
23
+ 'passed': 'pass',
24
+ 'failed': 'fail'
25
+ }
26
+
27
+ const asyncResources = new WeakMap()
28
+ const originalTestFns = new WeakMap()
29
+
30
+ // based on https://github.com/facebook/jest/blob/main/packages/jest-circus/src/formatNodeAssertErrors.ts#L41
31
+ function formatJestError (errors) {
32
+ let error
33
+ if (Array.isArray(errors)) {
34
+ const [originalError, asyncError] = errors
35
+ if (originalError === null || !originalError.stack) {
36
+ error = asyncError
37
+ error.message = originalError
38
+ } else {
39
+ error = originalError
40
+ }
41
+ } else {
42
+ error = errors
43
+ }
44
+ return error
45
+ }
46
+
47
+ function getWrappedEnvironment (BaseEnvironment) {
48
+ return class DatadogEnvironment extends BaseEnvironment {
49
+ constructor (config, context) {
50
+ super(config, context)
51
+ this.testSuite = getTestSuitePath(context.testPath, config.rootDir)
52
+ this.nameToParams = {}
53
+ this.global._ddtrace = global._ddtrace
54
+ }
55
+ async teardown () {
56
+ super.teardown().finally(() => {
57
+ testSuiteEnd.publish()
58
+ })
59
+ }
60
+
61
+ async handleTestEvent (event, state) {
62
+ if (super.handleTestEvent) {
63
+ await super.handleTestEvent(event, state)
64
+ }
65
+
66
+ let context
67
+ if (this.getVmContext) {
68
+ context = this.getVmContext()
69
+ } else {
70
+ context = this.context
71
+ }
72
+
73
+ const setNameToParams = (name, params) => { this.nameToParams[name] = params }
74
+
75
+ if (event.name === 'setup') {
76
+ shimmer.wrap(this.global.test, 'each', each => function () {
77
+ const testParameters = getFormattedJestTestParameters(arguments)
78
+ const eachBind = each.apply(this, arguments)
79
+ return function () {
80
+ const [testName] = arguments
81
+ setNameToParams(testName, testParameters)
82
+ return eachBind.apply(this, arguments)
83
+ }
84
+ })
85
+ }
86
+ if (event.name === 'test_start') {
87
+ const testParameters = getTestParametersString(this.nameToParams, event.test.name)
88
+
89
+ // Async resource for this test is created here
90
+ // It is used later on by the test_done handler
91
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
92
+ asyncResources.set(event.test, asyncResource)
93
+ asyncResource.runInAsyncScope(() => {
94
+ testStartCh.publish({
95
+ name: context.expect.getState().currentTestName,
96
+ suite: this.testSuite,
97
+ runner: 'jest-circus',
98
+ testParameters
99
+ })
100
+ originalTestFns.set(event.test, event.test.fn)
101
+ event.test.fn = asyncResource.bind(event.test.fn)
102
+ })
103
+ }
104
+ if (event.name === 'test_done') {
105
+ const asyncResource = asyncResources.get(event.test)
106
+ asyncResource.runInAsyncScope(() => {
107
+ let status = 'pass'
108
+ if (event.test.errors && event.test.errors.length) {
109
+ status = 'fail'
110
+ const formattedError = formatJestError(event.test.errors[0])
111
+ testErrCh.publish(formattedError)
112
+ }
113
+ testRunEndCh.publish(status)
114
+ // restore in case it is retried
115
+ event.test.fn = originalTestFns.get(event.test)
116
+ })
117
+ }
118
+ if (event.name === 'test_skip' || event.name === 'test_todo') {
119
+ testSkippedCh.publish({
120
+ name: context.expect.getState().currentTestName,
121
+ suite: this.testSuite,
122
+ runner: 'jest-circus'
123
+ })
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ addHook({
130
+ name: 'jest-environment-node',
131
+ versions: ['>=24.8.0']
132
+ }, getWrappedEnvironment)
133
+
134
+ addHook({
135
+ name: 'jest-environment-jsdom',
136
+ versions: ['>=24.8.0']
137
+ }, getWrappedEnvironment)
138
+
139
+ addHook({
140
+ name: 'jest-jasmine2',
141
+ versions: ['>=24.8.0'],
142
+ file: 'build/jasmineAsyncInstall.js'
143
+ }, (jasmineAsyncInstallExport) => {
144
+ return function (globalConfig, globalInput) {
145
+ globalInput._ddtrace = global._ddtrace
146
+ shimmer.wrap(globalInput.jasmine.Spec.prototype, 'execute', execute => function (onComplete) {
147
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
148
+ asyncResource.runInAsyncScope(() => {
149
+ const testSuite = getTestSuitePath(this.result.testPath, globalConfig.rootDir)
150
+ testStartCh.publish({
151
+ name: this.getFullName(),
152
+ suite: testSuite,
153
+ runner: 'jest-jasmine2'
154
+ })
155
+ const spec = this
156
+ const callback = asyncResource.bind(function () {
157
+ if (spec.result.failedExpectations && spec.result.failedExpectations.length) {
158
+ const formattedError = formatJestError(spec.result.failedExpectations[0].error)
159
+ testErrCh.publish(formattedError)
160
+ }
161
+ testRunEndCh.publish(specStatusToTestStatus[spec.result.status])
162
+ onComplete.apply(this, arguments)
163
+ })
164
+ arguments[0] = callback
165
+ execute.apply(this, arguments)
166
+ })
167
+ })
168
+ return jasmineAsyncInstallExport.default(globalConfig, globalInput)
169
+ }
170
+ })
@@ -5,11 +5,7 @@ const { storage } = require('../../datadog-core')
5
5
 
6
6
  const {
7
7
  CI_APP_ORIGIN,
8
- TEST_TYPE,
9
- TEST_NAME,
10
- TEST_SUITE,
11
8
  TEST_SKIP_REASON,
12
- TEST_FRAMEWORK_VERSION,
13
9
  ERROR_MESSAGE,
14
10
  TEST_STATUS,
15
11
  TEST_CODE_OWNERS,
@@ -17,11 +13,10 @@ const {
17
13
  getTestEnvironmentMetadata,
18
14
  getTestSuitePath,
19
15
  getCodeOwnersFileEntries,
20
- getCodeOwnersForFilename
16
+ getCodeOwnersForFilename,
17
+ getTestCommonTags
21
18
  } = require('../../dd-trace/src/plugins/util/test')
22
- const { SPAN_TYPE, RESOURCE_NAME, SAMPLING_PRIORITY } = require('../../../ext/tags')
23
- const { SAMPLING_RULE_DECISION } = require('../../dd-trace/src/constants')
24
- const { AUTO_KEEP } = require('../../../ext/priority')
19
+ const { RESOURCE_NAME } = require('../../../ext/tags')
25
20
 
26
21
  class CucumberPlugin extends Plugin {
27
22
  static get name () {
@@ -40,26 +35,19 @@ class CucumberPlugin extends Plugin {
40
35
  const childOf = store ? store.span : store
41
36
  const testSuite = getTestSuitePath(pickleUri, sourceRoot)
42
37
 
43
- const testSpanMetadata = {
44
- [SPAN_TYPE]: 'test',
45
- [RESOURCE_NAME]: pickleName,
46
- [TEST_TYPE]: 'test',
47
- [TEST_NAME]: pickleName,
48
- [TEST_SUITE]: testSuite,
49
- [SAMPLING_RULE_DECISION]: 1,
50
- [SAMPLING_PRIORITY]: AUTO_KEEP,
51
- [TEST_FRAMEWORK_VERSION]: this.tracer._version,
52
- ...testEnvironmentMetadata
53
- }
38
+ const commonTags = getTestCommonTags(pickleName, testSuite, this.tracer._version)
54
39
 
55
40
  const codeOwners = getCodeOwnersForFilename(testSuite, codeOwnersEntries)
56
41
  if (codeOwners) {
57
- testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
42
+ commonTags[TEST_CODE_OWNERS] = codeOwners
58
43
  }
59
44
 
60
45
  const span = this.tracer.startSpan('cucumber.test', {
61
46
  childOf,
62
- tags: testSpanMetadata
47
+ tags: {
48
+ ...commonTags,
49
+ ...testEnvironmentMetadata
50
+ }
63
51
  })
64
52
 
65
53
  span.context()._trace.origin = CI_APP_ORIGIN
@@ -1,21 +1,16 @@
1
1
  const {
2
- TEST_TYPE,
3
- TEST_NAME,
4
- TEST_SUITE,
5
2
  TEST_STATUS,
6
- TEST_FRAMEWORK_VERSION,
7
3
  TEST_IS_RUM_ACTIVE,
8
4
  TEST_CODE_OWNERS,
9
5
  getTestEnvironmentMetadata,
10
6
  CI_APP_ORIGIN,
11
7
  getTestParentSpan,
12
8
  getCodeOwnersFileEntries,
13
- getCodeOwnersForFilename
9
+ getCodeOwnersForFilename,
10
+ getTestCommonTags
14
11
  } = require('../../dd-trace/src/plugins/util/test')
15
12
 
16
- const { SAMPLING_RULE_DECISION, ORIGIN_KEY } = require('../../dd-trace/src/constants')
17
- const { SAMPLING_PRIORITY, SPAN_TYPE, RESOURCE_NAME } = require('../../../ext/tags')
18
- const { AUTO_KEEP } = require('../../../ext/priority')
13
+ const { ORIGIN_KEY } = require('../../dd-trace/src/constants')
19
14
 
20
15
  const CYPRESS_STATUS_TO_TEST_STATUS = {
21
16
  passed: 'pass',
@@ -27,15 +22,11 @@ const CYPRESS_STATUS_TO_TEST_STATUS = {
27
22
  function getTestSpanMetadata (tracer, testName, testSuite, cypressConfig) {
28
23
  const childOf = getTestParentSpan(tracer)
29
24
 
25
+ const commonTags = getTestCommonTags(testName, testSuite, cypressConfig.version)
26
+
30
27
  return {
31
28
  childOf,
32
- resource: `${testSuite}.${testName}`,
33
- [TEST_TYPE]: 'test',
34
- [TEST_NAME]: testName,
35
- [TEST_SUITE]: testSuite,
36
- [SAMPLING_RULE_DECISION]: 1,
37
- [SAMPLING_PRIORITY]: AUTO_KEEP,
38
- [TEST_FRAMEWORK_VERSION]: cypressConfig.version
29
+ ...commonTags
39
30
  }
40
31
  }
41
32
 
@@ -71,8 +62,6 @@ module.exports = (on, config) => {
71
62
  activeSpan = tracer.startSpan('cypress.test', {
72
63
  childOf,
73
64
  tags: {
74
- [SPAN_TYPE]: 'test',
75
- [RESOURCE_NAME]: resource,
76
65
  [ORIGIN_KEY]: CI_APP_ORIGIN,
77
66
  ...testSpanMetadata,
78
67
  ...testEnvironmentMetadata
@@ -60,6 +60,7 @@ function createWrapCreateReadStream (config, tracer) {
60
60
  const tags = makeFSFlagTags('ReadStream', path, options, 'r', config, tracer)
61
61
  return tracer.trace('fs.operation', { tags, orphanable }, (span, done) => {
62
62
  const stream = createReadStream.apply(this, arguments)
63
+ stream.once('close', done)
63
64
  stream.once('end', done)
64
65
  stream.once('error', done)
65
66
  return stream
@@ -74,6 +75,7 @@ function createWrapCreateWriteStream (config, tracer) {
74
75
  const tags = makeFSFlagTags('WriteStream', path, options, 'w', config, tracer)
75
76
  return tracer.trace('fs.operation', { tags, orphanable }, (span, done) => {
76
77
  const stream = createWriteStream.apply(this, arguments)
78
+ stream.once('close', done)
77
79
  stream.once('finish', done)
78
80
  stream.once('error', done)
79
81
  return stream
@@ -4,9 +4,6 @@ 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
6
  const { incomingHttpRequestStart } = require('../../dd-trace/src/appsec/gateway/channels')
7
- const tags = require('../../../ext/tags')
8
- const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
9
- const SERVICE_NAME = tags.SERVICE_NAME
10
7
 
11
8
  class HttpServerPlugin extends Plugin {
12
9
  static get name () {
@@ -20,11 +17,6 @@ class HttpServerPlugin extends Plugin {
20
17
  const store = storage.getStore()
21
18
  const span = web.startSpan(this.tracer, this.config, req, res, 'http.request')
22
19
 
23
- if (this.config.service) {
24
- span.setTag(SERVICE_NAME, this.config.service)
25
- }
26
-
27
- analyticsSampler.sample(span, this.config.measured, true)
28
20
  this.enter(span, store)
29
21
 
30
22
  const context = web.getContext(req)
@@ -1,4 +1,102 @@
1
- const jestEnvironment = require('./jest-environment')
2
- const jestJasmine2 = require('./jest-jasmine2')
1
+ const Plugin = require('../../dd-trace/src/plugins/plugin')
2
+ const { storage } = require('../../datadog-core')
3
3
 
4
- module.exports = [].concat(jestEnvironment, jestJasmine2)
4
+ const {
5
+ CI_APP_ORIGIN,
6
+ TEST_STATUS,
7
+ JEST_TEST_RUNNER,
8
+ finishAllTraceSpans,
9
+ getTestEnvironmentMetadata,
10
+ getTestParentSpan,
11
+ getTestCommonTags,
12
+ TEST_PARAMETERS,
13
+ getCodeOwnersFileEntries,
14
+ getCodeOwnersForFilename,
15
+ TEST_CODE_OWNERS
16
+ } = require('../../dd-trace/src/plugins/util/test')
17
+
18
+ function getTestSpanMetadata (tracer, test) {
19
+ const childOf = getTestParentSpan(tracer)
20
+
21
+ const { suite, name, runner, testParameters } = test
22
+
23
+ const commonTags = getTestCommonTags(name, suite, tracer._version)
24
+
25
+ return {
26
+ childOf,
27
+ ...commonTags,
28
+ [JEST_TEST_RUNNER]: runner,
29
+ [TEST_PARAMETERS]: testParameters
30
+ }
31
+ }
32
+
33
+ class JestPlugin extends Plugin {
34
+ static get name () {
35
+ return 'jest'
36
+ }
37
+
38
+ constructor (...args) {
39
+ super(...args)
40
+
41
+ this.testEnvironmentMetadata = getTestEnvironmentMetadata('jest', this.config)
42
+ this.codeOwnersEntries = getCodeOwnersFileEntries()
43
+
44
+ this.addSub('ci:jest:test:start', (test) => {
45
+ const store = storage.getStore()
46
+ const span = this.startTestSpan(test)
47
+
48
+ this.enter(span, store)
49
+ })
50
+
51
+ this.addSub('ci:jest:test:end', (status) => {
52
+ const span = storage.getStore().span
53
+ span.setTag(TEST_STATUS, status)
54
+ span.finish()
55
+ finishAllTraceSpans(span)
56
+ this.exit()
57
+ })
58
+
59
+ this.addSub('ci:jest:test-suite:end', () => {
60
+ this.tracer._exporter._writer.flush()
61
+ })
62
+
63
+ this.addSub('ci:jest:test:err', (error) => {
64
+ if (error) {
65
+ const span = storage.getStore().span
66
+ span.setTag(TEST_STATUS, 'fail')
67
+ span.setTag('error', error)
68
+ }
69
+ })
70
+
71
+ this.addSub('ci:jest:test:skip', (test) => {
72
+ const span = this.startTestSpan(test)
73
+ span.setTag(TEST_STATUS, 'skip')
74
+ span.finish()
75
+ })
76
+ }
77
+
78
+ startTestSpan (test) {
79
+ const { childOf, ...testSpanMetadata } = getTestSpanMetadata(this.tracer, test)
80
+
81
+ const codeOwners = getCodeOwnersForFilename(test.suite, this.codeOwnersEntries)
82
+
83
+ if (codeOwners) {
84
+ testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
85
+ }
86
+
87
+ const testSpan = this.tracer
88
+ .startSpan('jest.test', {
89
+ childOf,
90
+ tags: {
91
+ ...this.testEnvironmentMetadata,
92
+ ...testSpanMetadata
93
+ }
94
+ })
95
+
96
+ testSpan.context()._trace.origin = CI_APP_ORIGIN
97
+
98
+ return testSpan
99
+ }
100
+ }
101
+
102
+ module.exports = JestPlugin
@@ -1,8 +1,3 @@
1
- const { SAMPLING_RULE_DECISION } = require('../../dd-trace/src/constants')
2
- const { SAMPLING_PRIORITY, SPAN_TYPE } = require('../../../ext/tags')
3
- const { AUTO_KEEP } = require('../../../ext/priority')
4
- const { TEST_TYPE, TEST_STATUS, getTestParentSpan } = require('../../dd-trace/src/plugins/util/test')
5
-
6
1
  /**
7
2
  * There are two ways to call `test.each` in `jest`:
8
3
  * 1. With an array of arrays: https://jestjs.io/docs/api#1-testeachtablename-fn-timeout
@@ -38,27 +33,4 @@ function getFormattedJestTestParameters (testParameters) {
38
33
  return formattedParameters
39
34
  }
40
35
 
41
- function getTestSpanTags (tracer, testEnvironmentMetadata) {
42
- const childOf = getTestParentSpan(tracer)
43
-
44
- const commonSpanTags = {
45
- [TEST_TYPE]: 'test',
46
- [SAMPLING_RULE_DECISION]: 1,
47
- [SAMPLING_PRIORITY]: AUTO_KEEP,
48
- [SPAN_TYPE]: 'test',
49
- ...testEnvironmentMetadata
50
- }
51
- return {
52
- childOf,
53
- commonSpanTags
54
- }
55
- }
56
-
57
- function setSuppressedErrors (suppressedErrors, testSpan) {
58
- if (suppressedErrors && suppressedErrors.length) {
59
- testSpan.setTag('error', suppressedErrors[0])
60
- testSpan.setTag(TEST_STATUS, 'fail')
61
- }
62
- }
63
-
64
- module.exports = { getFormattedJestTestParameters, getTestSpanTags, setSuppressedErrors }
36
+ module.exports = { getFormattedJestTestParameters }
@@ -6,10 +6,7 @@ const { storage } = require('../../datadog-core')
6
6
  const {
7
7
  CI_APP_ORIGIN,
8
8
  TEST_CODE_OWNERS,
9
- TEST_TYPE,
10
- TEST_NAME,
11
9
  TEST_SUITE,
12
- TEST_FRAMEWORK_VERSION,
13
10
  TEST_STATUS,
14
11
  TEST_PARAMETERS,
15
12
  finishAllTraceSpans,
@@ -18,11 +15,9 @@ const {
18
15
  getTestParentSpan,
19
16
  getTestParametersString,
20
17
  getCodeOwnersFileEntries,
21
- getCodeOwnersForFilename
18
+ getCodeOwnersForFilename,
19
+ getTestCommonTags
22
20
  } = require('../../dd-trace/src/plugins/util/test')
23
- const { SPAN_TYPE, RESOURCE_NAME, SAMPLING_PRIORITY } = require('../../../ext/tags')
24
- const { SAMPLING_RULE_DECISION } = require('../../dd-trace/src/constants')
25
- const { AUTO_KEEP } = require('../../../ext/priority')
26
21
 
27
22
  const skippedTests = new WeakSet()
28
23
 
@@ -33,16 +28,11 @@ function getTestSpanMetadata (tracer, test, sourceRoot) {
33
28
  const fullTestName = test.fullTitle()
34
29
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, sourceRoot)
35
30
 
31
+ const commonTags = getTestCommonTags(fullTestName, testSuite, tracer._version)
32
+
36
33
  return {
37
34
  childOf,
38
- [SPAN_TYPE]: 'test',
39
- [TEST_TYPE]: 'test',
40
- [TEST_NAME]: fullTestName,
41
- [TEST_SUITE]: testSuite,
42
- [SAMPLING_RULE_DECISION]: 1,
43
- [SAMPLING_PRIORITY]: AUTO_KEEP,
44
- [TEST_FRAMEWORK_VERSION]: tracer._version,
45
- [RESOURCE_NAME]: `${testSuite}.${fullTestName}`
35
+ ...commonTags
46
36
  }
47
37
  }
48
38
 
@@ -1 +1 @@
1
- module.exports = '2.5.0'
1
+ module.exports = '2.6.0'
@@ -7,16 +7,14 @@ const Reporter = require('../reporter')
7
7
 
8
8
  const validAddressSet = new Set(Object.values(addresses))
9
9
 
10
- const DEFAULT_MAX_BUDGET = 5e3 // µs
11
-
12
10
  // TODO: put reusable code in a base class
13
11
  class WAFCallback {
14
- static loadDDWAF (rules) {
12
+ static loadDDWAF (rules, config) {
15
13
  try {
16
14
  // require in `try/catch` because this can throw at require time
17
15
  const { DDWAF } = require('@datadog/native-appsec')
18
16
 
19
- return new DDWAF(rules)
17
+ return new DDWAF(rules, config)
20
18
  } catch (err) {
21
19
  log.error('AppSec could not load native package. In-app WAF features will not be available.')
22
20
 
@@ -24,8 +22,24 @@ class WAFCallback {
24
22
  }
25
23
  }
26
24
 
27
- constructor (rules) {
28
- this.ddwaf = WAFCallback.loadDDWAF(rules)
25
+ constructor (rules, config) {
26
+ const { wafTimeout, obfuscatorKeyRegex, obfuscatorValueRegex } = config
27
+
28
+ this.ddwaf = WAFCallback.loadDDWAF(rules, { obfuscatorKeyRegex, obfuscatorValueRegex })
29
+
30
+ this.wafTimeout = wafTimeout
31
+
32
+ const version = this.ddwaf.constructor.version()
33
+
34
+ Reporter.metricsQueue.set('_dd.appsec.waf.version', `${version.major}.${version.minor}.${version.patch}`)
35
+
36
+ const { loaded, failed } = this.ddwaf.rulesInfo
37
+
38
+ Reporter.metricsQueue.set('_dd.appsec.event_rules.loaded', loaded)
39
+ Reporter.metricsQueue.set('_dd.appsec.event_rules.error_count', failed)
40
+
41
+ Reporter.metricsQueue.set('manual.keep', true)
42
+
29
43
  this.wafContextCache = new WeakMap()
30
44
 
31
45
  // closures are faster than binds
@@ -81,7 +95,7 @@ class WAFCallback {
81
95
 
82
96
  try {
83
97
  // TODO: possible optimizaion: only send params that haven't already been sent to this wafContext
84
- const result = wafContext.run(params, DEFAULT_MAX_BUDGET)
98
+ const result = wafContext.run(params, this.wafTimeout)
85
99
 
86
100
  return this.applyResult(result, store)
87
101
  } catch (err) {
@@ -93,13 +107,14 @@ class WAFCallback {
93
107
  }
94
108
 
95
109
  applyResult (result, store) {
110
+ Reporter.reportMetrics({
111
+ duration: result.totalRuntime,
112
+ rulesVersion: this.ddwaf.rulesInfo.version
113
+ }, store)
114
+
96
115
  if (result.data && result.data !== '[]') {
97
116
  Reporter.reportAttack(result.data, store)
98
117
  }
99
-
100
- // TODO: use these values later for budget management
101
- // result.perfData
102
- // result.perfTotalRuntime
103
118
  }
104
119
 
105
120
  clear () {
@@ -16,7 +16,7 @@ function enable (config) {
16
16
  let rules = fs.readFileSync(config.appsec.rules)
17
17
  rules = JSON.parse(rules)
18
18
 
19
- RuleManager.applyRules(rules)
19
+ RuleManager.applyRules(rules, config.appsec)
20
20
  } catch (err) {
21
21
  log.error('Unable to start AppSec')
22
22
  log.error(err)
@@ -94,12 +94,16 @@ function incomingHttpEndTranslator (data) {
94
94
  }
95
95
 
96
96
  if (data.req.cookies && typeof data.req.cookies === 'object') {
97
- payload[addresses.HTTP_INCOMING_COOKIES] = data.req.cookies
97
+ payload[addresses.HTTP_INCOMING_COOKIES] = {}
98
+
99
+ for (const k of Object.keys(data.req.cookies)) {
100
+ payload[addresses.HTTP_INCOMING_COOKIES][k] = [ data.req.cookies[k] ]
101
+ }
98
102
  }
99
103
 
100
104
  Gateway.propagate(payload, context)
101
105
 
102
- Reporter.finishAttacks(data.req, context)
106
+ Reporter.finishRequest(data.req, context)
103
107
  }
104
108
 
105
109
  function disable () {