dd-trace 1.5.0 → 2.0.0-appsec-beta.1

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 (60) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +0 -6
  3. package/package.json +10 -6
  4. package/packages/datadog-core/index.js +7 -0
  5. package/packages/datadog-core/src/storage/async_hooks.js +49 -0
  6. package/packages/datadog-core/src/storage/async_resource.js +76 -0
  7. package/packages/datadog-core/src/storage/index.js +14 -0
  8. package/packages/datadog-plugin-cucumber/src/index.js +2 -0
  9. package/packages/datadog-plugin-cypress/src/plugin.js +5 -3
  10. package/packages/datadog-plugin-graphql/src/index.js +16 -10
  11. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  12. package/packages/datadog-plugin-http/src/server.js +1 -1
  13. package/packages/datadog-plugin-jest/src/jest-environment.js +5 -1
  14. package/packages/datadog-plugin-jest/src/jest-jasmine2.js +11 -2
  15. package/packages/datadog-plugin-mocha/src/index.js +9 -1
  16. package/packages/dd-trace/lib/version.js +1 -1
  17. package/packages/dd-trace/src/appsec/addresses.js +11 -0
  18. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +126 -0
  19. package/packages/dd-trace/src/appsec/callbacks/index.js +7 -0
  20. package/packages/dd-trace/src/appsec/index.js +97 -2
  21. package/packages/dd-trace/src/appsec/recommended.json +1 -0
  22. package/packages/dd-trace/src/appsec/reporter.js +275 -0
  23. package/packages/dd-trace/src/appsec/rule_manager.js +27 -0
  24. package/packages/dd-trace/src/config.js +1 -5
  25. package/packages/dd-trace/src/encode/0.4.js +84 -23
  26. package/packages/dd-trace/src/encode/chunk.js +12 -14
  27. package/packages/dd-trace/src/gateway/als.js +5 -0
  28. package/packages/dd-trace/src/gateway/channels.js +8 -0
  29. package/packages/dd-trace/src/gateway/dc_block.js +68 -0
  30. package/packages/dd-trace/src/gateway/engine/engine.js +150 -0
  31. package/packages/dd-trace/src/gateway/engine/index.js +46 -0
  32. package/packages/dd-trace/src/gateway/engine/runner.js +40 -0
  33. package/packages/dd-trace/src/id.js +22 -18
  34. package/packages/dd-trace/src/instrumenter.js +2 -2
  35. package/packages/dd-trace/src/noop/scope.js +23 -0
  36. package/packages/dd-trace/src/noop/span.js +2 -0
  37. package/packages/dd-trace/src/noop/tracer.js +3 -10
  38. package/packages/dd-trace/src/opentracing/span.js +2 -0
  39. package/packages/dd-trace/src/plugins/util/ci-app-spec.json +1 -0
  40. package/packages/dd-trace/src/plugins/util/ci.js +36 -7
  41. package/packages/dd-trace/src/plugins/util/git.js +11 -25
  42. package/packages/dd-trace/src/plugins/util/test.js +14 -2
  43. package/packages/dd-trace/src/plugins/util/user-provided-git.js +57 -0
  44. package/packages/dd-trace/src/plugins/util/web.js +15 -2
  45. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -4
  46. package/packages/dd-trace/src/profiling/profilers/cpu.js +1 -3
  47. package/packages/dd-trace/src/proxy.js +1 -1
  48. package/packages/dd-trace/src/scope.js +212 -18
  49. package/packages/dd-trace/src/startup-log.js +2 -1
  50. package/packages/dd-trace/src/tracer.js +4 -19
  51. package/scripts/version.js +8 -3
  52. package/NOTICE +0 -4
  53. package/packages/dd-trace/src/.DS_Store +0 -0
  54. package/packages/dd-trace/src/scope/async_hooks.js +0 -139
  55. package/packages/dd-trace/src/scope/async_local_storage.js +0 -29
  56. package/packages/dd-trace/src/scope/async_resource.js +0 -82
  57. package/packages/dd-trace/src/scope/base.js +0 -218
  58. package/packages/dd-trace/src/scope/scope_manager.js +0 -5
  59. package/packages/dd-trace/src/scope/sync.js +0 -29
  60. package/packages/dd-trace/src/scope/zone.js +0 -38
