dd-trace 4.21.0 → 4.22.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 (32) hide show
  1. package/index.d.ts +5 -0
  2. package/package.json +5 -5
  3. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  4. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  5. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  7. package/packages/datadog-instrumentations/src/http/client.js +2 -14
  8. package/packages/datadog-instrumentations/src/next.js +15 -5
  9. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  10. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  11. package/packages/datadog-plugin-http/src/client.js +1 -1
  12. package/packages/datadog-plugin-next/src/index.js +32 -6
  13. package/packages/dd-trace/src/appsec/activation.js +29 -0
  14. package/packages/dd-trace/src/appsec/addresses.js +1 -0
  15. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  16. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  17. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  18. package/packages/dd-trace/src/appsec/channels.js +4 -1
  19. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  20. package/packages/dd-trace/src/appsec/index.js +29 -40
  21. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  22. package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
  23. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  24. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +25 -13
  25. package/packages/dd-trace/src/config.js +5 -0
  26. package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
  27. package/packages/dd-trace/src/profiling/profiler.js +7 -6
  28. package/packages/dd-trace/src/profiling/profilers/events.js +17 -12
  29. package/packages/dd-trace/src/profiling/profilers/shared.js +33 -3
  30. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  31. package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
  32. package/packages/dd-trace/src/proxy.js +4 -0
