dd-trace 5.17.0 → 5.18.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 (63) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ext/exporters.d.ts +1 -1
  3. package/index.d.ts +47 -1
  4. package/init.js +40 -1
  5. package/initialize.mjs +8 -5
  6. package/package.json +24 -20
  7. package/packages/datadog-core/src/storage/index.js +1 -10
  8. package/packages/datadog-esbuild/index.js +5 -1
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
  10. package/packages/datadog-instrumentations/src/cucumber.js +76 -34
  11. package/packages/datadog-instrumentations/src/helpers/hook.js +8 -3
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
  14. package/packages/datadog-instrumentations/src/helpers/register.js +56 -5
  15. package/packages/datadog-instrumentations/src/mocha/main.js +12 -1
  16. package/packages/datadog-instrumentations/src/mocha/utils.js +58 -14
  17. package/packages/datadog-instrumentations/src/mocha/worker.js +1 -0
  18. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  19. package/packages/datadog-instrumentations/src/vitest.js +303 -0
  20. package/packages/datadog-plugin-aws-sdk/src/base.js +8 -1
  21. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  22. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +6 -1
  23. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +23 -5
  24. package/packages/datadog-plugin-child_process/src/index.js +1 -1
  25. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  26. package/packages/datadog-plugin-mocha/src/index.js +25 -4
  27. package/packages/datadog-plugin-openai/src/index.js +52 -30
  28. package/packages/datadog-plugin-openai/src/token-estimator.js +20 -0
  29. package/packages/datadog-plugin-vitest/src/index.js +156 -0
  30. package/packages/dd-trace/src/appsec/iast/path-line.js +2 -19
  31. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -0
  32. package/packages/dd-trace/src/appsec/index.js +1 -1
  33. package/packages/dd-trace/src/appsec/rasp.js +32 -5
  34. package/packages/dd-trace/src/appsec/recommended.json +208 -3
  35. package/packages/dd-trace/src/appsec/reporter.js +64 -20
  36. package/packages/dd-trace/src/appsec/sdk/track_event.js +3 -0
  37. package/packages/dd-trace/src/appsec/stack_trace.js +90 -0
  38. package/packages/dd-trace/src/appsec/standalone.js +130 -0
  39. package/packages/dd-trace/src/appsec/telemetry.js +33 -1
  40. package/packages/dd-trace/src/appsec/waf/index.js +2 -2
  41. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +2 -2
  42. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  43. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  44. package/packages/dd-trace/src/config.js +110 -40
  45. package/packages/dd-trace/src/constants.js +3 -1
  46. package/packages/dd-trace/src/datastreams/processor.js +2 -1
  47. package/packages/dd-trace/src/exporters/agent/index.js +2 -2
  48. package/packages/dd-trace/src/format.js +1 -0
  49. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -0
  50. package/packages/dd-trace/src/opentracing/span.js +4 -1
  51. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  52. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -0
  53. package/packages/dd-trace/src/plugins/index.js +2 -0
  54. package/packages/dd-trace/src/plugins/util/test.js +5 -1
  55. package/packages/dd-trace/src/priority_sampler.js +2 -5
  56. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  57. package/packages/dd-trace/src/proxy.js +3 -1
  58. package/packages/dd-trace/src/rate_limiter.js +2 -2
  59. package/packages/dd-trace/src/span_stats.js +4 -3
  60. package/packages/dd-trace/src/telemetry/init-telemetry.js +75 -0
  61. package/packages/dd-trace/src/tracer.js +2 -2
  62. package/packages/dd-trace/src/util.js +6 -1
  63. package/packages/datadog-core/src/storage/async_hooks.js +0 -49
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "2.2",
3
3
  "metadata": {
4
- "rules_version": "1.11.0"
4
+ "rules_version": "1.12.0"
5
5
  },
6
6
  "rules": [
7
7
  {
@@ -1921,7 +1921,6 @@
1921
1921
  "$ifs",
1922
1922
  "$oldpwd",
1923
1923
  "$ostype",
1924
- "$path",
1925
1924
  "$pwd",
1926
1925
  "dev/fd/",
1927
1926
  "dev/null",
@@ -5849,7 +5848,8 @@
5849
5848
  "/website.php",
5850
5849
  "/stats.php",
5851
5850
  "/assets/plugins/mp3_id/mp3_id.php",
5852
- "/siteminderagent/forms/smpwservices.fcc"
5851
+ "/siteminderagent/forms/smpwservices.fcc",
5852
+ "/eval-stdin.php"
5853
5853
  ]
5854
5854
  }