@@ -1,9 +1,11 @@
1
1
  Component,Origin,License,Copyright
2
+ require,@datadog/native-appsec,Apache license 2.0,Copyright 2018 Datadog Inc.
2
3
  require,@datadog/native-metrics,Apache license 2.0,Copyright 2018 Datadog Inc.
3
4
  require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
4
5
  require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
5
6
  require,@types/node,MIT,Copyright Authors
6
7
  require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
8
+ require,diagnostics_channel,MIT,Copyright 2021 Simon D.
7
9
  require,form-data,MIT,Copyright 2012 Felix Geisendörfer and contributors
8
10
  require,import-in-the-middle,Apache license 2.0,Copyright 2021 Datadog Inc.
9
11
  require,koalas,MIT,Copyright 2013-2017 Brian Woodward
package/index.d.ts CHANGED
@@ -285,12 +285,6 @@ export declare interface TracerOptions {
285
285
  */
286
286
  runtimeMetrics?: boolean
287
287
 
288
- /**
289
- * Whether to track the scope of async functions. This is needed for async/await to work with non-native promises (thenables). Only disable this if you are sure only native promises are used with async/await, or if you are using Node >=14.5 since the issue has been fixed in that version.
290
- * @default true
291
- */
292
- trackAsyncScope?: boolean
293
-
294
288
  /**
295
289
  * Custom function for DNS lookups when sending requests to the agent.
296
290
  * @default dns.lookup()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "1.5.0",
3
+ "version": "2.0.0-appsec-beta.1",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -16,8 +16,10 @@
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 --exit --expose-gc 'packages/dd-trace/test/setup/node.js' 'packages/*/test/**/*.spec.js'",
19
- "test:core": "mocha --exit --expose-gc --file packages/dd-trace/test/setup/core.js \"packages/dd-trace/test/**/*.spec.js\"",
20
- "test:core:ci": "nyc --include \"packages/dd-trace/src/**/*.js\" -- npm run test:core -- --reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter-config.json",
19
+ "test:trace:core": "mocha --exit --expose-gc --file packages/dd-trace/test/setup/core.js \"packages/dd-trace/test/**/*.spec.js\"",
20
+ "test:trace:core:ci": "nyc --include \"packages/dd-trace/src/**/*.js\" -- npm run test:trace:core -- --reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter-config.json",
21
+ "test:core": "mocha --file packages/datadog-core/test/setup.js 'packages/datadog-core/test/**/*.spec.js'",
22
+ "test:core:ci": "nyc --include 'packages/datadog-core/src/**/*.js' -- npm run test:core -- --reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter-config.json",
21
23
  "test:plugins": "mocha --exit --file \"packages/dd-trace/test/setup/core.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/**/*.spec.js\"",
22
24
  "test:plugins:ci": "yarn services && nyc --include \"packages/datadog-plugin-@($(echo $PLUGINS))/src/**/*.js\" -- npm run test:plugins -- --reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter-config.json",
23
25
  "test:plugins:upstream": "node ./packages/dd-trace/test/plugins/suite.js",
@@ -56,13 +58,15 @@
56
58
  "node": ">=12"
57
59
  },