package/index.d.ts CHANGED
@@ -567,6 +567,11 @@ export declare interface TracerOptions {
567
567
  */
568
568
  blockedTemplateJson?: string,
569
569
 
570
+ /**
571
+ * Specifies a path to a custom blocking template json file for graphql requests
572
+ */
573
+ blockedTemplateGraphql?: string,
574
+
570
575
  /**
571
576
  * Controls the automated user event tracking configuration
572
577
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.21.0",
3
+ "version": "4.22.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -68,8 +68,8 @@
68
68
  "node": ">=16"
69
69
  },
70
70
  "dependencies": {
71
- "@datadog/native-appsec": "5.0.0",
72
- "@datadog/native-iast-rewriter": "2.2.1",
71
+ "@datadog/native-appsec": "6.0.0",
72
+ "@datadog/native-iast-rewriter": "2.2.2",
73
73
  "@datadog/native-iast-taint-tracking": "1.6.4",
74
74
  "@datadog/native-metrics": "^2.0.0",
75
75
  "@datadog/pprof": "4.1.0",
@@ -99,9 +99,9 @@
99
99
  "path-to-regexp": "^0.1.2",
100
100
  "pprof-format": "^2.0.7",
101
101
  "protobufjs": "^7.2.5",
102
- "tlhunter-sorted-set": "^0.1.0",
103
102
  "retry": "^0.13.1",
104
- "semver": "^7.5.4"
103
+ "semver": "^7.5.4",
104
+ "tlhunter-sorted-set": "^0.1.0"
105
105
  },
106
106
  "devDependencies": {
107
107
  "@types/node": ">=16",
@@ -0,0 +1,41 @@
1
+ 'use strict'
2
+
3
+ const { AbortController } = require('node-abort-controller')
4
+ const { addHook } = require('./helpers/instrument')
5
+ const shimmer = require('../../datadog-shimmer')
6
+ const dc = require('dc-polyfill')
7
+
8
+ const requestChannel = dc.tracingChannel('datadog:apollo-server-core:request')
9
+
10
+ addHook({ name: 'apollo-server-core', file: 'dist/runHttpQuery.js', versions: ['>3.0.0'] }, runHttpQueryModule => {
11
+ const HttpQueryError = runHttpQueryModule.HttpQueryError
12
+
13
+ shimmer.wrap(runHttpQueryModule, 'runHttpQuery', function wrapRunHttpQuery (originalRunHttpQuery) {
14
+ return async function runHttpQuery () {
15
+ if (!requestChannel.start.hasSubscribers) {
16
+ return originalRunHttpQuery.apply(this, arguments)
17
+ }
18
+
19
+ const abortController = new AbortController()
20
+ const abortData = {}
21
+
22
+ const runHttpQueryResult = requestChannel.tracePromise(
23
+ originalRunHttpQuery,
24
+ { abortController, abortData },
25
+ this,
26
+ ...arguments)
27
+
28
+ const abortPromise = new Promise((resolve, reject) => {
29
+ abortController.signal.addEventListener('abort', (event) => {
30
+ // runHttpQuery callbacks are writing the response on resolve/reject.
31
+ // We should return blocking data in the apollo-server-core HttpQueryError object
32
+ reject(new HttpQueryError(abortData.statusCode, abortData.message, true, abortData.headers))
33
+ }, { once: true })
34
+ })
35
+
36
+ return Promise.race([runHttpQueryResult, abortPromise])
37
+ }
38
+ })
39
+
40
+ return runHttpQueryModule
41
+ })
@@ -0,0 +1,83 @@
1
+ 'use strict'
2
+
3
+ const { AbortController } = require('node-abort-controller')
4
+ const dc = require('dc-polyfill')
5
+
6
+ const { addHook } = require('./helpers/instrument')
7
+ const shimmer = require('../../datadog-shimmer')
8
+
9
+ const graphqlMiddlewareChannel = dc.tracingChannel('datadog:apollo:middleware')
10
+
11
+ const requestChannel = dc.tracingChannel('datadog:apollo:request')
12
+
13
+ let HeaderMap
14
+
15
+ function wrapExecuteHTTPGraphQLRequest (originalExecuteHTTPGraphQLRequest) {
16
+ return async function executeHTTPGraphQLRequest () {
17
+ if (!HeaderMap || !requestChannel.start.hasSubscribers) {
18
+ return originalExecuteHTTPGraphQLRequest.apply(this, arguments)
19
+ }
20
+
21
+ const abortController = new AbortController()
22
+ const abortData = {}
23
+
24
+ const graphqlResponseData = requestChannel.tracePromise(
25
+ originalExecuteHTTPGraphQLRequest,
26
+ { abortController, abortData },
27
+ this,
28
+ ...arguments)
29
+
30
+ const abortPromise = new Promise((resolve, reject) => {
31
+ abortController.signal.addEventListener('abort', (event) => {
32
+ // This method is expected to return response data
33
+ // with headers, status and body
34
+ const headers = new HeaderMap()
35
+ Object.keys(abortData.headers).forEach(key => {
36
+ headers.set(key, abortData.headers[key])
37
+ })
38
+
39
+ resolve({
40
+ headers: headers,
41
+ status: abortData.statusCode,
42
+ body: {
43
+ kind: 'complete',
44
+ string: abortData.message
45
+ }
46
+ })
47
+ }, { once: true })
48
+ })
49
+
50
+ return Promise.race([abortPromise, graphqlResponseData])
51
+ }
52
+ }
53
+
54
+ function apolloExpress4Hook (express4) {
55
+ shimmer.wrap(express4, 'expressMiddleware', function wrapExpressMiddleware (originalExpressMiddleware) {
56
+ return function expressMiddleware (server, options) {
57
+ const originalMiddleware = originalExpressMiddleware.apply(this, arguments)
58
+
59
+ return shimmer.wrap(originalMiddleware, function (req, res, next) {
60
+ if (!graphqlMiddlewareChannel.start.hasSubscribers) {
61
+ return originalMiddleware.apply(this, arguments)
62
+ }
63
+
64
+ return graphqlMiddlewareChannel.traceSync(originalMiddleware, { req }, this, ...arguments)
65
+ })
66
+ }
67
+ })
68
+ return express4
69
+ }
70
+
71
+ function apolloHeaderMapHook (headerMap) {
72
+ HeaderMap = headerMap.HeaderMap
73
+ return headerMap
74
+ }
75
+
76
+ function apolloServerHook (apolloServer) {
77
+ shimmer.wrap(apolloServer.ApolloServer.prototype, 'executeHTTPGraphQLRequest', wrapExecuteHTTPGraphQLRequest)
78
+ return apolloServer
79
+ }
80
+
81
+ addHook({ name: '@apollo/server', file: 'dist/cjs/ApolloServer.js', versions: ['>=4.0.0'] }, apolloServerHook)
82
+ addHook({ name: '@apollo/server', file: 'dist/cjs/express4/index.js', versions: ['>=4.0.0'] }, apolloExpress4Hook)
83
+ addHook({ name: '@apollo/server', file: 'dist/cjs/utils/HeaderMap.js', versions: ['>=4.0.0'] }, apolloHeaderMapHook)
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { AbortController } = require('node-abort-controller')
4
+
3
5
  const {
4
6
  addHook,
5
7
  channel,
@@ -37,6 +39,13 @@ const validateStartCh = channel('apm:graphql:validate:start')
37
39
  const validateFinishCh = channel('apm:graphql:validate:finish')
38
40
  const validateErrorCh = channel('apm:graphql:validate:error')
39
41
 
42
+ class AbortError extends Error {
43
+ constructor (message) {
44
+ super(message)
45
+ this.name = 'AbortError'
46
+ }
47
+ }
48
+
40
49
  function getOperation (document, operationName) {
41
50
  if (!document || !Array.isArray(document.definitions)) {
42
51
  return
@@ -175,11 +184,11 @@ function wrapExecute (execute) {
175
184
  docSource: documentSources.get(document)
176
185
  })
177
186
 
178
- const context = { source, asyncResource, fields: {} }
187
+ const context = { source, asyncResource, fields: {}, abortController: new AbortController() }
179
188
 
180
189
  contexts.set(contextValue, context)
181
190
 
182
- return callInAsyncScope(exe, asyncResource, this, arguments, (err, res) => {
191
+ return callInAsyncScope(exe, asyncResource, this, arguments, context.abortController, (err, res) => {
183
192
  if (finishResolveCh.hasSubscribers) finishResolvers(context)
184
193
 
185
194
  const error = err || (res && res.errors && res.errors[0])
@@ -207,7 +216,7 @@ function wrapResolve (resolve) {
207
216
 
208
217
  const field = assertField(context, info, args)
209
218
 
210
- return callInAsyncScope(resolve, field.asyncResource, this, arguments, (err) => {
219
+ return callInAsyncScope(resolve, field.asyncResource, this, arguments, context.abortController, (err) => {
211
220
  updateFieldCh.publish({ field, info, err })
212
221
  })
213
222
  }
@@ -217,10 +226,15 @@ function wrapResolve (resolve) {
217
226
  return resolveAsync
218
227
  }
219
228
 
220
- function callInAsyncScope (fn, aR, thisArg, args, cb) {
229
+ function callInAsyncScope (fn, aR, thisArg, args, abortController, cb) {
221
230
  cb = cb || (() => {})
222
231
 
223
232
  return aR.runInAsyncScope(() => {
233
+ if (abortController?.signal.aborted) {
234
+ cb(null, null)
235
+ throw new AbortError('Aborted')
236
+ }
237
+
224
238
  try {
225
239
  const result = fn.apply(thisArg, args)
226
240
  if (result && typeof result.then === 'function') {
@@ -1,6 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
+ '@apollo/server': () => require('../apollo-server'),
5
+ 'apollo-server-core': () => require('../apollo-server-core'),
4
6
  '@aws-sdk/smithy-client': () => require('../aws-sdk'),
5
7
  '@cucumber/cucumber': () => require('../cucumber'),
6
8
  '@playwright/test': () => require('../playwright'),
@@ -69,29 +69,17 @@ function patch (http, methodName) {
69
69
  try {
70
70
  const req = request.call(this, options, callback)
71
71
  const emit = req.emit
72
-
73
- const requestSetTimeout = req.setTimeout
72
+ const setTimeout = req.setTimeout
74
73
 
75
74
  ctx.req = req
76
75
 
77
76
  // tracked to accurately discern custom request socket timeout
78
77
  let customRequestTimeout = false
79
-
80
78
  req.setTimeout = function () {
81
79
  customRequestTimeout = true
82
- return requestSetTimeout.apply(this, arguments)
80
+ return setTimeout.apply(this, arguments)
83
81
  }
84
82
 
85
- req.on('socket', socket => {
86
- if (socket) {
87
- const socketSetTimeout = socket.setTimeout
88
- socket.setTimeout = function () {
89
- customRequestTimeout = true
90
- return socketSetTimeout.apply(this, arguments)
91
- }
92
- }
93
- })
94
-
95
83
  req.emit = function (eventName, arg) {
96
84
  switch (eventName) {
97
85
  case 'response': {
@@ -65,7 +65,7 @@ function wrapRenderToHTML (renderToHTML) {
65
65
 
66
66
  function wrapRenderErrorToHTML (renderErrorToHTML) {
67
67
  return function (err, req, res, pathname, query) {
68
- return instrument(req, res, () => renderErrorToHTML.apply(this, arguments))
68
+ return instrument(req, res, err, () => renderErrorToHTML.apply(this, arguments))
69
69
  }
70
70
  }
71
71
 
@@ -76,8 +76,8 @@ function wrapRenderToResponse (renderToResponse) {
76
76
  }
77
77
 
78
78
  function wrapRenderErrorToResponse (renderErrorToResponse) {
79
- return function (ctx) {
80
- return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
79
+ return function (ctx, err) {
80
+ return instrument(ctx.req, ctx.res, err, () => renderErrorToResponse.apply(this, arguments))
81
81
  }
82
82
  }
83
83
 
@@ -111,13 +111,23 @@ function getPageFromPath (page, dynamicRoutes = []) {
111
111
  return getPagePath(page)
112
112
  }
113
113
 
114
- function instrument (req, res, handler) {
114
+ function instrument (req, res, error, handler) {
115
+ if (typeof error === 'function') {
116
+ handler = error
117
+ error = null
118
+ }
119
+
115
120
  req = req.originalRequest || req
116
121
  res = res.originalResponse || res
117
122
 
118
123
  // TODO support middleware properly in the future?
119
124
  const isMiddleware = req.headers[MIDDLEWARE_HEADER]
120
- if (isMiddleware || requests.has(req)) return handler()
125
+ if (isMiddleware || requests.has(req)) {
126
+ if (error) {
127
+ errorChannel.publish({ error })
128
+ }
129
+ return handler()
130
+ }
121
131
 
122
132
  requests.add(req)
123
133
 
@@ -22,7 +22,7 @@ const dispatchReceiveCh = channel('apm:rhea:receive:dispatch')
22
22
  const errorReceiveCh = channel('apm:rhea:receive:error')
23
23
  const finishReceiveCh = channel('apm:rhea:receive:finish')
24
24
 
25
- const contexts = new WeakMap()
25
+ const contexts = new WeakMap() // key: delivery Fn, val: context
26
26
 
27
27
  addHook({ name: 'rhea', versions: ['>=1'] }, rhea => {
28
28
  shimmer.wrap(rhea.message, 'encode', encode => function (msg) {
@@ -52,7 +52,8 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/link.js' }, obj => {
52
52
  startSendCh.publish({ targetAddress, host, port, msg })
53
53
  const delivery = send.apply(this, arguments)
54
54
  const context = {
55
- asyncResource
55
+ asyncResource,
56
+ connection: this.connection
56
57
  }
57
58
  contexts.set(delivery, context)
58
59
 
@@ -80,7 +81,8 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/link.js' }, obj => {
80
81
 
81
82
  if (msgObj.delivery) {
82
83
  const context = {
83
- asyncResource
84
+ asyncResource,
85
+ connection: this.connection
84
86
  }
85
87
  contexts.set(msgObj.delivery, context)
86
88
  msgObj.delivery.update = wrapDeliveryUpdate(msgObj.delivery, msgObj.delivery.update)
@@ -114,7 +116,7 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/connection.js' }, Connecti
114
116
 
115
117
  asyncResource.runInAsyncScope(() => {
116
118
  errorReceiveCh.publish(error)
117
- beforeFinish(delivery, null)
119
+ exports.beforeFinish(delivery, null)
118
120
  finishReceiveCh.publish()
119
121
  })
120
122
  })
@@ -187,7 +189,7 @@ function patchCircularBuffer (proto, Session) {
187
189
  const state = remoteState && remoteState.constructor
188
190
  ? entry.remote_state.constructor.composite_type : undefined
189
191
  asyncResource.runInAsyncScope(() => {
190
- beforeFinish(entry, state)
192
+ exports.beforeFinish(entry, state)
191
193
  finishSendCh.publish()
192
194
  })
193
195
  }
@@ -217,13 +219,13 @@ function addToInFlightDeliveries (connection, delivery) {
217
219
  }
218
220
 
219
221
  function beforeFinish (delivery, state) {
220
- const obj = contexts.get(delivery)
221
- if (obj) {
222
+ const context = contexts.get(delivery)
223
+ if (context) {
222
224
  if (state) {
223
225
  dispatchReceiveCh.publish({ state })
224
226
  }
225
- if (obj.connection && obj.connection[inFlightDeliveries]) {
226
- obj.connection[inFlightDeliveries].delete(delivery)
227
+ if (context.connection && context.connection[inFlightDeliveries]) {
228
+ context.connection[inFlightDeliveries].delete(delivery)
227
229
  }
228
230
  }
229
231
  }
@@ -238,3 +240,7 @@ function getStateFromData (stateData) {
238
240
  }
239
241
  }
240
242
  }
243
+
244
+ module.exports.inFlightDeliveries = inFlightDeliveries
245
+ module.exports.beforeFinish = beforeFinish
246
+ module.exports.contexts = contexts
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const dc = require('dc-polyfill')
4
5
 
5
6
  const collapsedPathSym = Symbol('collapsedPaths')
6
7
 
@@ -14,8 +15,6 @@ class GraphQLResolvePlugin extends TracingPlugin {
14
15
  if (!shouldInstrument(this.config, path)) return
15
16
  const computedPathString = path.join('.')
16
17
 
17
- addResolver(context, info, args)
18
-
19
18
  if (this.config.collapse) {
20
19
  if (!context[collapsedPathSym]) {
21
20
  context[collapsedPathSym] = {}
@@ -55,6 +54,10 @@ class GraphQLResolvePlugin extends TracingPlugin {
55
54
  span.setTag(`graphql.variables.${name}`, variables[name])
56
55
  })
57
56
  }
57
+
58
+ if (this.resolverStartCh.hasSubscribers) {
59
+ this.resolverStartCh.publish({ context, resolverInfo: getResolverInfo(info, args) })
60
+ }
58
61
  }
59
62
 
60
63
  constructor (...args) {
@@ -69,6 +72,8 @@ class GraphQLResolvePlugin extends TracingPlugin {
69
72
  field.finishTime = span._getTime ? span._getTime() : 0
70
73
  field.error = field.error || err
71
74
  })
75
+
76
+ this.resolverStartCh = dc.channel('datadog:graphql:resolver:start')
72
77
  }
73
78
 
74
79
  configure (config) {
@@ -109,28 +114,31 @@ function withCollapse (responsePathAsArray) {
109
114
  }
110
115
  }
111
116
 
112
- function addResolver (context, info, args) {
113
- if (info.rootValue && !info.rootValue[info.fieldName]) {
114
- return
115
- }
117
+ function getResolverInfo (info, args) {
118
+ let resolverInfo = null
119
+ const resolverVars = {}
116
120
 
117
- if (!context.resolvers) {
118
- context.resolvers = {}
121
+ if (args && Object.keys(args).length) {
122
+ Object.assign(resolverVars, args)
119
123
  }
120
124
 
121
- const resolvers = context.resolvers
122
-
123
- if (!resolvers[info.fieldName]) {
124
- if (args && Object.keys(args).length) {
125
- resolvers[info.fieldName] = [args]
126
- } else {
127
- resolvers[info.fieldName] = []
125
+ const directives = info.fieldNodes[0].directives
126
+ for (const directive of directives) {
127
+ const argList = {}
128
+ for (const argument of directive['arguments']) {
129
+ argList[argument.name.value] = argument.value.value
128
130
  }
129
- } else {
130
- if (args && Object.keys(args).length) {
131
- resolvers[info.fieldName].push(args)
131
+
132
+ if (Object.keys(argList).length) {
133
+ resolverVars[directive.name.value] = argList
132
134
  }
133
135
  }
136
+
137
+ if (Object.keys(resolverVars).length) {
138
+ resolverInfo = { [info.fieldName]: resolverVars }
139
+ }
140
+
141
+ return resolverInfo
134
142
  }
135
143
 
136
144
  module.exports = GraphQLResolvePlugin
@@ -121,7 +121,7 @@ class HttpClientPlugin extends ClientPlugin {
121
121
  } else {
122
122
  // conditions for no error:
123
123
  // 1. not using a custom agent instance with custom timeout specified
124
- // 2. no invocation of `req.setTimeout` or `socket.setTimeout`
124
+ // 2. no invocation of `req.setTimeout`
125
125
  if (!args.options.agent?.options.timeout && !customRequestTimeout) return
126
126
 
127
127
  span.setTag('error', 1)
@@ -6,6 +6,8 @@ const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
  const { COMPONENT } = require('../../dd-trace/src/constants')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
8
 
9
+ const errorPages = ['/404', '/500', '/_error', '/_not-found']
10
+
9
11
  class NextPlugin extends ServerPlugin {
10
12
  static get id () {
11
13
  return 'next'
@@ -40,6 +42,13 @@ class NextPlugin extends ServerPlugin {
40
42
  }
41
43
 
42
44
  error ({ span, error }) {
45
+ if (!span) {
46
+ const store = storage.getStore()
47
+ if (!store) return
48
+
49
+ span = store.span
50
+ }
51
+
43
52
  this.addError(error, span)
44
53
  }
45
54
 
@@ -50,10 +59,20 @@ class NextPlugin extends ServerPlugin {
50
59
 
51
60
  const span = store.span
52
61
  const error = span.context()._tags['error']
53
-
54
- if (!this.config.validateStatus(res.statusCode) && !error) {
55
- span.setTag('error', req.error || nextRequest.error || true)
56
- web.addError(req, req.error || nextRequest.error || true)
62
+ const requestError = req.error || nextRequest.error
63
+
64
+ if (requestError) {
65
+ // prioritize user-set errors from API routes
66
+ span.setTag('error', requestError)
67
+ web.addError(req, requestError)
68
+ } else if (error) {
69
+ // general error handling
70
+ span.setTag('error', error)
71
+ web.addError(req, requestError || error)
72
+ } else if (!this.config.validateStatus(res.statusCode)) {
73
+ // where there's no error, we still need to validate status
74
+ span.setTag('error', true)
75
+ web.addError(req, true)
57
76
  }
58
77
 
59
78
  span.addTags({
@@ -73,14 +92,21 @@ class NextPlugin extends ServerPlugin {
73
92
  const span = store.span
74
93
  const req = this._requests.get(span)
75
94
 
95
+ // safeguard against missing req in complicated timeout scenarios
96
+ if (!req) return
97
+
76
98
  // Only use error page names if there's not already a name
77
99
  const current = span.context()._tags['next.page']
78
- if (current && ['/404', '/500', '/_error', '/_not-found'].includes(page)) {
100
+ const isErrorPage = errorPages.includes(page)
101
+
102
+ if (current && isErrorPage) {
79
103
  return
80
104
  }
81
105
 
82
106
  // remove ending /route or /page for appDir projects
83
- if (isAppPath) page = page.substring(0, page.lastIndexOf('/'))
107
+ // need to check if not an error page too, as those are marked as app directory
108
+ // in newer versions
109
+ if (isAppPath && !isErrorPage) page = page.substring(0, page.lastIndexOf('/'))
84
110
 
85
111
  // handle static resource
86
112
  if (isStatic) {
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ const Activation = {
4
+ ONECLICK: 'OneClick',
5
+ ENABLED: 'Enabled',
6
+ DISABLED: 'Disabled',
7
+
8
+ fromConfig (config) {
9
+ switch (config.appsec.enabled) {
10
+ // ASM is activated by an env var DD_APPSEC_ENABLED=true
11
+ case true:
12
+ return Activation.ENABLED
13
+
14
+ // ASM is disabled by an env var DD_APPSEC_ENABLED=false
15
+ case false:
16
+ return Activation.DISABLED
17
+
18
+ // ASM is activated by one click remote config
19
+ case undefined:
20
+ return Activation.ONECLICK
21
+
22
+ // Any other value should never occur
23
+ default:
24
+ return Activation.DISABLED
25
+ }
26
+ }
27
+ }
28
+
29
+ module.exports = Activation
@@ -13,6 +13,7 @@ module.exports = {
13
13
  HTTP_INCOMING_RESPONSE_HEADERS: 'server.response.headers.no_cookies',
14
14
  // TODO: 'server.response.trailers',
15
15
  HTTP_INCOMING_GRAPHQL_RESOLVERS: 'graphql.server.all_resolvers',
16
+ HTTP_INCOMING_GRAPHQL_RESOLVER: 'graphql.server.resolver',
16
17
 
17
18
  HTTP_CLIENT_IP: 'http.client_ip',
18
19
 
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+
5
+ let enabled
6
+ let requestSampling
7
+
8
+ function configure ({ apiSecurity }) {
9
+ enabled = apiSecurity.enabled
10
+ setRequestSampling(apiSecurity.requestSampling)
11
+ }
12
+
13
+ function disable () {
14
+ enabled = false
15
+ }
16
+
17
+ function setRequestSampling (sampling) {
18
+ requestSampling = parseRequestSampling(sampling)
19
+ }
20
+
21
+ function parseRequestSampling (requestSampling) {
22
+ let parsed = parseFloat(requestSampling)
23
+
24
+ if (isNaN(parsed)) {
25
+ log.warn(`Incorrect API Security request sampling value: ${requestSampling}`)
26
+
27
+ parsed = 0
28
+ } else {
29
+ parsed = Math.min(1, Math.max(0, parsed))
30
+ }
31
+
32
+ return parsed
33
+ }
34
+
35
+ function sampleRequest () {
36
+ if (!enabled || !requestSampling) {
37
+ return false
38
+ }
39
+
40
+ return Math.random() <= requestSampling
41
+ }
42
+
43
+ module.exports = {
44
+ configure,
45
+ disable,
46
+ setRequestSampling,
47
+ sampleRequest
48
+ }
@@ -5,7 +5,10 @@ const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta n
5
5
 
6
6
  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."}]}`
7
7
 
8
+ const graphqlJson = `{"errors":[{"message":"You've been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}]}`
9
+
8
10
  module.exports = {
9
11
  html,
10
- json
12
+ json,
13
+ graphqlJson
11
14
  }