5855
5855
  }
@@ -6236,6 +6236,155 @@
6236
6236
  ],
6237
6237
  "transformers": []
6238
6238
  },
6239
+ {
6240
+ "id": "rasp-930-100",
6241
+ "name": "Local file inclusion exploit",
6242
+ "enabled": false,
6243
+ "tags": {
6244
+ "type": "lfi",
6245
+ "category": "vulnerability_trigger",
6246
+ "cwe": "22",
6247
+ "capec": "1000/255/153/126",
6248
+ "confidence": "0",
6249
+ "module": "rasp"
6250
+ },
6251
+ "conditions": [
6252
+ {
6253
+ "parameters": {
6254
+ "resource": [
6255
+ {
6256
+ "address": "server.io.fs.file"
6257
+ }
6258
+ ],
6259
+ "params": [
6260
+ {
6261
+ "address": "server.request.query"
6262
+ },
6263
+ {
6264
+ "address": "server.request.body"
6265
+ },
6266
+ {
6267
+ "address": "server.request.path_params"
6268
+ },
6269
+ {
6270
+ "address": "grpc.server.request.message"
6271
+ },
6272
+ {
6273
+ "address": "graphql.server.all_resolvers"
6274
+ },
6275
+ {
6276
+ "address": "graphql.server.resolver"
6277
+ }
6278
+ ]
6279
+ },
6280
+ "operator": "lfi_detector"
6281
+ }
6282
+ ],
6283
+ "transformers": [],
6284
+ "on_match": [
6285
+ "stack_trace"
6286
+ ]
6287
+ },
6288
+ {
6289
+ "id": "rasp-934-100",
6290
+ "name": "Server-side request forgery exploit",
6291
+ "enabled": false,
6292
+ "tags": {
6293
+ "type": "ssrf",
6294
+ "category": "vulnerability_trigger",
6295
+ "cwe": "918",
6296
+ "capec": "1000/225/115/664",
6297
+ "confidence": "0",
6298
+ "module": "rasp"
6299
+ },
6300
+ "conditions": [
6301
+ {
6302
+ "parameters": {
6303
+ "resource": [
6304
+ {
6305
+ "address": "server.io.net.url"
6306
+ }
6307
+ ],
6308
+ "params": [
6309
+ {
6310
+ "address": "server.request.query"
6311
+ },
6312
+ {
6313
+ "address": "server.request.body"
6314
+ },
6315
+ {
6316
+ "address": "server.request.path_params"
6317
+ },
6318
+ {
6319
+ "address": "grpc.server.request.message"
6320
+ },
6321
+ {
6322
+ "address": "graphql.server.all_resolvers"
6323
+ },
6324
+ {
6325
+ "address": "graphql.server.resolver"
6326
+ }
6327
+ ]
6328
+ },
6329
+ "operator": "ssrf_detector"
6330
+ }
6331
+ ],
6332
+ "transformers": [],
6333
+ "on_match": [
6334
+ "stack_trace"
6335
+ ]
6336
+ },
6337
+ {
6338
+ "id": "rasp-942-100",
6339
+ "name": "SQL injection exploit",
6340
+ "enabled": false,
6341
+ "tags": {
6342
+ "type": "sql_injection",
6343
+ "category": "vulnerability_trigger",
6344
+ "cwe": "89",
6345
+ "capec": "1000/152/248/66",
6346
+ "confidence": "0",
6347
+ "module": "rasp"
6348
+ },
6349
+ "conditions": [
6350
+ {
6351
+ "parameters": {
6352
+ "resource": [
6353
+ {
6354
+ "address": "server.db.statement"
6355
+ }
6356
+ ],
6357
+ "params": [
6358
+ {
6359
+ "address": "server.request.query"
6360
+ },
6361
+ {
6362
+ "address": "server.request.body"
6363
+ },
6364
+ {
6365
+ "address": "server.request.path_params"
6366
+ },
6367
+ {
6368
+ "address": "graphql.server.all_resolvers"
6369
+ },
6370
+ {
6371
+ "address": "graphql.server.resolver"
6372
+ }
6373
+ ],
6374
+ "db_type": [
6375
+ {
6376
+ "address": "server.db.system"
6377
+ }
6378
+ ]
6379
+ },
6380
+ "operator": "sqli_detector"
6381
+ }
6382
+ ],
6383
+ "transformers": [],
6384
+ "on_match": [
6385
+ "stack_trace"
6386
+ ]
6387
+ },
6239
6388
  {
6240
6389
  "id": "sqr-000-001",
6241
6390
  "name": "SSRF: Try to access the credential manager of the main cloud services",
@@ -8391,6 +8540,34 @@
8391
8540
  }
8392
8541
  ],
8393
8542
  "scanners": [
8543
+ {
8544
+ "id": "406f8606-52c4-4663-8db9-df70f9e8766c",
8545
+ "name": "ZIP Code",
8546
+ "key": {
8547
+ "operator": "match_regex",
8548
+ "parameters": {
8549
+ "regex": "\\b(?:zip|postal)\\b",
8550
+ "options": {
8551
+ "case_sensitive": false,
8552
+ "min_length": 3
8553
+ }
8554
+ }
8555
+ },
8556
+ "value": {
8557
+ "operator": "match_regex",
8558
+ "parameters": {
8559
+ "regex": "^[0-9]{5}(?:-[0-9]{4})?$",
8560
+ "options": {
8561
+ "case_sensitive": true,
8562
+ "min_length": 5
8563
+ }
8564
+ }
8565
+ },
8566
+ "tags": {
8567
+ "type": "zipcode",
8568
+ "category": "address"
8569
+ }
8570
+ },
8394
8571
  {
8395
8572
  "id": "JU1sRk3mSzqSUJn6GrVn7g",
8396
8573
  "name": "American Express Card Scanner (4+4+4+3 digits)",
@@ -9157,6 +9334,34 @@
9157
9334
  "category": "payment"
9158
9335
  }
9159
9336
  },