58
60
  "dependencies": {
59
- "@datadog/native-metrics": "^1.0.0",
60
- "@datadog/pprof": "^0.1.3",
61
+ "@datadog/native-appsec": "^0.6.0",
62
+ "@datadog/native-metrics": "^1.0.1",
63
+ "@datadog/pprof": "^0.3.0",
61
64
  "@datadog/sketches-js": "^1.0.4",
62
65
  "@types/node": "^10.12.18",
63
66
  "crypto-randomuuid": "^1.0.0",
67
+ "diagnostics_channel": "^1.1.0",
64
68
  "form-data": "^3.0.0",
65
- "import-in-the-middle": "^1.1.1",
69
+ "import-in-the-middle": "^1.1.2",
66
70
  "koalas": "^1.0.2",
67
71
  "limiter": "^1.1.4",
68
72
  "lodash.kebabcase": "^4.1.1",
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const LocalStorage = require('./src/storage')
4
+
5
+ const storage = new LocalStorage()
6
+
7
+ module.exports = { storage }
@@ -0,0 +1,49 @@
1
+ 'use strict'
2
+
3
+ const { createHook, executionAsyncId } = require('async_hooks')
4
+ const AsyncResourceStorage = require('./async_resource')
5
+
6
+ class AsyncHooksStorage extends AsyncResourceStorage {
7
+ constructor () {
8
+ super()
9
+
10
+ this._resources = new Map()
11
+ }
12
+
13
+ disable () {
14
+ super.disable()
15
+
16
+ this._resources.clear()
17
+ }
18
+
19
+ _createHook () {
20
+ return createHook({
21
+ init: this._init.bind(this),
22
+ destroy: this._destroy.bind(this)
23
+ })
24
+ }
25
+
26
+ _init (asyncId, type, triggerAsyncId, resource) {
27
+ super._init.apply(this, arguments)
28
+
29
+ this._resources.set(asyncId, resource)
30
+ }
31
+
32
+ _destroy (asyncId) {
33
+ this._resources.delete(asyncId)
34
+ }
35
+
36
+ _executionAsyncResource () {
37
+ const asyncId = executionAsyncId()
38
+
39
+ let resource = this._resources.get(asyncId)
40
+
41
+ if (!resource) {
42
+ this._resources.set(asyncId, resource = {})
43
+ }
44
+
45
+ return resource
46
+ }
47
+ }
48
+
49
+ module.exports = AsyncHooksStorage
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ const { createHook, executionAsyncResource } = require('async_hooks')
4
+
5
+ class AsyncResourceStorage {
6
+ constructor () {
7
+ this._ddResourceStore = Symbol('ddResourceStore')
8
+ this._enabled = false
9
+ this._hook = this._createHook()
10
+ }
11
+
12
+ disable () {
13
+ if (!this._enabled) return
14
+
15
+ this._hook.disable()
16
+ this._enabled = false
17
+ }
18
+
19
+ getStore () {
20
+ if (!this._enabled) return
21
+
22
+ const resource = this._executionAsyncResource()
23
+
24
+ return resource[this._ddResourceStore]
25
+ }
26
+
27
+ enterWith (store) {
28
+ this._enable()
29
+
30
+ const resource = this._executionAsyncResource()
31
+
32
+ resource[this._ddResourceStore] = store
33
+ }
34
+
35
+ run (store, callback, ...args) {
36
+ this._enable()
37
+
38
+ const resource = this._executionAsyncResource()
39
+ const oldStore = resource[this._ddResourceStore]
40
+
41
+ resource[this._ddResourceStore] = store
42
+
43
+ try {
44
+ return callback(...args)
45
+ } finally {
46
+ resource[this._ddResourceStore] = oldStore
47
+ }
48
+ }
49
+
50
+ _createHook () {
51
+ return createHook({
52
+ init: this._init.bind(this)
53
+ })
54
+ }
55
+
56
+ _enable () {
57
+ if (this._enabled) return
58
+
59
+ this._enabled = true
60
+ this._hook.enable()
61
+ }
62
+
63
+ _init (asyncId, type, triggerAsyncId, resource) {
64
+ const currentResource = this._executionAsyncResource()
65
+
66
+ if (Object.prototype.hasOwnProperty.call(currentResource, this._ddResourceStore)) {
67
+ resource[this._ddResourceStore] = currentResource[this._ddResourceStore]
68
+ }
69
+ }
70
+
71
+ _executionAsyncResource () {
72
+ return executionAsyncResource()
73
+ }
74
+ }
75
+
76
+ module.exports = AsyncResourceStorage
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ // TODO: default to AsyncLocalStorage when it supports triggerAsyncResource
4
+
5
+ const semver = require('semver')
6
+
7
+ // https://github.com/nodejs/node/pull/33801
8
+ const hasJavaScriptAsyncHooks = semver.satisfies(process.versions.node, '>=14.5 || ^12.19.0')
9
+
10
+ if (hasJavaScriptAsyncHooks) {
11
+ module.exports = require('./async_resource')
12
+ } else {
13
+ module.exports = require('./async_hooks')
14
+ }
@@ -5,6 +5,7 @@ const {
5
5
  TEST_NAME,
6
6
  TEST_SUITE,
7
7
  TEST_STATUS,
8
+ TEST_FRAMEWORK_VERSION,
8
9
  TEST_SKIP_REASON,
9
10
  CI_APP_ORIGIN,
10
11
  ERROR_MESSAGE,
@@ -52,6 +53,7 @@ function createWrapRun (tracer, testEnvironmentMetadata, sourceRoot, setStatus)
52
53
  [TEST_NAME]: testName,
53
54
  [TEST_SUITE]: testSuite,
54
55
  [SAMPLING_RULE_DECISION]: 1,
56
+ [TEST_FRAMEWORK_VERSION]: tracer._version,
55
57
  ...testEnvironmentMetadata
56
58
  }
57
59
 
@@ -3,6 +3,7 @@ const {
3
3
  TEST_NAME,
4
4
  TEST_SUITE,
5
5
  TEST_STATUS,
6
+ TEST_FRAMEWORK_VERSION,
6
7
  getTestEnvironmentMetadata,
7
8
  CI_APP_ORIGIN,
8
9
  getTestParentSpan
@@ -19,7 +20,7 @@ const CYPRESS_STATUS_TO_TEST_STATUS = {
19
20
  skipped: 'skip'
20
21
  }
21
22
 
22
- function getTestSpanMetadata (tracer, testName, testSuite) {
23
+ function getTestSpanMetadata (tracer, testName, testSuite, cypressConfig) {
23
24
  const childOf = getTestParentSpan(tracer)
24
25
 
25
26
  return {
@@ -29,7 +30,8 @@ function getTestSpanMetadata (tracer, testName, testSuite) {
29
30
  [TEST_NAME]: testName,
30
31
  [TEST_SUITE]: testSuite,
31
32
  [SAMPLING_RULE_DECISION]: 1,
32
- [SAMPLING_PRIORITY]: AUTO_KEEP
33
+ [SAMPLING_PRIORITY]: AUTO_KEEP,
34
+ [TEST_FRAMEWORK_VERSION]: cypressConfig.version
33
35
  }
34
36
  }
35
37
 
@@ -50,7 +52,7 @@ module.exports = (on, config) => {
50
52
  childOf,
51
53
  resource,
52
54
  ...testSpanMetadata
53
- } = getTestSpanMetadata(tracer, testName, testSuite)
55
+ } = getTestSpanMetadata(tracer, testName, testSuite, config)
54
56
 
55
57
  if (!activeSpan) {
56
58
  activeSpan = tracer.startSpan('cypress.test', {
@@ -9,20 +9,17 @@ let tools
9
9
  function createWrapExecute (tracer, config, defaultFieldResolver) {
10
10
  return function wrapExecute (execute) {
11
11
  return function executeWithTrace () {
12
- const args = normalizeArgs(arguments)
12
+ const args = normalizeArgs(arguments, tracer, config, defaultFieldResolver)
13
13
  const schema = args.schema
14
14
  const document = args.document
15
15
  const source = document && document._datadog_source
16
- const fieldResolver = args.fieldResolver || defaultFieldResolver
17
- const contextValue = args.contextValue = args.contextValue || {}
16
+ const contextValue = args.contextValue
18
17
  const operation = getOperation(document, args.operationName)
19
18
 
20
19
  if (contextValue._datadog_graphql) {
21
20
  return execute.apply(this, arguments)
22
21
  }
23
22
 
24
- args.fieldResolver = wrapResolve(fieldResolver, tracer, config)
25
-
26
23
  if (schema) {
27
24
  wrapFields(schema._queryType, tracer, config)
28
25
  wrapFields(schema._mutationType, tracer, config)
@@ -32,7 +29,7 @@ function createWrapExecute (tracer, config, defaultFieldResolver) {
32
29
 
33
30
  contextValue._datadog_graphql = { source, span, fields: {} }
34
31
 
35
- return call(execute, span, this, [args], (err, res) => {
32
+ return call(execute, span, this, arguments, (err, res) => {
36
33
  finishResolvers(contextValue, config)
37
34
 
38
35
  setError(span, err || (res && res.errors && res.errors[0]))
@@ -212,10 +209,19 @@ function getField (contextValue, path) {
212
209
  return contextValue._datadog_graphql.fields[path.join('.')]
213
210
  }
214
211
 
215
- function normalizeArgs (args) {
216
- if (args.length === 1) {
217
- return args[0]
218
- }
212
+ function normalizeArgs (args, tracer, config, defaultFieldResolver) {
213
+ if (args.length !== 1) return normalizePositional(args, tracer, config, defaultFieldResolver)
214
+
215
+ args[0].contextValue = args[0].contextValue || {}
216
+ args[0].fieldResolver = wrapResolve(args[0].fieldResolver || defaultFieldResolver, tracer, config)
217
+
218
+ return args[0]
219
+ }
220
+
221
+ function normalizePositional (args, tracer, config, defaultFieldResolver) {
222
+ args[3] = args[3] || {} // contextValue
223
+ args[6] = wrapResolve(args[6] || defaultFieldResolver, tracer, config) // fieldResolver
224
+ args.length = Math.max(args.length, 7)
219
225
 
220
226
  return {
221
227
  schema: args[0],
@@ -48,7 +48,7 @@ function createWrapMakeClientConstructor (tracer, config) {
48
48
  function wrapPackageDefinition (tracer, config, def) {
49
49
  for (const name in def) {
50
50
  if (def[name].format) continue
51
- if (def[name].service) {
51
+ if (def[name].service && def[name].prototype) {
52
52
  wrapClientConstructor(tracer, config, def[name], def[name].service)
53
53
  } else {
54
54
  wrapPackageDefinition(tracer, config, def[name])
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const web = require('../../dd-trace/src/plugins/util/web')
4
- const Scope = require('../../dd-trace/src/scope/base')
4
+ const Scope = require('../../dd-trace/src/scope')
5
5
 
6
6
  function createWrapEmit (tracer, config) {
7
7
  config = web.normalizeConfig(config)
@@ -5,6 +5,8 @@ const {
5
5
  TEST_NAME,
6
6
  TEST_SUITE,
7
7
  TEST_STATUS,
8
+ TEST_FRAMEWORK_VERSION,
9
+ JEST_TEST_RUNNER,
8
10
  ERROR_MESSAGE,
9
11
  ERROR_TYPE,
10
12
  TEST_PARAMETERS,
@@ -138,7 +140,9 @@ function createHandleTestEvent (tracer, testEnvironmentMetadata, instrumenter) {
138
140
  const spanTags = {
139
141
  ...commonSpanTags,
140
142
  [TEST_NAME]: testName,
141
- [TEST_SUITE]: this.testSuite
143
+ [TEST_SUITE]: this.testSuite,
144
+ [TEST_FRAMEWORK_VERSION]: tracer._version,
145
+ [JEST_TEST_RUNNER]: 'jest-circus'
142
146
  }
143
147
 
144
148
  const testParametersString = getTestParametersString(nameToParams, event.test.name)
@@ -5,6 +5,8 @@ const {
5
5
  TEST_NAME,
6
6
  TEST_SUITE,
7
7
  TEST_STATUS,
8
+ TEST_FRAMEWORK_VERSION,
9
+ JEST_TEST_RUNNER,
8
10
  CI_APP_ORIGIN,
9
11
  getTestEnvironmentMetadata,
10
12
  finishAllTraceSpans,
@@ -29,7 +31,12 @@ function createWrapIt (tracer, globalConfig, globalInput, testEnvironmentMetadat
29
31
  {
30
32
  type: 'test',
31
33
  childOf,
32
- tags: { ...commonSpanTags, [TEST_SUITE]: testSuite }
34
+ tags: {
35
+ ...commonSpanTags,
36
+ [TEST_SUITE]: testSuite,
37
+ [TEST_FRAMEWORK_VERSION]: tracer._version,
38
+ [JEST_TEST_RUNNER]: 'jest-jasmine2'
39
+ }
33
40
  },
34
41
  async (done) => {
35
42
  const testSpan = tracer.scope().active()
@@ -124,7 +131,9 @@ function createWrapItSkip (tracer, globalConfig, globalInput, testEnvironmentMet
124
131
  [RESOURCE_NAME]: resource,
125
132
  [TEST_NAME]: testName,
126
133
  [TEST_SUITE]: testSuite,
127
- [TEST_STATUS]: 'skip'
134
+ [TEST_STATUS]: 'skip',
135
+ [TEST_FRAMEWORK_VERSION]: tracer._version,
136
+ [JEST_TEST_RUNNER]: 'jest-jasmine2'
128
137
  }
129
138
  }
130
139
  )
@@ -9,6 +9,7 @@ const {
9
9
  TEST_SUITE,
10
10
  TEST_STATUS,
11
11
  TEST_PARAMETERS,
12
+ TEST_FRAMEWORK_VERSION,
12
13
  CI_APP_ORIGIN,
13
14
  getTestEnvironmentMetadata,
14
15
  getTestParametersString,
@@ -31,13 +32,20 @@ function getTestSpanMetadata (tracer, test, sourceRoot) {
31
32
  [TEST_NAME]: fullTestName,
32
33
  [TEST_SUITE]: testSuite,
33
34
  [SAMPLING_RULE_DECISION]: 1,
34
- [SAMPLING_PRIORITY]: AUTO_KEEP
35
+ [SAMPLING_PRIORITY]: AUTO_KEEP,
36
+ [TEST_FRAMEWORK_VERSION]: tracer._version
35
37
  }
36
38
  }
37
39
 
38
40
  function createWrapRunTest (tracer, testEnvironmentMetadata, sourceRoot) {
39
41
  return function wrapRunTest (runTest) {
40
42
  return async function runTestWithTrace () {
43
+ // `runTest` is rerun when retries are configured through `this.retries` and the test fails.
44
+ // This clause prevents rewrapping `this.test.fn` when it has already been wrapped.
45
+ if (this.test._currentRetry !== undefined && this.test._currentRetry !== 0) {
46
+ return runTest.apply(this, arguments)
47
+ }
48
+
41
49
  let specFunction = this.test.fn
42
50
  if (specFunction.length) {
43
51
  specFunction = promisify(specFunction)
@@ -1 +1 @@
1
- module.exports = '1.5.0'
1
+ module.exports = '2.0.0-pre'
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ HTTP_INCOMING_URL: 'server.request.uri.raw',
5
+ HTTP_INCOMING_HEADERS: 'server.request.headers.no_cookies',
6
+ HTTP_INCOMING_METHOD: 'server.request.method',
7
+ HTTP_INCOMING_REMOTE_IP: 'server.request.client_ip',
8
+ HTTP_INCOMING_REMOTE_PORT: 'server.request.client_port',
9
+ HTTP_INCOMING_RESPONSE_CODE: 'server.response.status',
10
+ HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies'
11
+ }
@@ -0,0 +1,126 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+ const addresses = require('../addresses')
5
+ const Gateway = require('../../gateway/engine')
6
+ const Reporter = require('../reporter')
7
+
8
+ let warned = false
9
+
10
+ const validAddressSet = new Set(Object.values(addresses))
11
+
12
+ const DEFAULT_MAX_BUDGET = 5e3 // µs
13
+
14
+ // TODO: put reusable code in a base class
15
+ class WAFCallback {
16
+ static loadDDWAF (rules) {
17
+ try {
18
+ // require in `try/catch` because this can throw at require time
19
+ const { DDWAF } = require('@datadog/native-appsec')
20
+
21
+ return new DDWAF(rules)
22
+ } catch (err) {
23
+ if (!warned) {
24
+ log.warn('AppSec could not load native package. In-app WAF features will not be available.')
25
+ warned = true
26
+ }
27
+
28
+ throw err
29
+ }
30
+ }
31
+
32
+ constructor (rules) {
33
+ this.ddwaf = WAFCallback.loadDDWAF(rules)
34
+ this.wafContextCache = new WeakMap()
35
+
36
+ // closures are faster than binds
37
+ const self = this
38
+ const method = (params, store) => {
39
+ return self.action(params, store)
40
+ }
41
+
42
+ // might be its own class with more info later
43
+ const callback = { method }
44
+
45
+ const subscribedAddresses = new Set()
46
+
47
+ for (const rule of rules.rules) {
48
+ for (const condition of rule.conditions) {
49
+ for (const input of condition.parameters.inputs) {
50
+ const address = input.address.split(':', 2)[0]
51
+
52
+ if (!validAddressSet.has(address) || subscribedAddresses.has(address)) continue
53
+
54
+ subscribedAddresses.add(address)
55
+
56
+ Gateway.manager.addSubscription({ addresses: [ address ], callback })
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ action (params, store) {
63
+ let wafContext
64
+
65
+ if (store) {
66
+ const key = store.get('context')
67
+
68
+ if (key) {
69
+ if (this.wafContextCache.has(key)) {
70
+ wafContext = this.wafContextCache.get(key)
71
+ } else {
72
+ wafContext = this.ddwaf.createContext()
73
+ this.wafContextCache.set(key, wafContext)
74
+ }
75
+ }
76
+ }
77
+
78
+ if (!wafContext) {
79
+ wafContext = this.ddwaf.createContext()
80
+ }
81
+
82
+ // TODO: if status code in params, convert it to string
83
+
84
+ try {
85
+ // TODO: possible optimizaion: only send params that haven't already been sent to this wafContext
86
+ const result = wafContext.run(params, DEFAULT_MAX_BUDGET)
87
+
88
+ return this.applyResult(result)
89
+ } catch (err) {
90
+ log.warn('Error while running the AppSec WAF')
91
+ }
92
+ }
93
+
94
+ applyResult (result) {
95
+ if (result.action) {
96
+ const data = JSON.parse(result.data)
97
+
98
+ for (let i = 0; i < data.length; ++i) {
99
+ const point = data[i]
100
+ const ruleMatch = point.rule_matches[0]
101
+
102
+ ruleMatch.highlight = []
103
+
104
+ for (const param of ruleMatch.parameters) {
105
+ ruleMatch.highlight = ruleMatch.highlight.concat(param.highlight)
106
+ delete param.highlight
107
+ }
108
+
109
+ Reporter.reportAttack(point.rule, ruleMatch)
110
+ }
111
+ }
112
+
113
+ // result.perfData
114
+ // result.perfTotalRuntime
115
+ }
116
+
117
+ clear () {
118
+ this.ddwaf.dispose()
119
+
120
+ this.wafContextCache = new WeakMap()
121
+
122
+ Gateway.manager.clear()
123
+ }
124
+ }
125
+
126
+ module.exports = WAFCallback
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ // lazy loading
4
+ // TODO: cache the returned value
5
+ module.exports = {
6
+ get DDWAF () { return require('./ddwaf') }
7
+ }