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.
- package/LICENSE-3rdparty.csv +1 -0
- package/ext/exporters.d.ts +1 -1
- package/index.d.ts +47 -1
- package/init.js +40 -1
- package/initialize.mjs +8 -5
- package/package.json +24 -20
- package/packages/datadog-core/src/storage/index.js +1 -10
- package/packages/datadog-esbuild/index.js +5 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +76 -34
- package/packages/datadog-instrumentations/src/helpers/hook.js +8 -3
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +4 -3
- package/packages/datadog-instrumentations/src/helpers/register.js +56 -5
- package/packages/datadog-instrumentations/src/mocha/main.js +12 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +58 -14
- package/packages/datadog-instrumentations/src/mocha/worker.js +1 -0
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/vitest.js +303 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +8 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +23 -5
- package/packages/datadog-plugin-child_process/src/index.js +1 -1
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-mocha/src/index.js +25 -4
- package/packages/datadog-plugin-openai/src/index.js +52 -30
- package/packages/datadog-plugin-openai/src/token-estimator.js +20 -0
- package/packages/datadog-plugin-vitest/src/index.js +156 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +2 -19
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -0
- package/packages/dd-trace/src/appsec/index.js +1 -1
- package/packages/dd-trace/src/appsec/rasp.js +32 -5
- package/packages/dd-trace/src/appsec/recommended.json +208 -3
- package/packages/dd-trace/src/appsec/reporter.js +64 -20
- package/packages/dd-trace/src/appsec/sdk/track_event.js +3 -0
- package/packages/dd-trace/src/appsec/stack_trace.js +90 -0
- package/packages/dd-trace/src/appsec/standalone.js +130 -0
- package/packages/dd-trace/src/appsec/telemetry.js +33 -1
- package/packages/dd-trace/src/appsec/waf/index.js +2 -2
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/config.js +110 -40
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/datastreams/processor.js +2 -1
- package/packages/dd-trace/src/exporters/agent/index.js +2 -2
- package/packages/dd-trace/src/format.js +1 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -0
- package/packages/dd-trace/src/opentracing/span.js +4 -1
- package/packages/dd-trace/src/opentracing/tracer.js +2 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +7 -0
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +5 -1
- package/packages/dd-trace/src/priority_sampler.js +2 -5
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/proxy.js +3 -1
- package/packages/dd-trace/src/rate_limiter.js +2 -2
- package/packages/dd-trace/src/span_stats.js +4 -3
- package/packages/dd-trace/src/telemetry/init-telemetry.js +75 -0
- package/packages/dd-trace/src/tracer.js +2 -2
- package/packages/dd-trace/src/util.js +6 -1
- 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.
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
const newTags = {
|
|
128
|
+
'appsec.event': 'true'
|
|
129
|
+
}
|
|
118
130
|
|
|
119
131
|
if (limiter.isAllowed()) {
|
|
120
|
-
newTags[
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
+
}
|