9337
+ {
9338
+ "id": "18b608bd7a764bff5b2344c0",
9339
+ "name": "Phone number",
9340
+ "key": {
9341
+ "operator": "match_regex",
9342
+ "parameters": {
9343
+ "regex": "\\bphone|number|mobile\\b",
9344
+ "options": {
9345
+ "case_sensitive": false,
9346
+ "min_length": 3
9347
+ }
9348
+ }
9349
+ },
9350
+ "value": {
9351
+ "operator": "match_regex",
9352
+ "parameters": {
9353
+ "regex": "^(?:\\(\\+\\d{1,3}\\)|\\+\\d{1,3}|00\\d{1,3})?[-\\s\\.]?(?:\\(\\d{3}\\)[-\\s\\.]?)?(?:\\d[-\\s\\.]?){6,10}$",
9354
+ "options": {
9355
+ "case_sensitive": false,
9356
+ "min_length": 6
9357
+ }
9358
+ }
9359
+ },
9360
+ "tags": {
9361
+ "type": "phone",
9362
+ "category": "pii"
9363
+ }
9364
+ },
9160
9365
  {
9161
9366
  "id": "de0899e0cbaaa812bb624cf04c912071012f616d-mod",
9162
9367
  "name": "UK National Insurance Number Scanner",
@@ -7,11 +7,14 @@ const { ipHeaderList } = require('../plugins/util/ip_extractor')
7
7
  const {
8
8
  incrementWafInitMetric,
9
9
  updateWafRequestsMetricTags,
10
+ updateRaspRequestsMetricTags,
10
11
  incrementWafUpdatesMetric,
11
12
  incrementWafRequestsMetric,
12
13
  getRequestMetrics
13
14
  } = require('./telemetry')
14
15
  const zlib = require('zlib')
16
+ const { MANUAL_KEEP } = require('../../../../ext/tags')
17
+ const standalone = require('./standalone')
15
18
 
16
19
  // default limiter, configurable with setRateLimit()
17
20
  let limiter = new Limiter(100)
@@ -26,19 +29,17 @@ const contentHeaderList = [
26
29
  'content-language'
27
30
  ]
28
31
 
29
- const REQUEST_HEADERS_MAP = mapHeaderAndTags([
32
+ const EVENT_HEADERS_MAP = mapHeaderAndTags([
30
33
  ...ipHeaderList,
31
34
  'forwarded',
32
35
  'via',
33
36
  ...contentHeaderList,
34
37
  'host',
35
- 'user-agent',
36
- 'accept',
37
38
  'accept-encoding',
38
39
  'accept-language'
39
40
  ], 'http.request.headers.')
40
41
 
41
- const IDENTIFICATION_HEADERS_MAP = mapHeaderAndTags([
42
+ const identificationHeaders = [
42
43
  'x-amzn-trace-id',
43
44
  'cloudfront-viewer-ja3-fingerprint',
44
45
  'cf-ray',
@@ -47,6 +48,14 @@ const IDENTIFICATION_HEADERS_MAP = mapHeaderAndTags([
47
48
  'x-sigsci-requestid',
48
49
  'x-sigsci-tags',
49
50
  'akamai-user-risk'
51
+ ]
52
+
53
+ // these request headers are always collected - it breaks the expected spec orders
54
+ const REQUEST_HEADERS_MAP = mapHeaderAndTags([
55
+ 'content-type',
56
+ 'user-agent',
57
+ 'accept',
58
+ ...identificationHeaders
50
59
  ], 'http.request.headers.')
51
60
 
52
61
  const RESPONSE_HEADERS_MAP = mapHeaderAndTags(contentHeaderList, 'http.response.headers.')
@@ -87,12 +96,12 @@ function reportWafInit (wafVersion, rulesVersion, diagnosticsRules = {}) {
87
96
  metricsQueue.set('_dd.appsec.event_rules.errors', JSON.stringify(diagnosticsRules.errors))
88
97
  }
89
98
 
90
- metricsQueue.set('manual.keep', 'true')
99
+ metricsQueue.set(MANUAL_KEEP, 'true')
91
100
 
92
101
  incrementWafInitMetric(wafVersion, rulesVersion)
93
102
  }
94
103
 
95
- function reportMetrics (metrics) {
104
+ function reportMetrics (metrics, raspRuleType) {
96
105
  const store = storage.getStore()
97
106
  const rootSpan = store?.req && web.root(store.req)
98
107
  if (!rootSpan) return
@@ -100,8 +109,11 @@ function reportMetrics (metrics) {
100
109
  if (metrics.rulesVersion) {
101
110
  rootSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
102
111
  }
103
-
104
- updateWafRequestsMetricTags(metrics, store.req)
112
+ if (raspRuleType) {
113
+ updateRaspRequestsMetricTags(metrics, store.req, raspRuleType)
114
+ } else {
115
+ updateWafRequestsMetricTags(metrics, store.req)
116
+ }
105
117
  }
106
118
 
107
119
  function reportAttack (attackData) {
@@ -112,12 +124,14 @@ function reportAttack (attackData) {
112
124
 
113
125
  const currentTags = rootSpan.context()._tags
114
126
 
115
- const newTags = filterHeaders(req.headers, REQUEST_HEADERS_MAP)
116
-
117
- newTags['appsec.event'] = 'true'
127
+ const newTags = {
128
+ 'appsec.event': 'true'
129
+ }
118
130
 
119
131
  if (limiter.isAllowed()) {
120
- newTags['manual.keep'] = 'true' // TODO: figure out how to keep appsec traces with sampling revamp
132
+ newTags[MANUAL_KEEP] = 'true'
133
+
134
+ standalone.sample(rootSpan)
121
135
  }
122
136
 
123
137
  // TODO: maybe add this to format.js later (to take decision as late as possible)
@@ -134,11 +148,6 @@ function reportAttack (attackData) {
134
148
  newTags['_dd.appsec.json'] = '{"triggers":' + attackData + '}'
135
149
  }
136
150
 
137
- const ua = newTags['http.request.headers.user-agent']
138
- if (ua) {
139
- newTags['http.useragent'] = ua
140
- }
141
-
142
151
  newTags['network.client.ip'] = req.socket.remoteAddress
143
152
 
144
153
  rootSpan.addTags(newTags)
@@ -168,6 +177,8 @@ function finishRequest (req, res) {
168
177
  if (metricsQueue.size) {
169
178
  rootSpan.addTags(Object.fromEntries(metricsQueue))
170
179
 
180
+ standalone.sample(rootSpan)
181
+
171
182
  metricsQueue.clear()
172
183
  }
173
184
 
@@ -180,22 +191,55 @@ function finishRequest (req, res) {
180
191
  rootSpan.setTag('_dd.appsec.waf.duration_ext', metrics.durationExt)
181
192
  }
182
193
 
194
+ if (metrics?.raspDuration) {
195
+ rootSpan.setTag('_dd.appsec.rasp.duration', metrics.raspDuration)
196
+ }
197
+
198
+ if (metrics?.raspDurationExt) {
199
+ rootSpan.setTag('_dd.appsec.rasp.duration_ext', metrics.raspDurationExt)
200
+ }
201
+
202
+ if (metrics?.raspEvalCount) {
203
+ rootSpan.setTag('_dd.appsec.rasp.rule.eval', metrics.raspEvalCount)
204
+ }
205
+
183
206
  incrementWafRequestsMetric(req)
184
207
 
185
208
  // collect some headers even when no attack is detected
186
- rootSpan.addTags(filterHeaders(req.headers, IDENTIFICATION_HEADERS_MAP))
209
+ const mandatoryTags = filterHeaders(req.headers, REQUEST_HEADERS_MAP)
210
+ const ua = mandatoryTags['http.request.headers.user-agent']
211
+ if (ua) {
212
+ mandatoryTags['http.useragent'] = ua
213
+ }
214
+ rootSpan.addTags(mandatoryTags)
187
215
 
188
- if (!rootSpan.context()._tags['appsec.event']) return
216
+ const tags = rootSpan.context()._tags
217
+ if (!shouldCollectEventHeaders(tags)) return
189
218
 
190
219
  const newTags = filterHeaders(res.getHeaders(), RESPONSE_HEADERS_MAP)
220
+ Object.assign(newTags, filterHeaders(req.headers, EVENT_HEADERS_MAP))
191
221
 
192
- if (req.route && typeof req.route.path === 'string') {
222
+ if (tags['appsec.event'] === 'true' && typeof req.route?.path === 'string') {
193
223
  newTags['http.endpoint'] = req.route.path
194
224
  }
195
225
 
196
226
  rootSpan.addTags(newTags)
197
227
  }
198
228
 
229
+ function shouldCollectEventHeaders (tags = {}) {
230
+ if (tags['appsec.event'] === 'true') {
231
+ return true
232
+ }
233
+
234
+ for (const tagName of Object.keys(tags)) {
235
+ if (tagName.startsWith('appsec.events.')) {
236
+ return true
237
+ }
238
+ }
239
+
240
+ return false
241
+ }
242
+
199
243
  function setRateLimit (rateLimit) {
200
244
  limiter = new Limiter(rateLimit)
201
245
  }
@@ -4,6 +4,7 @@ const log = require('../../log')
4
4
  const { getRootSpan } = require('./utils')
5
5
  const { MANUAL_KEEP } = require('../../../../../ext/tags')
6
6
  const { setUserTags } = require('./set_user')
7
+ const standalone = require('../standalone')
7
8
 
8
9
  function trackUserLoginSuccessEvent (tracer, user, metadata) {
9
10
  // TODO: better user check here and in _setUser() ?
@@ -73,6 +74,8 @@ function trackEvent (eventName, fields, sdkMethodName, rootSpan, mode) {
73
74
  }
74
75
 
75
76
  rootSpan.addTags(tags)
77
+
78
+ standalone.sample(rootSpan)
76
79
  }
77
80
 
78
81
  module.exports = {
@@ -0,0 +1,90 @@
1
+ 'use strict'
2
+
3
+ const { calculateDDBasePath } = require('../util')
4
+
5
+ const ddBasePath = calculateDDBasePath(__dirname)
6
+
7
+ const LIBRARY_FRAMES_BUFFER = 20
8
+
9
+ function getCallSiteList (maxDepth = 100) {
10
+ const previousPrepareStackTrace = Error.prepareStackTrace
11
+ const previousStackTraceLimit = Error.stackTraceLimit
12
+ let callsiteList
13
+ Error.stackTraceLimit = maxDepth
14
+
15
+ try {
16
+ Error.prepareStackTrace = function (_, callsites) {
17
+ callsiteList = callsites
18
+ }
19
+ const e = new Error()
20
+ e.stack
21
+ } finally {
22
+ Error.prepareStackTrace = previousPrepareStackTrace
23
+ Error.stackTraceLimit = previousStackTraceLimit
24
+ }
25
+
26
+ return callsiteList
27
+ }
28
+
29
+ function filterOutFramesFromLibrary (callSiteList) {
30
+ return callSiteList.filter(callSite => !callSite.getFileName()?.startsWith(ddBasePath))
31
+ }
32
+
33
+ function getFramesForMetaStruct (callSiteList, maxDepth = 32) {
34
+ const filteredFrames = filterOutFramesFromLibrary(callSiteList)
35
+
36
+ const half = filteredFrames.length > maxDepth ? Math.round(maxDepth / 2) : Infinity
37
+
38
+ const indexedFrames = []
39
+ for (let i = 0; i < Math.min(filteredFrames.length, maxDepth); i++) {
40
+ const index = i < half ? i : i + filteredFrames.length - maxDepth
41
+ const callSite = filteredFrames[index]
42
+ indexedFrames.push({
43
+ id: index,
44
+ file: callSite.getFileName(),
45
+ line: callSite.getLineNumber(),
46
+ column: callSite.getColumnNumber(),
47
+ function: callSite.getFunctionName(),
48
+ class_name: callSite.getTypeName()
49
+ })
50
+ }
51
+
52
+ return indexedFrames
53
+ }
54
+
55
+ function reportStackTrace (rootSpan, stackId, maxDepth, maxStackTraces, callSiteListGetter = getCallSiteList) {
56
+ if (!rootSpan) return
57
+
58
+ if (maxStackTraces < 1 || (rootSpan.meta_struct?.['_dd.stack']?.exploit?.length ?? 0) < maxStackTraces) {
59
+ // Since some frames will be discarded because they come from tracer codebase, a buffer is added
60
+ // to the limit in order to get as close as `maxDepth` number of frames.
61
+ if (maxDepth < 1) maxDepth = Infinity
62
+ const callSiteList = callSiteListGetter(maxDepth + LIBRARY_FRAMES_BUFFER)
63
+ if (!Array.isArray(callSiteList)) return
64
+
65
+ if (!rootSpan.meta_struct) {
66
+ rootSpan.meta_struct = {}
67
+ }
68
+
69
+ if (!rootSpan.meta_struct['_dd.stack']) {
70
+ rootSpan.meta_struct['_dd.stack'] = {}
71
+ }
72
+
73
+ if (!rootSpan.meta_struct['_dd.stack'].exploit) {
74
+ rootSpan.meta_struct['_dd.stack'].exploit = []
75
+ }
76
+
77
+ const frames = getFramesForMetaStruct(callSiteList, maxDepth)
78
+
79
+ rootSpan.meta_struct['_dd.stack'].exploit.push({
80
+ id: stackId,
81
+ language: 'nodejs',
82
+ frames
83
+ })
84
+ }
85
+ }
86
+
87
+ module.exports = {
88
+ getCallSiteList,
89
+ reportStackTrace
90
+ }
@@ -0,0 +1,130 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+ const { USER_KEEP, AUTO_KEEP, AUTO_REJECT } = require('../../../../ext/priority')
5
+ const { MANUAL_KEEP } = require('../../../../ext/tags')
6
+ const PrioritySampler = require('../priority_sampler')
7
+ const RateLimiter = require('../rate_limiter')
8
+ const TraceState = require('../opentracing/propagation/tracestate')
9
+ const { hasOwn } = require('../util')
10
+ const { APM_TRACING_ENABLED_KEY, APPSEC_PROPAGATION_KEY, SAMPLING_MECHANISM_DEFAULT } = require('../constants')
11
+
12
+ const startCh = channel('dd-trace:span:start')
13
+ const injectCh = channel('dd-trace:span:inject')
14
+ const extractCh = channel('dd-trace:span:extract')
15
+
16
+ let enabled
17
+
18
+ class StandAloneAsmPrioritySampler extends PrioritySampler {
19
+ constructor (env) {
20
+ super(env, { sampleRate: 0, rateLimit: 0, rules: [] })
21
+
22
+ // let some regular APM traces go through, 1 per minute to keep alive the service
23
+ this._limiter = new RateLimiter(1, 'minute')
24
+ }
25
+
26
+ configure (env, config) {
27
+ // rules not supported
28
+ this._env = env
29
+ }
30
+
31
+ _getPriorityFromTags (tags, context) {
32
+ if (hasOwn(tags, MANUAL_KEEP) &&
33
+ tags[MANUAL_KEEP] !== false &&
34
+ hasOwn(context._trace.tags, APPSEC_PROPAGATION_KEY)
35
+ ) {
36
+ return USER_KEEP
37
+ }
38
+ }
39
+
40
+ _getPriorityFromAuto (span) {
41
+ const context = this._getContext(span)
42
+
43
+ context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
44
+
45
+ if (hasOwn(context._trace.tags, APPSEC_PROPAGATION_KEY)) {
46
+ return USER_KEEP
47
+ }
48
+
49
+ return this._isSampledByRateLimit(context) ? AUTO_KEEP : AUTO_REJECT
50
+ }
51
+ }
52
+
53
+ function onSpanStart ({ span, fields }) {
54
+ const tags = span.context?.()?._tags
55
+ if (!tags) return
56
+
57
+ const { parent } = fields
58
+ if (!parent || parent._isRemote) {
59
+ tags[APM_TRACING_ENABLED_KEY] = 0
60
+ }
61
+ }
62
+
63
+ function onSpanInject ({ spanContext, carrier }) {
64
+ if (!spanContext?._trace?.tags || !carrier) return
65
+
66
+ // do not inject trace and sampling if there is no appsec event
67
+ if (!hasOwn(spanContext._trace.tags, APPSEC_PROPAGATION_KEY)) {
68
+ for (const key in carrier) {
69
+ const lKey = key.toLowerCase()
70
+ if (lKey.startsWith('x-datadog')) {
71
+ delete carrier[key]
72
+ } else if (lKey === 'tracestate') {
73
+ const tracestate = TraceState.fromString(carrier[key])
74
+ tracestate.forVendor('dd', state => state.clear())
75
+ carrier[key] = tracestate.toString()
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ function onSpanExtract ({ spanContext = {} }) {
82
+ if (!spanContext._trace?.tags || !spanContext._sampling) return
83
+
84
+ // reset upstream priority if _dd.p.appsec is not found
85
+ if (!hasOwn(spanContext._trace.tags, APPSEC_PROPAGATION_KEY)) {
86
+ spanContext._sampling.priority = undefined
87
+ } else if (spanContext._sampling.priority !== USER_KEEP) {
88
+ spanContext._sampling.priority = USER_KEEP
89
+ }
90
+ }
91
+
92
+ function sample (span) {
93
+ const spanContext = span.context?.()
94
+ if (enabled && spanContext?._trace?.tags) {
95
+ spanContext._trace.tags[APPSEC_PROPAGATION_KEY] = '1'
96
+
97
+ // TODO: ask. can we reset here sampling like this?
98
+ if (spanContext._sampling?.priority < AUTO_KEEP) {
99
+ spanContext._sampling.priority = undefined
100
+ }
101
+ }
102
+ }
103
+
104
+ function configure (config) {
105
+ const configChanged = enabled !== config.appsec?.standalone?.enabled
106
+ if (!configChanged) return
107
+
108
+ enabled = config.appsec?.standalone?.enabled
109
+
110
+ let prioritySampler
111
+ if (enabled) {
112
+ startCh.subscribe(onSpanStart)
113
+ injectCh.subscribe(onSpanInject)
114
+ extractCh.subscribe(onSpanExtract)
115
+
116
+ prioritySampler = new StandAloneAsmPrioritySampler(config.env)
117
+ } else {
118
+ if (startCh.hasSubscribers) startCh.unsubscribe(onSpanStart)
119
+ if (injectCh.hasSubscribers) injectCh.unsubscribe(onSpanInject)
120
+ if (extractCh.hasSubscribers) extractCh.unsubscribe(onSpanExtract)
121
+ }
122
+
123
+ return prioritySampler
124
+ }
125
+
126
+ module.exports = {
127
+ configure,
128
+ sample,
129
+ StandAloneAsmPrioritySampler
130
+ }