otto 2.8.1 → 2.10.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +5 -0
  3. data/.github/workflows/ci.yml +11 -8
  4. data/.github/workflows/claude-code-review.yml +38 -13
  5. data/.github/workflows/claude.yml +10 -8
  6. data/.github/workflows/code-smells.yml +5 -5
  7. data/.github/workflows/release-gem.yml +2 -2
  8. data/.github/workflows/ruby-lint.yml +3 -3
  9. data/.github/workflows/yardoc.yml +5 -5
  10. data/.gitignore +1 -5
  11. data/.rubocop_todo.yml +7 -5
  12. data/AGENTS.md +22 -1
  13. data/CHANGELOG.rst +239 -0
  14. data/Gemfile +3 -3
  15. data/Gemfile.lock +11 -15
  16. data/README.md +139 -38
  17. data/docs/README.md +166 -0
  18. data/docs/adr/README.md +16 -0
  19. data/docs/adr/adr-001-route-authentication-at-handler-boundary.md +38 -0
  20. data/docs/adr/adr-002-multi-strategy-authentication-and-authorization.md +50 -0
  21. data/docs/adr/adr-003-caddy-tls-route-based-integration.md +48 -0
  22. data/docs/adr/adr-004-separate-compatibility-from-security-maintenance.md +58 -0
  23. data/docs/guides/authentication.md +377 -0
  24. data/docs/guides/caddy-tls.md +205 -0
  25. data/docs/guides/configuration_freezing.md +146 -0
  26. data/docs/guides/enrichment.md +161 -0
  27. data/docs/guides/forwarded-authority.md +249 -0
  28. data/docs/guides/geo-country.md +168 -0
  29. data/docs/guides/ip_privacy.md +39 -0
  30. data/docs/guides/ipaddr-encoding-quirk.md +56 -0
  31. data/docs/guides/mcp.md +282 -0
  32. data/docs/guides/privacy.md +193 -0
  33. data/docs/guides/routing.md +181 -0
  34. data/docs/guides/structured_logging.md +281 -0
  35. data/docs/guides/testing-guide.md +391 -0
  36. data/docs/maintainers/github-actions.md +41 -0
  37. data/docs/maintainers/investigations/.gitignore +2 -0
  38. data/docs/migrating/v2.0.0.md +337 -0
  39. data/docs/reference/authentication.md +290 -0
  40. data/docs/reference/route-syntax.md +181 -0
  41. data/docs/reference/runtime-and-dependency-security.md +86 -0
  42. data/examples/advanced_routes/README.md +43 -57
  43. data/examples/authentication_strategies/README.md +37 -196
  44. data/examples/basic/README.md +24 -39
  45. data/examples/basic/config.ru +0 -1
  46. data/examples/caddy_tls_demo/README.md +8 -2
  47. data/examples/lambda_handlers/README.md +11 -2
  48. data/examples/mcp_demo/README.md +75 -161
  49. data/examples/mcp_demo/config.ru +1 -0
  50. data/examples/security_features/README.md +36 -234
  51. data/lib/otto/caddy_tls/localhost_guard.rb +22 -24
  52. data/lib/otto/core/configuration.rb +36 -22
  53. data/lib/otto/core/file_safety.rb +89 -31
  54. data/lib/otto/core/middleware_stack.rb +36 -36
  55. data/lib/otto/core/router.rb +62 -19
  56. data/lib/otto/env_keys.rb +42 -2
  57. data/lib/otto/mcp/auth/token.rb +10 -4
  58. data/lib/otto/mcp/core.rb +23 -5
  59. data/lib/otto/mcp/endpoint.rb +41 -0
  60. data/lib/otto/mcp/errors.rb +15 -0
  61. data/lib/otto/mcp/options.rb +292 -0
  62. data/lib/otto/mcp/protocol.rb +52 -22
  63. data/lib/otto/mcp/rate_limiting.rb +175 -97
  64. data/lib/otto/mcp/registry.rb +14 -14
  65. data/lib/otto/mcp/schema_validation.rb +20 -12
  66. data/lib/otto/mcp/server.rb +131 -32
  67. data/lib/otto/optional_dependency.rb +57 -0
  68. data/lib/otto/privacy/config.rb +10 -8
  69. data/lib/otto/response.rb +7 -1
  70. data/lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb +22 -5
  71. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +181 -18
  72. data/lib/otto/security/authentication/strategies/permission_strategy.rb +1 -0
  73. data/lib/otto/security/authentication/strategies/role_strategy.rb +1 -0
  74. data/lib/otto/security/authentication/strategies/session_strategy.rb +1 -0
  75. data/lib/otto/security/authentication/strategy_result.rb +58 -28
  76. data/lib/otto/security/config.rb +379 -20
  77. data/lib/otto/security/configurator.rb +36 -6
  78. data/lib/otto/security/core.rb +19 -1
  79. data/lib/otto/security/csp/emit_middleware.rb +2 -1
  80. data/lib/otto/security/csp/policy.rb +159 -7
  81. data/lib/otto/security/csp/request_extras.rb +256 -0
  82. data/lib/otto/security/csp/writer.rb +153 -12
  83. data/lib/otto/security/csp.rb +5 -0
  84. data/lib/otto/security/middleware/ip_privacy_middleware.rb +105 -4
  85. data/lib/otto/security/middleware/rate_limit_middleware.rb +1 -6
  86. data/lib/otto/security/rate_limiter.rb +81 -46
  87. data/lib/otto/utils.rb +50 -0
  88. data/lib/otto/version.rb +1 -1
  89. data/lib/otto.rb +9 -11
  90. data/otto.gemspec +0 -2
  91. metadata +32 -41
  92. data/docs/.gitignore +0 -10
  93. data/docs/1108-STREAMING_ARCHITECTURE_ANALYSIS.md +0 -1105
  94. data/docs/1108-STREAMING_SUPPORT_SUMMARY.md +0 -376
  95. data/docs/enrichment.md +0 -128
  96. data/docs/geo-country.md +0 -181
  97. data/docs/ipaddr-encoding-quirk.md +0 -34
  98. data/docs/migrating/v2.0.0-pre1.md +0 -276
  99. data/docs/migrating/v2.0.0-pre2.md +0 -338
  100. data/docs/modern-authentication-authorization-landscape.md +0 -558
  101. data/docs/multi-strategy-authentication-design.md +0 -1401
  102. data/docs/reverse-proxy-network-services.md +0 -371
data/lib/otto.rb CHANGED
@@ -76,7 +76,7 @@ class Otto
76
76
  end
77
77
  @logger = Logger.new($stdout, Logger::INFO)
78
78
 
79
- attr_reader :routes, :routes_literal, :routes_static, :route_definitions,
79
+ attr_reader :routes, :routes_literal, :route_definitions,
80
80
  :routes_by_definition, :option,
81
81
  :static_route, :security_config, :locale_config, :auth_config,
82
82
  :route_handler_factory, :mcp_server, :caddy_tls_server, :security, :middleware,
@@ -84,6 +84,7 @@ class Otto
84
84
  attr_accessor :not_found, :server_error
85
85
 
86
86
  def initialize(path = nil, opts = {})
87
+ constructed = false
87
88
  initialize_core_state
88
89
  initialize_options(path, opts)
89
90
  initialize_configurations(opts)
@@ -106,6 +107,13 @@ class Otto
106
107
  # but before processing requests.
107
108
  @freeze_mutex = Mutex.new
108
109
  @configuration_frozen = false
110
+ constructed = true
111
+ ensure
112
+ # A config that committed to a forwarding family during configure_security
113
+ # is held process-wide; withdraw it if construction failed afterwards
114
+ # (e.g. a bad routes path), or the dead app would keep vetoing other
115
+ # families for the life of the process.
116
+ Otto::Security::Config.release_rack_forwarding_family!(@security_config) if @security_config && !constructed
109
117
  end
110
118
  alias options option
111
119
 
@@ -172,16 +180,6 @@ class Otto
172
180
  private
173
181
 
174
182
  def initialize_core_state
175
- # The GET cache is a Concurrent::Map, not a plain Hash: lazy static-file
176
- # discovery (Core::Router#handle_request, Core::FileSafety#add_static_path)
177
- # writes into it at request time, after freeze_configuration! has already
178
- # deep-frozen the rest of the routing state. Deep-freezing this cache too
179
- # would turn every as-yet-uncached static file request into a 500
180
- # (FrozenError) in production (issue #185), so it is intentionally excluded
181
- # from deep_freeze_value in Configuration#freeze_configuration! and kept as
182
- # a structure that is both mutable post-freeze and safe under concurrent
183
- # request threads.
184
- @routes_static = { GET: Concurrent::Map.new }
185
183
  @routes = { GET: [] }
186
184
  @routes_literal = { GET: {} }
187
185
  @route_definitions = {}
data/otto.gemspec CHANGED
@@ -34,8 +34,6 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency 'logger', '~> 1', '< 2.0'
35
35
 
36
36
  spec.add_dependency 'rack', '~> 3.1', '< 4.0'
37
- spec.add_dependency 'rack-parser', '~> 0.7'
38
- spec.add_dependency 'rexml', '~> 3.4'
39
37
 
40
38
  # Security dependencies
41
39
  spec.add_dependency 'loofah', '~> 2.20'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otto
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.1
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-16 00:00:00.000000000 Z
11
+ date: 2026-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -70,34 +70,6 @@ dependencies:
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
72
  version: '4.0'
73
- - !ruby/object:Gem::Dependency
74
- name: rack-parser
75
- requirement: !ruby/object:Gem::Requirement
76
- requirements:
77
- - - "~>"
78
- - !ruby/object:Gem::Version
79
- version: '0.7'
80
- type: :runtime
81
- prerelease: false
82
- version_requirements: !ruby/object:Gem::Requirement
83
- requirements:
84
- - - "~>"
85
- - !ruby/object:Gem::Version
86
- version: '0.7'
87
- - !ruby/object:Gem::Dependency
88
- name: rexml
89
- requirement: !ruby/object:Gem::Requirement
90
- requirements:
91
- - - "~>"
92
- - !ruby/object:Gem::Version
93
- version: '3.4'
94
- type: :runtime
95
- prerelease: false
96
- version_requirements: !ruby/object:Gem::Requirement
97
- requirements:
98
- - - "~>"
99
- - !ruby/object:Gem::Version
100
- version: '3.4'
101
73
  - !ruby/object:Gem::Dependency
102
74
  name: loofah
103
75
  requirement: !ruby/object:Gem::Requirement
@@ -144,18 +116,32 @@ files:
144
116
  - bin/rspec
145
117
  - changelog.d/README.md
146
118
  - changelog.d/scriv.ini
147
- - docs/.gitignore
148
- - docs/1108-STREAMING_ARCHITECTURE_ANALYSIS.md
149
- - docs/1108-STREAMING_SUPPORT_SUMMARY.md
150
- - docs/enrichment.md
151
- - docs/geo-country.md
152
- - docs/ipaddr-encoding-quirk.md
153
- - docs/migrating/v2.0.0-pre1.md
154
- - docs/migrating/v2.0.0-pre2.md
119
+ - docs/README.md
120
+ - docs/adr/README.md
121
+ - docs/adr/adr-001-route-authentication-at-handler-boundary.md
122
+ - docs/adr/adr-002-multi-strategy-authentication-and-authorization.md
123
+ - docs/adr/adr-003-caddy-tls-route-based-integration.md
124
+ - docs/adr/adr-004-separate-compatibility-from-security-maintenance.md
125
+ - docs/guides/authentication.md
126
+ - docs/guides/caddy-tls.md
127
+ - docs/guides/configuration_freezing.md
128
+ - docs/guides/enrichment.md
129
+ - docs/guides/forwarded-authority.md
130
+ - docs/guides/geo-country.md
131
+ - docs/guides/ip_privacy.md
132
+ - docs/guides/ipaddr-encoding-quirk.md
133
+ - docs/guides/mcp.md
134
+ - docs/guides/privacy.md
135
+ - docs/guides/routing.md
136
+ - docs/guides/structured_logging.md
137
+ - docs/guides/testing-guide.md
138
+ - docs/maintainers/github-actions.md
139
+ - docs/maintainers/investigations/.gitignore
140
+ - docs/migrating/v2.0.0.md
155
141
  - docs/migrating/v2.3.0.md
156
- - docs/modern-authentication-authorization-landscape.md
157
- - docs/multi-strategy-authentication-design.md
158
- - docs/reverse-proxy-network-services.md
142
+ - docs/reference/authentication.md
143
+ - docs/reference/route-syntax.md
144
+ - docs/reference/runtime-and-dependency-security.md
159
145
  - examples/.gitignore
160
146
  - examples/advanced_routes/README.md
161
147
  - examples/advanced_routes/app.rb
@@ -251,12 +237,16 @@ files:
251
237
  - lib/otto/mcp.rb
252
238
  - lib/otto/mcp/auth/token.rb
253
239
  - lib/otto/mcp/core.rb
240
+ - lib/otto/mcp/endpoint.rb
241
+ - lib/otto/mcp/errors.rb
242
+ - lib/otto/mcp/options.rb
254
243
  - lib/otto/mcp/protocol.rb
255
244
  - lib/otto/mcp/rate_limiting.rb
256
245
  - lib/otto/mcp/registry.rb
257
246
  - lib/otto/mcp/route_parser.rb
258
247
  - lib/otto/mcp/schema_validation.rb
259
248
  - lib/otto/mcp/server.rb
249
+ - lib/otto/optional_dependency.rb
260
250
  - lib/otto/privacy.rb
261
251
  - lib/otto/privacy/anonymizer_resolver.rb
262
252
  - lib/otto/privacy/asn_resolver.rb
@@ -312,6 +302,7 @@ files:
312
302
  - lib/otto/security/csp/policy.rb
313
303
  - lib/otto/security/csp/report.rb
314
304
  - lib/otto/security/csp/report_middleware.rb
305
+ - lib/otto/security/csp/request_extras.rb
315
306
  - lib/otto/security/csp/writer.rb
316
307
  - lib/otto/security/csrf.rb
317
308
  - lib/otto/security/csrf_enforcement_wrapper.rb
data/docs/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- *
2
- !.gitignore
3
- !migrating/
4
- !migrating/*.md
5
- !ipaddr-encoding-quirk.md
6
- !enrichment.md
7
- !geo-country.md
8
- !modern-authentication-authorization-landscape.md
9
- !multi-strategy-authentication-design.md
10
- !reverse-proxy-network-services.md