datadog 2.12.1 → 2.12.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d150a054dd119a853c1f7c0653a2ea2dedb1b20ce4b538168ead98ed3abc8720
4
- data.tar.gz: 69ff582e245644c0c867d48ecd20fd2b493fc97b778235d163bef193aa8ea298
3
+ metadata.gz: 5a27bb8e1291ee014e342297fc3d53aca8a895017281201b1b0a29cd376ba905
4
+ data.tar.gz: e26c326437e4edcbdfac0ad604514a48c23c31500a68dcc4affb53251aed5f8f
5
5
  SHA512:
6
- metadata.gz: 8845d2c09585196054cdf50b353dbf1a226a58d468c3ed9c99b8a12bfa12e659b41c4e67abf6e20288fa740692c9bd4ea874004e523bbe29b1a30b6bc997a53b
7
- data.tar.gz: c4e4b6a42acc8af8675287440b437db002201d5eb5a1371e141b6ffdef82c752f063f8c4fd679fdaa4196530612bdc066e81897a1c14c41ec62ac77e65afafe2
6
+ metadata.gz: e8dab2f6ed8bc48fb95a4c9c3c28fce16b8eaaceb70179ee12b9a009498dabe498d73b08a25180fdb53642fc78da4388b2c80bbf76a61afd25af53449f2e9375
7
+ data.tar.gz: 4489a8e084f7873e59b4d2a13d27b9a75f2b95ae0c9b5129edfdb02a58c736890bd5226df765de216010512b9fd0ffad6f82cc373ea8ee995340ef838c721d65
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.12.2] - 2025-03-17
6
+
7
+ ### Fixed
8
+
9
+ * AppSec: Fix custom In-App WAF blocking response that was configured in the UI is now applied correctly ([#4497][])
10
+
5
11
  ## [2.12.1] - 2025-03-05
6
12
 
7
13
  ### Fixed
@@ -3139,7 +3145,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
3139
3145
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3140
3146
 
3141
3147
 
3142
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.12.1...master
3148
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.12.2...master
3149
+ [2.12.2]: https://github.com/DataDog/dd-trace-rb/compare/v2.12.1...v2.12.2
3143
3150
  [2.12.1]: https://github.com/DataDog/dd-trace-rb/compare/v2.12.0...v2.12.1
3144
3151
  [2.12.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.11.0...v2.12.0
3145
3152
  [2.11.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.10.0...v2.11.0
@@ -4643,6 +4650,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4643
4650
  [#4425]: https://github.com/DataDog/dd-trace-rb/issues/4425
4644
4651
  [#4426]: https://github.com/DataDog/dd-trace-rb/issues/4426
4645
4652
  [#4437]: https://github.com/DataDog/dd-trace-rb/issues/4437
4653
+ [#4497]: https://github.com/DataDog/dd-trace-rb/issues/4497
4646
4654
  [@AdrianLC]: https://github.com/AdrianLC
4647
4655
  [@Azure7111]: https://github.com/Azure7111
4648
4656
  [@BabyGroot]: https://github.com/BabyGroot
@@ -4794,4 +4802,4 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4794
4802
  [@y-yagi]: https://github.com/y-yagi
4795
4803
  [@yujideveloper]: https://github.com/yujideveloper
4796
4804
  [@yukimurasawa]: https://github.com/yukimurasawa
4797
- [@zachmccormick]: https://github.com/zachmccormick
4805
+ [@zachmccormick]: https://github.com/zachmccormick
@@ -312,6 +312,7 @@ VALUE thread_name_for(VALUE thread) {
312
312
  // to support our custom rb_profile_frames (see below)
313
313
  // Modifications:
314
314
  // * Support int first_lineno for Ruby 3.2.0+ (https://github.com/ruby/ruby/pull/6430)
315
+ // * Validate iseq and pos before calling `rb_iseq_line_no` as a safety measure (see comment below for details)
315
316
  //
316
317
  // `node_id` gets used depending on Ruby VM compilation settings (USE_ISEQ_NODE_ID being defined).
317
318
  // To avoid getting false "unused argument" warnings in setups where it's not used, we need to do this weird dance
@@ -358,6 +359,13 @@ calc_pos(const rb_iseq_t *iseq, const VALUE *pc, int *lineno, int *node_id)
358
359
  __builtin_trap();
359
360
  }
360
361
  #endif
362
+
363
+ // In PROF-11475 we spotted a crash when calling `rb_iseq_line_no` from this method. We couldn't reproduce or
364
+ // figure out the root cause, but "just in case", we're validating that the iseq looks valid and that the
365
+ // `n` used for the position is also sane, and if they don't look good, we don't calculate the line, rather
366
+ // than potentially trigger any issues.
367
+ if (RB_UNLIKELY(!RB_TYPE_P((VALUE) iseq, T_IMEMO) || n < 0 || n > ISEQ_BODY(iseq)->iseq_size)) return 0;
368
+
361
369
  if (lineno) *lineno = rb_iseq_line_no(iseq, pos);
362
370
  #ifdef USE_ISEQ_NODE_ID
363
371
  if (node_id) *node_id = rb_iseq_node_id(iseq, pos);
@@ -6,6 +6,26 @@ module Datadog
6
6
  module Rack
7
7
  # Rack integration constants
8
8
  module Ext
9
+ IDENTITY_COLLECTABLE_REQUEST_HEADERS = [
10
+ 'accept-encoding',
11
+ 'accept-language',
12
+ 'cf-connecting-ip',
13
+ 'cf-connecting-ipv6',
14
+ 'content-encoding',
15
+ 'content-language',
16
+ 'content-length',
17
+ 'fastly-client-ip',
18
+ 'forwarded',
19
+ 'forwarded-for',
20
+ 'host',
21
+ 'true-client-ip',
22
+ 'via',
23
+ 'x-client-ip',
24
+ 'x-cluster-client-ip',
25
+ 'x-forwarded',
26
+ 'x-forwarded-for',
27
+ 'x-real-ip'
28
+ ].freeze
9
29
  end
10
30
  end
11
31
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../ext'
3
4
  require_relative '../../../instrumentation/gateway'
4
5
  require_relative '../../../event'
5
6
 
@@ -110,6 +111,25 @@ module Datadog
110
111
  stack.call(gateway_request.request)
111
112
  end
112
113
  end
114
+
115
+ # NOTE: In the current state we unable to substibe twice to the same
116
+ # event within the same group. Ideally this code should live
117
+ # somewhere closer to identity related monitor.
118
+ # WARNING: The Gateway is a subject of refactoring
119
+ def watch_request_finish(gateway = Instrumentation.gateway)
120
+ gateway.watch('rack.request.finish', :appsec) do |stack, gateway_request|
121
+ context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
122
+ next stack.call(gateway_request.request) if context.span.nil? || !gateway.pushed?('identity.set_user')
123
+
124
+ gateway_request.headers.each do |name, value|
125
+ next unless Ext::IDENTITY_COLLECTABLE_REQUEST_HEADERS.include?(name)
126
+
127
+ context.span["http.request.headers.#{name}"] = value
128
+ end
129
+
130
+ stack.call(gateway_request.request)
131
+ end
132
+ end
113
133
  end
114
134
  end
115
135
  end
@@ -77,6 +77,8 @@ module Datadog
77
77
  gateway_response = nil
78
78
 
79
79
  interrupt_params = catch(::Datadog::AppSec::Ext::INTERRUPT) do
80
+ # TODO: This event should be renamed into `rack.request.start` to
81
+ # reflect that it's the beginning of the request-cycle
80
82
  http_response, _gateway_request = Instrumentation.gateway.push('rack.request', gateway_request) do
81
83
  @app.call(env)
82
84
  end
@@ -85,6 +87,7 @@ module Datadog
85
87
  http_response[2], http_response[0], http_response[1], context: ctx
86
88
  )
87
89
 
90
+ Instrumentation.gateway.push('rack.request.finish', gateway_request)
88
91
  Instrumentation.gateway.push('rack.response', gateway_response)
89
92
 
90
93
  nil
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module Instrumentation
6
+ class Gateway
7
+ # NOTE: This class extracted as-is and will be deprecated
8
+ # Instrumentation gateway middleware
9
+ class Middleware
10
+ attr_reader :key, :block
11
+
12
+ def initialize(key, &block)
13
+ @key = key
14
+ @block = block
15
+ end
16
+
17
+ def call(stack, env)
18
+ @block.call(stack, env)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,35 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'gateway/middleware'
4
+
3
5
  module Datadog
4
6
  module AppSec
5
7
  # Instrumentation for AppSec
6
8
  module Instrumentation
7
9
  # Instrumentation gateway implementation
8
10
  class Gateway
9
- # Instrumentation gateway middleware
10
- class Middleware
11
- attr_reader :key, :block
12
-
13
- def initialize(key, &block)
14
- @key = key
15
- @block = block
16
- end
17
-
18
- def call(stack, env)
19
- @block.call(stack, env)
20
- end
21
- end
22
-
23
- private_constant :Middleware
24
-
25
11
  def initialize
26
12
  @middlewares = Hash.new { |h, k| h[k] = [] }
13
+ @pushed_events = {}
27
14
  end
28
15
 
16
+ # NOTE: Be careful with pushed names because every pushed event name
17
+ # is recorded in order to provide an ability to any subscriber
18
+ # to check wether an arbitrary event had happened.
19
+ #
20
+ # WARNING: If we start pushing generated names we should consider
21
+ # limiting the storage of pushed names.
29
22
  def push(name, env, &block)
30
- block ||= -> {}
23
+ @pushed_events[name] = true
31
24
 
32
- middlewares_for_name = middlewares[name]
25
+ block ||= -> {}
26
+ middlewares_for_name = @middlewares[name]
33
27
 
34
28
  return [block.call, nil] if middlewares_for_name.empty?
35
29
 
@@ -48,14 +42,15 @@ module Datadog
48
42
  end
49
43
 
50
44
  def watch(name, key, &block)
51
- @middlewares[name] << Middleware.new(key, &block) unless middlewares[name].any? { |m| m.key == key }
45
+ @middlewares[name] << Middleware.new(key, &block) unless @middlewares[name].any? { |m| m.key == key }
52
46
  end
53
47
 
54
- private
55
-
56
- attr_reader :middlewares
48
+ def pushed?(name)
49
+ @pushed_events.key?(name)
50
+ end
57
51
  end
58
52
 
53
+ # NOTE: This left as-is and will be depricated soon.
59
54
  def self.gateway
60
55
  @gateway ||= Gateway.new # TODO: not thread safe
61
56
  end
@@ -22,7 +22,7 @@ module Datadog
22
22
  # TODO: `processors` and `scanners` are not provided by the caller, consider removing them
23
23
  def merge(
24
24
  telemetry:,
25
- rules:, data: [], overrides: [], exclusions: [], custom_rules: [],
25
+ rules:, actions: [], data: [], overrides: [], exclusions: [], custom_rules: [],
26
26
  processors: nil, scanners: nil
27
27
  )
28
28
  processors ||= begin
@@ -54,6 +54,7 @@ module Datadog
54
54
  combined_exclusions = combine_exclusions(exclusions) if exclusions.any?
55
55
  combined_custom_rules = combine_custom_rules(custom_rules) if custom_rules.any?
56
56
 
57
+ combined_rules['actions'] = actions if actions.any?
57
58
  combined_rules['rules_data'] = combined_data if combined_data
58
59
  combined_rules['rules_override'] = combined_overrides if combined_overrides
59
60
  combined_rules['exclusions'] = combined_exclusions if combined_exclusions
@@ -53,10 +53,12 @@ module Datadog
53
53
  end
54
54
 
55
55
  # rubocop:disable Metrics/MethodLength
56
+ # rubocop:disable Metrics/CyclomaticComplexity
56
57
  def receivers(telemetry)
57
58
  return [] unless remote_features_enabled?
58
59
 
59
60
  matcher = Core::Remote::Dispatcher::Matcher::Product.new(ASM_PRODUCTS)
61
+ # rubocop:disable Metrics/BlockLength
60
62
  receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
61
63
  changes.each do |change|
62
64
  Datadog.logger.debug { "remote config change: '#{change.path}'" }
@@ -67,6 +69,7 @@ module Datadog
67
69
  data = []
68
70
  overrides = []
69
71
  exclusions = []
72
+ actions = []
70
73
 
71
74
  repository.contents.each do |content|
72
75
  parsed_content = parse_content(content)
@@ -80,6 +83,7 @@ module Datadog
80
83
  overrides << parsed_content['rules_override'] if parsed_content['rules_override']
81
84
  exclusions << parsed_content['exclusions'] if parsed_content['exclusions']
82
85
  custom_rules << parsed_content['custom_rules'] if parsed_content['custom_rules']
86
+ actions.concat(parsed_content['actions']) if parsed_content['actions']
83
87
  end
84
88
  end
85
89
 
@@ -97,6 +101,7 @@ module Datadog
97
101
  ruleset = AppSec::Processor::RuleMerger.merge(
98
102
  rules: rules,
99
103
  data: data,
104
+ actions: actions,
100
105
  overrides: overrides,
101
106
  exclusions: exclusions,
102
107
  custom_rules: custom_rules,
@@ -109,10 +114,12 @@ module Datadog
109
114
  content.applied if ASM_PRODUCTS.include?(content.path.product)
110
115
  end
111
116
  end
117
+ # rubocop:enable Metrics/BlockLength
112
118
 
113
119
  [receiver]
114
120
  end
115
121
  # rubocop:enable Metrics/MethodLength
122
+ # rubocop:enable Metrics/CyclomaticComplexity
116
123
 
117
124
  private
118
125
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Metadata
8
+ # This class is a data structure that is used to store
9
+ # complex metadata, such as an array of objects.
10
+ #
11
+ # It is serialized to MessagePack format when sent to the agent.
12
+ class Metastruct
13
+ extend Forwardable
14
+
15
+ def_delegators :@metastruct, :[], :[]=, :to_h
16
+
17
+ def initialize
18
+ @metastruct = {}
19
+ end
20
+
21
+ def to_msgpack(packer = nil)
22
+ # JRuby doesn't pass the packer
23
+ packer ||= MessagePack::Packer.new
24
+
25
+ packer.write(@metastruct.transform_values(&:to_msgpack))
26
+ end
27
+
28
+ def pretty_print(q)
29
+ q.seplist @metastruct.each do |key, value|
30
+ q.text "#{key} => #{value}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'metastruct'
4
+
5
+ module Datadog
6
+ module Tracing
7
+ module Metadata
8
+ # Adds data storage for the `meta_struct` field.
9
+ #
10
+ # This field is used to send more complex data like an array of objects
11
+ # in MessagePack format to the agent, and has no size limitations.
12
+ #
13
+ # The agent fully supports meta_struct from version v7.35.0 (April 2022).
14
+ #
15
+ # On versions older than v7.35.0, sending traces containing meta_struct
16
+ # has no unexpected side-effects; traces are sent to the backend as expected,
17
+ # while the meta_struct field is stripped.
18
+ module MetastructTagging
19
+ # Set the given key / value tag pair on the metastruct.
20
+ #
21
+ # A valid example is:
22
+ #
23
+ # span.set_metastruct_tag('_dd.stack', [])
24
+ def set_metastruct_tag(key, value)
25
+ metastruct[key] = value
26
+ end
27
+
28
+ # Return the metastruct tag value for the given key,
29
+ # returns nil if the key doesn't exist.
30
+ def get_metastruct_tag(key)
31
+ metastruct[key]
32
+ end
33
+
34
+ private
35
+
36
+ def metastruct
37
+ @metastruct ||= Metastruct.new
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'metadata/analytics'
4
4
  require_relative 'metadata/tagging'
5
+ require_relative 'metadata/metastruct_tagging'
5
6
  require_relative 'metadata/errors'
6
7
 
7
8
  module Datadog
@@ -10,6 +11,7 @@ module Datadog
10
11
  module Metadata
11
12
  def self.included(base)
12
13
  base.include(Metadata::Tagging)
14
+ base.include(Metadata::MetastructTagging)
13
15
  base.include(Metadata::Errors)
14
16
 
15
17
  # Additional extensions
@@ -33,6 +33,9 @@ module Datadog
33
33
  :status,
34
34
  :trace_id
35
35
 
36
+ attr_reader \
37
+ :metastruct
38
+
36
39
  attr_writer \
37
40
  :duration
38
41
 
@@ -54,6 +57,7 @@ module Datadog
54
57
  id: nil,
55
58
  meta: nil,
56
59
  metrics: nil,
60
+ metastruct: nil,
57
61
  parent_id: 0,
58
62
  resource: name,
59
63
  service: nil,
@@ -76,6 +80,7 @@ module Datadog
76
80
 
77
81
  @meta = meta || {}
78
82
  @metrics = metrics || {}
83
+ @metastruct = metastruct || {}
79
84
  @status = status || 0
80
85
 
81
86
  # start_time and end_time track wall clock. In Ruby, wall clock
@@ -144,6 +149,7 @@ module Datadog
144
149
  error: @status,
145
150
  meta: @meta,
146
151
  metrics: @metrics,
152
+ meta_struct: @metastruct.to_h,
147
153
  name: @name,
148
154
  parent_id: @parent_id,
149
155
  resource: @resource,
@@ -185,12 +191,15 @@ module Datadog
185
191
  q.text "#{key} => #{value}"
186
192
  end
187
193
  end
188
- q.group(2, 'Metrics: [', ']') do
194
+ q.group(2, 'Metrics: [', "]\n") do
189
195
  q.breakable
190
196
  q.seplist @metrics.each do |key, value|
191
197
  q.text "#{key} => #{value}"
192
198
  end
193
199
  end
200
+ q.group(2, 'Metastruct: [', ']') do
201
+ metastruct.pretty_print(q)
202
+ end
194
203
  end
195
204
  end
196
205
 
@@ -289,6 +289,7 @@ module Datadog
289
289
  id: @id,
290
290
  meta: meta,
291
291
  metrics: metrics,
292
+ metastruct: metastruct,
292
293
  name: @name,
293
294
  parent_id: @parent_id,
294
295
  resource: @resource,
@@ -328,12 +329,15 @@ module Datadog
328
329
  q.text "#{key} => #{value}"
329
330
  end
330
331
  end
331
- q.group(2, 'Metrics: [', ']') do
332
+ q.group(2, 'Metrics: [', "]\n") do
332
333
  q.breakable
333
334
  q.seplist metrics.each do |key, value|
334
335
  q.text "#{key} => #{value}"
335
336
  end
336
337
  end
338
+ q.group(2, 'Metastruct: [', ']') do
339
+ metastruct.pretty_print(q)
340
+ end
337
341
  end
338
342
  end
339
343
 
@@ -456,6 +460,7 @@ module Datadog
456
460
  id: @id,
457
461
  meta: Core::Utils::SafeDup.frozen_or_dup(meta),
458
462
  metrics: Core::Utils::SafeDup.frozen_or_dup(metrics),
463
+ metastruct: Core::Utils::SafeDup.frozen_or_dup(metastruct),
459
464
  parent_id: @parent_id,
460
465
  resource: @resource,
461
466
  service: @service,
@@ -69,7 +69,7 @@ module Datadog
69
69
  def to_msgpack(packer = nil)
70
70
  packer ||= MessagePack::Packer.new
71
71
 
72
- number_of_elements_to_write = 11
72
+ number_of_elements_to_write = 12
73
73
 
74
74
  number_of_elements_to_write += 1 if span.events.any? && @native_events_supported
75
75
 
@@ -117,6 +117,8 @@ module Datadog
117
117
  packer.write(span.meta)
118
118
  packer.write('metrics')
119
119
  packer.write(span.metrics)
120
+ packer.write('meta_struct')
121
+ packer.write(span.metastruct)
120
122
  packer.write('span_links')
121
123
  packer.write(span.links.map(&:to_hash))
122
124
  packer.write('error')
@@ -4,7 +4,7 @@ module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
6
  MINOR = 12
7
- PATCH = 1
7
+ PATCH = 2
8
8
  PRE = nil
9
9
  BUILD = nil
10
10
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.1
4
+ version: 2.12.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-06 00:00:00.000000000 Z
11
+ date: 2025-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -226,6 +226,7 @@ files:
226
226
  - lib/datadog/appsec/instrumentation.rb
227
227
  - lib/datadog/appsec/instrumentation/gateway.rb
228
228
  - lib/datadog/appsec/instrumentation/gateway/argument.rb
229
+ - lib/datadog/appsec/instrumentation/gateway/middleware.rb
229
230
  - lib/datadog/appsec/metrics.rb
230
231
  - lib/datadog/appsec/metrics/collector.rb
231
232
  - lib/datadog/appsec/metrics/exporter.rb
@@ -876,6 +877,8 @@ files:
876
877
  - lib/datadog/tracing/metadata/analytics.rb
877
878
  - lib/datadog/tracing/metadata/errors.rb
878
879
  - lib/datadog/tracing/metadata/ext.rb
880
+ - lib/datadog/tracing/metadata/metastruct.rb
881
+ - lib/datadog/tracing/metadata/metastruct_tagging.rb
879
882
  - lib/datadog/tracing/metadata/tagging.rb
880
883
  - lib/datadog/tracing/pipeline.rb
881
884
  - lib/datadog/tracing/pipeline/span_filter.rb
@@ -930,8 +933,8 @@ licenses:
930
933
  - Apache-2.0
931
934
  metadata:
932
935
  allowed_push_host: https://rubygems.org
933
- changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.12.1/CHANGELOG.md
934
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.12.1
936
+ changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.12.2/CHANGELOG.md
937
+ source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.12.2
935
938
  post_install_message:
936
939
  rdoc_options: []
937
940
  require_paths: