dd-trace 5.24.0 → 5.25.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 (98) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +335 -0
  3. package/package.json +13 -7
  4. package/packages/datadog-code-origin/index.js +4 -4
  5. package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
  6. package/packages/datadog-esbuild/index.js +4 -2
  7. package/packages/datadog-instrumentations/src/amqplib.js +65 -5
  8. package/packages/datadog-instrumentations/src/child_process.js +135 -27
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  10. package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
  11. package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
  13. package/packages/datadog-instrumentations/src/multer.js +37 -0
  14. package/packages/datadog-instrumentations/src/openai.js +2 -2
  15. package/packages/datadog-instrumentations/src/url.js +84 -0
  16. package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
  17. package/packages/datadog-plugin-amqplib/src/consumer.js +4 -4
  18. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  19. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  20. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -0
  21. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  22. package/packages/datadog-plugin-fastify/src/code_origin.js +2 -2
  23. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +8 -1
  24. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
  25. package/packages/datadog-plugin-grpc/src/client.js +3 -0
  26. package/packages/datadog-plugin-grpc/src/server.js +3 -0
  27. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
  28. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -4
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
  30. package/packages/datadog-plugin-mocha/src/index.js +4 -1
  31. package/packages/datadog-plugin-openai/src/index.js +9 -1015
  32. package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
  33. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  34. package/packages/dd-trace/src/appsec/channels.js +3 -1
  35. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
  36. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
  37. package/packages/dd-trace/src/appsec/index.js +3 -0
  38. package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
  39. package/packages/dd-trace/src/appsec/rasp/index.js +3 -0
  40. package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
  41. package/packages/dd-trace/src/appsec/rasp/utils.js +3 -2
  42. package/packages/dd-trace/src/appsec/recommended.json +2 -4
  43. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
  44. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  45. package/packages/dd-trace/src/appsec/reporter.js +5 -4
  46. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -3
  47. package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
  48. package/packages/dd-trace/src/azure_metadata.js +120 -0
  49. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
  50. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
  51. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
  52. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
  53. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
  54. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
  55. package/packages/dd-trace/src/config.js +75 -6
  56. package/packages/dd-trace/src/constants.js +3 -1
  57. package/packages/dd-trace/src/datastreams/pathway.js +1 -0
  58. package/packages/dd-trace/src/debugger/devtools_client/index.js +9 -13
  59. package/packages/dd-trace/src/debugger/devtools_client/send.js +15 -1
  60. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +57 -23
  61. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +12 -2
  62. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +31 -20
  63. package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
  64. package/packages/dd-trace/src/debugger/devtools_client/state.js +11 -2
  65. package/packages/dd-trace/src/debugger/index.js +10 -3
  66. package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
  67. package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
  68. package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
  69. package/packages/dd-trace/src/llmobs/index.js +103 -0
  70. package/packages/dd-trace/src/llmobs/noop.js +82 -0
  71. package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
  72. package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
  73. package/packages/dd-trace/src/llmobs/sdk.js +377 -0
  74. package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
  75. package/packages/dd-trace/src/llmobs/storage.js +7 -0
  76. package/packages/dd-trace/src/llmobs/tagger.js +322 -0
  77. package/packages/dd-trace/src/llmobs/util.js +176 -0
  78. package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
  79. package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
  80. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
  81. package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
  82. package/packages/dd-trace/src/llmobs/writers/spans/base.js +49 -0
  83. package/packages/dd-trace/src/noop/proxy.js +3 -0
  84. package/packages/dd-trace/src/noop/span.js +3 -0
  85. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  86. package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
  87. package/packages/dd-trace/src/opentracing/propagation/text_map.js +73 -12
  88. package/packages/dd-trace/src/opentracing/span.js +12 -0
  89. package/packages/dd-trace/src/opentracing/tracer.js +8 -1
  90. package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
  91. package/packages/dd-trace/src/plugins/outbound.js +9 -0
  92. package/packages/dd-trace/src/priority_sampler.js +16 -0
  93. package/packages/dd-trace/src/profiling/config.js +3 -1
  94. package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
  95. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -1
  96. package/packages/dd-trace/src/proxy.js +8 -1
  97. package/packages/dd-trace/src/span_processor.js +5 -0
  98. package/packages/dd-trace/src/telemetry/index.js +11 -1
@@ -28,6 +28,8 @@ module.exports = {
28
28
  DB_STATEMENT: 'server.db.statement',
29
29
  DB_SYSTEM: 'server.db.system',
30
30
 
31
+ SHELL_COMMAND: 'server.sys.shell.cmd',
32
+
31
33
  LOGIN_SUCCESS: 'server.business_logic.users.login.success',
32
34
  LOGIN_FAILURE: 'server.business_logic.users.login.failure'
33
35
  }
@@ -6,6 +6,7 @@ const dc = require('dc-polyfill')
6
6
  module.exports = {
7
7
  bodyParser: dc.channel('datadog:body-parser:read:finish'),
8
8
  cookieParser: dc.channel('datadog:cookie-parser:read:finish'),
9
+ multerParser: dc.channel('datadog:multer:read:finish'),
9
10
  startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'),
10
11
  graphqlMiddlewareChannel: dc.tracingChannel('datadog:apollo:middleware'),
11
12
  apolloChannel: dc.tracingChannel('datadog:apollo:request'),
@@ -28,5 +29,6 @@ module.exports = {
28
29
  mysql2OuterQueryStart: dc.channel('datadog:mysql2:outerquery:start'),
29
30
  wafRunFinished: dc.channel('datadog:waf:run:finish'),
30
31
  fsOperationStart: dc.channel('apm:fs:operation:start'),
31
- expressMiddlewareError: dc.channel('apm:express:middleware:error')
32
+ expressMiddlewareError: dc.channel('apm:express:middleware:error'),
33
+ childProcessExecutionTracingChannel: dc.tracingChannel('datadog:child_process:execution')
32
34
  }
@@ -23,18 +23,26 @@ class TaintTrackingPlugin extends SourceIastPlugin {
23
23
  constructor () {
24
24
  super()
25
25
  this._type = 'taint-tracking'
26
+ this._taintedURLs = new WeakMap()
26
27
  }
27
28
 
28
29
  onConfigure () {
30
+ const onRequestBody = ({ req }) => {
31
+ const iastContext = getIastContext(storage.getStore())
32
+ if (iastContext && iastContext.body !== req.body) {
33
+ this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
34
+ iastContext.body = req.body
35
+ }
36
+ }
37
+
29
38
  this.addSub(
30
39
  { channelName: 'datadog:body-parser:read:finish', tag: HTTP_REQUEST_BODY },
31
- ({ req }) => {
32
- const iastContext = getIastContext(storage.getStore())
33
- if (iastContext && iastContext.body !== req.body) {
34
- this._taintTrackingHandler(HTTP_REQUEST_BODY, req, 'body', iastContext)
35
- iastContext.body = req.body
36
- }
37
- }
40
+ onRequestBody
41
+ )
42
+
43
+ this.addSub(
44
+ { channelName: 'datadog:multer:read:finish', tag: HTTP_REQUEST_BODY },
45
+ onRequestBody
38
46
  )
39
47
 
40
48
  this.addSub(
@@ -81,6 +89,46 @@ class TaintTrackingPlugin extends SourceIastPlugin {
81
89
  }
82
90
  )
83
91
 
92
+ const urlResultTaintedProperties = ['host', 'origin', 'hostname']
93
+ this.addSub(
94
+ { channelName: 'datadog:url:parse:finish' },
95
+ ({ input, base, parsed, isURL }) => {
96
+ const iastContext = getIastContext(storage.getStore())
97
+ let ranges
98
+
99
+ if (base) {
100
+ ranges = getRanges(iastContext, base)
101
+ } else {
102
+ ranges = getRanges(iastContext, input)
103
+ }
104
+
105
+ if (ranges?.length) {
106
+ if (isURL) {
107
+ this._taintedURLs.set(parsed, ranges[0])
108
+ } else {
109
+ urlResultTaintedProperties.forEach(param => {
110
+ this._taintTrackingHandler(ranges[0].iinfo.type, parsed, param, iastContext)
111
+ })
112
+ }
113
+ }
114
+ }
115
+ )
116
+
117
+ this.addSub(
118
+ { channelName: 'datadog:url:getter:finish' },
119
+ (context) => {
120
+ if (!urlResultTaintedProperties.includes(context.property)) return
121
+
122
+ const origRange = this._taintedURLs.get(context.urlObject)
123
+ if (!origRange) return
124
+
125
+ const iastContext = getIastContext(storage.getStore())
126
+ if (!iastContext) return
127
+
128
+ context.result =
129
+ newTaintedString(iastContext, context.result, origRange.iinfo.parameterName, origRange.iinfo.type)
130
+ })
131
+
84
132
  // this is a special case to increment INSTRUMENTED_SOURCE metric for header
85
133
  this.addInstrumentedSource('http', [HTTP_REQUEST_HEADER_VALUE, HTTP_REQUEST_HEADER_NAME])
86
134
  }
@@ -1,10 +1,11 @@
1
1
  'use strict'
2
2
 
3
- const { MANUAL_KEEP } = require('../../../../../ext/tags')
4
3
  const LRU = require('lru-cache')
5
4
  const vulnerabilitiesFormatter = require('./vulnerabilities-formatter')
6
5
  const { IAST_ENABLED_TAG_KEY, IAST_JSON_TAG_KEY } = require('./tags')
7
6
  const standalone = require('../standalone')
7
+ const { SAMPLING_MECHANISM_APPSEC } = require('../../constants')
8
+ const { keepTrace } = require('../../priority_sampler')
8
9
 
9
10
  const VULNERABILITIES_KEY = 'vulnerabilities'
10
11
  const VULNERABILITY_HASHES_MAX_SIZE = 1000
@@ -56,9 +57,10 @@ function sendVulnerabilities (vulnerabilities, rootSpan) {
56
57
  const tags = {}
57
58
  // TODO: Store this outside of the span and set the tag in the exporter.
58
59
  tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
59
- tags[MANUAL_KEEP] = 'true'
60
60
  span.addTags(tags)
61
61
 
62
+ keepTrace(span, SAMPLING_MECHANISM_APPSEC)
63
+
62
64
  standalone.sample(span)
63
65
 
64
66
  if (!rootSpan) span.finish()
@@ -6,6 +6,7 @@ const remoteConfig = require('./remote_config')
6
6
  const {
7
7
  bodyParser,
8
8
  cookieParser,
9
+ multerParser,
9
10
  incomingHttpRequestStart,
10
11
  incomingHttpRequestEnd,
11
12
  passportVerify,
@@ -58,6 +59,7 @@ function enable (_config) {
58
59
  apiSecuritySampler.configure(_config.appsec)
59
60
 
60
61
  bodyParser.subscribe(onRequestBodyParsed)
62
+ multerParser.subscribe(onRequestBodyParsed)
61
63
  cookieParser.subscribe(onRequestCookieParser)
62
64
  incomingHttpRequestStart.subscribe(incomingHttpStartTranslator)
63
65
  incomingHttpRequestEnd.subscribe(incomingHttpEndTranslator)
@@ -299,6 +301,7 @@ function disable () {
299
301
 
300
302
  // Channel#unsubscribe() is undefined for non active channels
301
303
  if (bodyParser.hasSubscribers) bodyParser.unsubscribe(onRequestBodyParsed)
304
+ if (multerParser.hasSubscribers) multerParser.unsubscribe(onRequestBodyParsed)
302
305
  if (cookieParser.hasSubscribers) cookieParser.unsubscribe(onRequestCookieParser)
303
306
  if (incomingHttpRequestStart.hasSubscribers) incomingHttpRequestStart.unsubscribe(incomingHttpStartTranslator)
304
307
  if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.unsubscribe(incomingHttpEndTranslator)
@@ -0,0 +1,49 @@
1
+ 'use strict'
2
+
3
+ const { childProcessExecutionTracingChannel } = require('../channels')
4
+ const { RULE_TYPES, handleResult } = require('./utils')
5
+ const { storage } = require('../../../../datadog-core')
6
+ const addresses = require('../addresses')
7
+ const waf = require('../waf')
8
+
9
+ let config
10
+
11
+ function enable (_config) {
12
+ config = _config
13
+
14
+ childProcessExecutionTracingChannel.subscribe({
15
+ start: analyzeCommandInjection
16
+ })
17
+ }
18
+
19
+ function disable () {
20
+ if (childProcessExecutionTracingChannel.start.hasSubscribers) {
21
+ childProcessExecutionTracingChannel.unsubscribe({
22
+ start: analyzeCommandInjection
23
+ })
24
+ }
25
+ }
26
+
27
+ function analyzeCommandInjection ({ file, fileArgs, shell, abortController }) {
28
+ if (!file || !shell) return
29
+
30
+ const store = storage.getStore()
31
+ const req = store?.req
32
+ if (!req) return
33
+
34
+ const commandParams = fileArgs ? [file, ...fileArgs] : file
35
+
36
+ const persistent = {
37
+ [addresses.SHELL_COMMAND]: commandParams
38
+ }
39
+
40
+ const result = waf.run({ persistent }, req, RULE_TYPES.COMMAND_INJECTION)
41
+
42
+ const res = store?.res
43
+ handleResult(result, req, res, abortController, config)
44
+ }
45
+
46
+ module.exports = {
47
+ enable,
48
+ disable
49
+ }
@@ -6,6 +6,7 @@ const { block, isBlocked } = require('../blocking')
6
6
  const ssrf = require('./ssrf')
7
7
  const sqli = require('./sql_injection')
8
8
  const lfi = require('./lfi')
9
+ const cmdi = require('./command_injection')
9
10
 
10
11
  const { DatadogRaspAbortError } = require('./utils')
11
12
 
@@ -95,6 +96,7 @@ function enable (config) {
95
96
  ssrf.enable(config)
96
97
  sqli.enable(config)
97
98
  lfi.enable(config)
99
+ cmdi.enable(config)
98
100
 
99
101
  process.on('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
100
102
  expressMiddlewareError.subscribe(blockOnDatadogRaspAbortError)
@@ -104,6 +106,7 @@ function disable () {
104
106
  ssrf.disable()
105
107
  sqli.disable()
106
108
  lfi.disable()
109
+ cmdi.disable()
107
110
 
108
111
  process.off('uncaughtExceptionMonitor', handleUncaughtExceptionMonitor)
109
112
  if (expressMiddlewareError.hasSubscribers) expressMiddlewareError.unsubscribe(blockOnDatadogRaspAbortError)
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { format } = require('url')
3
4
  const { httpClientRequestStart } = require('../channels')
4
5
  const { storage } = require('../../../../datadog-core')
5
6
  const addresses = require('../addresses')
@@ -20,12 +21,12 @@ function disable () {
20
21
  function analyzeSsrf (ctx) {
21
22
  const store = storage.getStore()
22
23
  const req = store?.req
23
- const url = ctx.args.uri
24
+ const outgoingUrl = (ctx.args.options?.uri && format(ctx.args.options.uri)) ?? ctx.args.uri
24
25
 
25
- if (!req || !url) return
26
+ if (!req || !outgoingUrl) return
26
27
 
27
28
  const persistent = {
28
- [addresses.HTTP_OUTGOING_URL]: url
29
+ [addresses.HTTP_OUTGOING_URL]: outgoingUrl
29
30
  }
30
31
 
31
32
  const result = waf.run({ persistent }, req, RULE_TYPES.SSRF)
@@ -12,9 +12,10 @@ if (abortOnUncaughtException) {
12
12
  }
13
13
 
14
14
  const RULE_TYPES = {
15
- SSRF: 'ssrf',
15
+ COMMAND_INJECTION: 'command_injection',
16
+ LFI: 'lfi',
16
17
  SQL_INJECTION: 'sql_injection',
17
- LFI: 'lfi'
18
+ SSRF: 'ssrf'
18
19
  }
19
20
 
20
21
  class DatadogRaspAbortError extends Error {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "2.2",
3
3
  "metadata": {
4
- "rules_version": "1.13.1"
4
+ "rules_version": "1.13.2"
5
5
  },
6
6
  "rules": [
7
7
  {
@@ -6335,7 +6335,6 @@
6335
6335
  {
6336
6336
  "id": "rasp-934-100",
6337
6337
  "name": "Server-side request forgery exploit",
6338
- "enabled": false,
6339
6338
  "tags": {
6340
6339
  "type": "ssrf",
6341
6340
  "category": "vulnerability_trigger",
@@ -6384,7 +6383,6 @@
6384
6383
  {
6385
6384
  "id": "rasp-942-100",
6386
6385
  "name": "SQL injection exploit",
6387
- "enabled": false,
6388
6386
  "tags": {
6389
6387
  "type": "sql_injection",
6390
6388
  "category": "vulnerability_trigger",
@@ -6424,7 +6422,7 @@
6424
6422
  }
6425
6423
  ]
6426
6424
  },
6427
- "operator": "sqli_detector"
6425
+ "operator": "sqli_detector@v2"
6428
6426
  }
6429
6427
  ],
6430
6428
  "transformers": [],
@@ -20,6 +20,7 @@ module.exports = {
20
20
  ASM_RASP_SQLI: 1n << 21n,
21
21
  ASM_RASP_LFI: 1n << 22n,
22
22
  ASM_RASP_SSRF: 1n << 23n,
23
+ ASM_RASP_SHI: 1n << 24n,
23
24
  APM_TRACING_SAMPLE_RULES: 1n << 29n,
24
25
  ASM_ENDPOINT_FINGERPRINT: 1n << 32n,
25
26
  ASM_NETWORK_FINGERPRINT: 1n << 34n,
@@ -83,6 +83,7 @@ function enableWafUpdate (appsecConfig) {
83
83
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SQLI, true)
84
84
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SSRF, true)
85
85
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_LFI, true)
86
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SHI, true)
86
87
  }
87
88
 
88
89
  // TODO: delete noop handlers and kPreUpdate and replace with batched handlers
@@ -114,6 +115,7 @@ function disableWafUpdate () {
114
115
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SQLI, false)
115
116
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SSRF, false)
116
117
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_LFI, false)
118
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_RASP_SHI, false)
117
119
 
118
120
  rc.removeProductHandler('ASM_DATA')
119
121
  rc.removeProductHandler('ASM_DD')
@@ -13,8 +13,9 @@ const {
13
13
  getRequestMetrics
14
14
  } = require('./telemetry')
15
15
  const zlib = require('zlib')
16
- const { MANUAL_KEEP } = require('../../../../ext/tags')
17
16
  const standalone = require('./standalone')
17
+ const { SAMPLING_MECHANISM_APPSEC } = require('../constants')
18
+ const { keepTrace } = require('../priority_sampler')
18
19
 
19
20
  // default limiter, configurable with setRateLimit()
20
21
  let limiter = new Limiter(100)
@@ -96,8 +97,6 @@ function reportWafInit (wafVersion, rulesVersion, diagnosticsRules = {}) {
96
97
  metricsQueue.set('_dd.appsec.event_rules.errors', JSON.stringify(diagnosticsRules.errors))
97
98
  }
98
99
 
99
- metricsQueue.set(MANUAL_KEEP, 'true')
100
-
101
100
  incrementWafInitMetric(wafVersion, rulesVersion)
102
101
  }
103
102
 
@@ -129,7 +128,7 @@ function reportAttack (attackData) {
129
128
  }
130
129
 
131
130
  if (limiter.isAllowed()) {
132
- newTags[MANUAL_KEEP] = 'true'
131
+ keepTrace(rootSpan, SAMPLING_MECHANISM_APPSEC)
133
132
 
134
133
  standalone.sample(rootSpan)
135
134
  }
@@ -184,6 +183,8 @@ function finishRequest (req, res) {
184
183
  if (metricsQueue.size) {
185
184
  rootSpan.addTags(Object.fromEntries(metricsQueue))
186
185
 
186
+ keepTrace(rootSpan, SAMPLING_MECHANISM_APPSEC)
187
+
187
188
  standalone.sample(rootSpan)
188
189
 
189
190
  metricsQueue.clear()
@@ -2,10 +2,11 @@
2
2
 
3
3
  const log = require('../../log')
4
4
  const { getRootSpan } = require('./utils')
5
- const { MANUAL_KEEP } = require('../../../../../ext/tags')
6
5
  const { setUserTags } = require('./set_user')
7
6
  const standalone = require('../standalone')
8
7
  const waf = require('../waf')
8
+ const { SAMPLING_MECHANISM_APPSEC } = require('../../constants')
9
+ const { keepTrace } = require('../../priority_sampler')
9
10
 
10
11
  function trackUserLoginSuccessEvent (tracer, user, metadata) {
11
12
  // TODO: better user check here and in _setUser() ?
@@ -55,9 +56,10 @@ function trackEvent (eventName, fields, sdkMethodName, rootSpan, mode) {
55
56
  return
56
57
  }
57
58
 
59
+ keepTrace(rootSpan, SAMPLING_MECHANISM_APPSEC)
60
+
58
61
  const tags = {
59
- [`appsec.events.${eventName}.track`]: 'true',
60
- [MANUAL_KEEP]: 'true'
62
+ [`appsec.events.${eventName}.track`]: 'true'
61
63
  }
62
64
 
63
65
  if (mode === 'sdk') {
@@ -51,6 +51,10 @@ class WAFManager {
51
51
  update (newRules) {
52
52
  this.ddwaf.update(newRules)
53
53
 
54
+ if (this.ddwaf.diagnostics.ruleset_version) {
55
+ this.rulesVersion = this.ddwaf.diagnostics.ruleset_version
56
+ }
57
+
54
58
  Reporter.reportWafUpdate(this.ddwafVersion, this.rulesVersion)
55
59
  }
56
60
 
@@ -0,0 +1,120 @@
1
+ 'use strict'
2
+
3
+ // eslint-disable-next-line max-len
4
+ // Modeled after https://github.com/DataDog/libdatadog/blob/f3994857a59bb5679a65967138c5a3aec418a65f/ddcommon/src/azure_app_services.rs
5
+
6
+ const os = require('os')
7
+ const { getIsAzureFunction } = require('./serverless')
8
+
9
+ function extractSubscriptionID (ownerName) {
10
+ if (ownerName !== undefined) {
11
+ const subId = ownerName.split('+')[0].trim()
12
+ if (subId.length > 0) {
13
+ return subId
14
+ }
15
+ }
16
+ return undefined
17
+ }
18
+
19
+ function extractResourceGroup (ownerName) {
20
+ return /.+\+(.+)-.+webspace(-Linux)?/.exec(ownerName)?.[1]
21
+ }
22
+
23
+ function buildResourceID (subscriptionID, siteName, resourceGroup) {
24
+ if (subscriptionID === undefined || siteName === undefined || resourceGroup === undefined) {
25
+ return undefined
26
+ }
27
+ return `/subscriptions/${subscriptionID}/resourcegroups/${resourceGroup}/providers/microsoft.web/sites/${siteName}`
28
+ .toLowerCase()
29
+ }
30
+
31
+ function trimObject (obj) {
32
+ Object.entries(obj)
33
+ .filter(([_, value]) => value === undefined)
34
+ .forEach(([key, _]) => { delete obj[key] })
35
+ return obj
36
+ }
37
+
38
+ function buildMetadata () {
39
+ const {
40
+ COMPUTERNAME,
41
+ DD_AAS_DOTNET_EXTENSION_VERSION,
42
+ FUNCTIONS_EXTENSION_VERSION,
43
+ FUNCTIONS_WORKER_RUNTIME,
44
+ FUNCTIONS_WORKER_RUNTIME_VERSION,
45
+ WEBSITE_INSTANCE_ID,
46
+ WEBSITE_OWNER_NAME,
47
+ WEBSITE_OS,
48
+ WEBSITE_RESOURCE_GROUP,
49
+ WEBSITE_SITE_NAME
50
+ } = process.env
51
+
52
+ const subscriptionID = extractSubscriptionID(WEBSITE_OWNER_NAME)
53
+
54
+ const siteName = WEBSITE_SITE_NAME
55
+
56
+ const [siteKind, siteType] = getIsAzureFunction()
57
+ ? ['functionapp', 'function']
58
+ : ['app', 'app']
59
+
60
+ const resourceGroup = WEBSITE_RESOURCE_GROUP ?? extractResourceGroup(WEBSITE_OWNER_NAME)
61
+
62
+ return trimObject({
63
+ extensionVersion: DD_AAS_DOTNET_EXTENSION_VERSION,
64
+ functionRuntimeVersion: FUNCTIONS_EXTENSION_VERSION,
65
+ instanceID: WEBSITE_INSTANCE_ID,
66
+ instanceName: COMPUTERNAME,
67
+ operatingSystem: WEBSITE_OS ?? os.platform(),
68
+ resourceGroup,
69
+ resourceID: buildResourceID(subscriptionID, siteName, resourceGroup),
70
+ runtime: FUNCTIONS_WORKER_RUNTIME,
71
+ runtimeVersion: FUNCTIONS_WORKER_RUNTIME_VERSION,
72
+ siteKind,
73
+ siteName,
74
+ siteType,
75
+ subscriptionID
76
+ })
77
+ }
78
+
79
+ function getAzureAppMetadata () {
80
+ // DD_AZURE_APP_SERVICES is an environment variable introduced by the .NET APM team and is set automatically for
81
+ // anyone using the Datadog APM Extensions (.NET, Java, or Node) for Windows Azure App Services
82
+ // eslint-disable-next-line max-len
83
+ // See: https://github.com/DataDog/datadog-aas-extension/blob/01f94b5c28b7fa7a9ab264ca28bd4e03be603900/node/src/applicationHost.xdt#L20-L21
84
+ return process.env.DD_AZURE_APP_SERVICES !== undefined ? buildMetadata() : undefined
85
+ }
86
+
87
+ function getAzureFunctionMetadata () {
88
+ return getIsAzureFunction() ? buildMetadata() : undefined
89
+ }
90
+
91
+ // eslint-disable-next-line max-len
92
+ // Modeled after https://github.com/DataDog/libdatadog/blob/92272e90a7919f07178f3246ef8f82295513cfed/profiling/src/exporter/mod.rs#L187
93
+ // eslint-disable-next-line max-len
94
+ // and https://github.com/DataDog/libdatadog/blob/f3994857a59bb5679a65967138c5a3aec418a65f/trace-utils/src/trace_utils.rs#L533
95
+ function getAzureTagsFromMetadata (metadata) {
96
+ if (metadata === undefined) {
97
+ return {}
98
+ }
99
+ return trimObject({
100
+ 'aas.environment.extension_version': metadata.extensionVersion,
101
+ 'aas.environment.function_runtime': metadata.functionRuntimeVersion,
102
+ 'aas.environment.instance_id': metadata.instanceID,
103
+ 'aas.environment.instance_name': metadata.instanceName,
104
+ 'aas.environment.os': metadata.operatingSystem,
105
+ 'aas.environment.runtime': metadata.runtime,
106
+ 'aas.environment.runtime_version': metadata.runtimeVersion,
107
+ 'aas.resource.group': metadata.resourceGroup,
108
+ 'aas.resource.id': metadata.resourceID,
109
+ 'aas.site.kind': metadata.siteKind,
110
+ 'aas.site.name': metadata.siteName,
111
+ 'aas.site.type': metadata.siteType,
112
+ 'aas.subscription.id': metadata.subscriptionID
113
+ })
114
+ }
115
+
116
+ module.exports = {
117
+ getAzureAppMetadata,
118
+ getAzureFunctionMetadata,
119
+ getAzureTagsFromMetadata
120
+ }
@@ -0,0 +1,97 @@
1
+ 'use strict'
2
+
3
+ const { join } = require('path')
4
+ const { Worker } = require('worker_threads')
5
+ const { randomUUID } = require('crypto')
6
+ const log = require('../../log')
7
+
8
+ const probeIdToResolveBreakpointSet = new Map()
9
+ const probeIdToResolveBreakpointHit = new Map()
10
+
11
+ class TestVisDynamicInstrumentation {
12
+ constructor () {
13
+ this.worker = null
14
+ this._readyPromise = new Promise(resolve => {
15
+ this._onReady = resolve
16
+ })
17
+ this.breakpointSetChannel = new MessageChannel()
18
+ this.breakpointHitChannel = new MessageChannel()
19
+ }
20
+
21
+ // Return 3 elements:
22
+ // 1. Snapshot ID
23
+ // 2. Promise that's resolved when the breakpoint is set
24
+ // 3. Promise that's resolved when the breakpoint is hit
25
+ addLineProbe ({ file, line }) {
26
+ const snapshotId = randomUUID()
27
+ const probeId = randomUUID()
28
+
29
+ this.breakpointSetChannel.port2.postMessage({
30
+ snapshotId,
31
+ probe: { id: probeId, file, line }
32
+ })
33
+
34
+ return [
35
+ snapshotId,
36
+ new Promise(resolve => {
37
+ probeIdToResolveBreakpointSet.set(probeId, resolve)
38
+ }),
39
+ new Promise(resolve => {
40
+ probeIdToResolveBreakpointHit.set(probeId, resolve)
41
+ })
42
+ ]
43
+ }
44
+
45
+ isReady () {
46
+ return this._readyPromise
47
+ }
48
+
49
+ start () {
50
+ if (this.worker) return
51
+
52
+ const { NODE_OPTIONS, ...envWithoutNodeOptions } = process.env
53
+
54
+ log.debug('Starting Test Visibility - Dynamic Instrumentation client...')
55
+
56
+ this.worker = new Worker(
57
+ join(__dirname, 'worker', 'index.js'),
58
+ {
59
+ execArgv: [],
60
+ env: envWithoutNodeOptions,
61
+ workerData: {
62
+ breakpointSetChannel: this.breakpointSetChannel.port1,
63
+ breakpointHitChannel: this.breakpointHitChannel.port1
64
+ },
65
+ transferList: [this.breakpointSetChannel.port1, this.breakpointHitChannel.port1]
66
+ }
67
+ )
68
+ this.worker.on('online', () => {
69
+ log.debug('Test Visibility - Dynamic Instrumentation client is ready')
70
+ this._onReady()
71
+ })
72
+
73
+ // Allow the parent to exit even if the worker is still running
74
+ this.worker.unref()
75
+
76
+ this.breakpointSetChannel.port2.on('message', (message) => {
77
+ const { probeId } = message
78
+ const resolve = probeIdToResolveBreakpointSet.get(probeId)
79
+ if (resolve) {
80
+ resolve()
81
+ probeIdToResolveBreakpointSet.delete(probeId)
82
+ }
83
+ }).unref()
84
+
85
+ this.breakpointHitChannel.port2.on('message', (message) => {
86
+ const { snapshot } = message
87
+ const { probe: { id: probeId } } = snapshot
88
+ const resolve = probeIdToResolveBreakpointHit.get(probeId)
89
+ if (resolve) {
90
+ resolve({ snapshot })
91
+ probeIdToResolveBreakpointHit.delete(probeId)
92
+ }
93
+ }).unref()
94
+ }
95
+ }
96
+
97
+ module.exports = new TestVisDynamicInstrumentation()