otto 2.9.0 → 2.11.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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +5 -0
- data/.github/workflows/ci.yml +11 -8
- data/.github/workflows/claude-code-review.yml +38 -13
- data/.github/workflows/claude.yml +10 -8
- data/.github/workflows/code-smells.yml +5 -5
- data/.github/workflows/release-gem.yml +2 -2
- data/.github/workflows/ruby-lint.yml +3 -3
- data/.github/workflows/yardoc.yml +5 -5
- data/.gitignore +1 -5
- data/.rubocop_todo.yml +8 -5
- data/AGENTS.md +40 -1
- data/CHANGELOG.rst +235 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +11 -15
- data/README.md +75 -37
- data/docs/README.md +166 -0
- data/docs/adr/README.md +16 -0
- data/docs/adr/adr-001-route-authentication-at-handler-boundary.md +38 -0
- data/docs/adr/adr-002-multi-strategy-authentication-and-authorization.md +50 -0
- data/docs/adr/adr-003-caddy-tls-route-based-integration.md +48 -0
- data/docs/adr/adr-004-separate-compatibility-from-security-maintenance.md +58 -0
- data/docs/guides/authentication.md +377 -0
- data/docs/guides/caddy-tls.md +205 -0
- data/docs/guides/configuration_freezing.md +157 -0
- data/docs/guides/enrichment.md +161 -0
- data/docs/guides/forwarded-authority.md +249 -0
- data/docs/guides/geo-country.md +168 -0
- data/docs/guides/ip_privacy.md +39 -0
- data/docs/guides/ipaddr-encoding-quirk.md +56 -0
- data/docs/guides/mcp.md +282 -0
- data/docs/guides/privacy.md +193 -0
- data/docs/guides/routing.md +293 -0
- data/docs/guides/structured_logging.md +281 -0
- data/docs/guides/testing-guide.md +391 -0
- data/docs/maintainers/github-actions.md +41 -0
- data/docs/maintainers/investigations/.gitignore +2 -0
- data/docs/migrating/v2.0.0.md +337 -0
- data/docs/reference/authentication.md +290 -0
- data/docs/reference/route-syntax.md +181 -0
- data/docs/reference/runtime-and-dependency-security.md +86 -0
- data/examples/advanced_routes/README.md +43 -57
- data/examples/authentication_strategies/README.md +37 -196
- data/examples/basic/README.md +24 -39
- data/examples/basic/config.ru +0 -1
- data/examples/caddy_tls_demo/README.md +8 -2
- data/examples/lambda_handlers/README.md +11 -2
- data/examples/mcp_demo/README.md +75 -161
- data/examples/mcp_demo/config.ru +1 -0
- data/examples/security_features/README.md +36 -234
- data/lib/otto/caddy_tls/localhost_guard.rb +22 -24
- data/lib/otto/core/configuration.rb +40 -23
- data/lib/otto/core/error_handler.rb +40 -2
- data/lib/otto/core/file_safety.rb +105 -31
- data/lib/otto/core/middleware_stack.rb +36 -36
- data/lib/otto/core/router.rb +94 -18
- data/lib/otto/core/static_mounts.rb +172 -0
- data/lib/otto/core.rb +1 -0
- data/lib/otto/env_keys.rb +20 -2
- data/lib/otto/mcp/auth/token.rb +10 -4
- data/lib/otto/mcp/core.rb +23 -5
- data/lib/otto/mcp/endpoint.rb +41 -0
- data/lib/otto/mcp/errors.rb +15 -0
- data/lib/otto/mcp/options.rb +292 -0
- data/lib/otto/mcp/protocol.rb +52 -22
- data/lib/otto/mcp/rate_limiting.rb +175 -97
- data/lib/otto/mcp/registry.rb +14 -14
- data/lib/otto/mcp/schema_validation.rb +20 -12
- data/lib/otto/mcp/server.rb +131 -32
- data/lib/otto/optional_dependency.rb +57 -0
- data/lib/otto/privacy/config.rb +10 -8
- data/lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb +22 -5
- data/lib/otto/security/authentication/strategies/api_key_strategy.rb +181 -18
- data/lib/otto/security/authentication/strategies/permission_strategy.rb +1 -0
- data/lib/otto/security/authentication/strategies/role_strategy.rb +1 -0
- data/lib/otto/security/authentication/strategies/session_strategy.rb +1 -0
- data/lib/otto/security/authentication/strategy_result.rb +58 -28
- data/lib/otto/security/config.rb +328 -14
- data/lib/otto/security/configurator.rb +36 -6
- data/lib/otto/security/core.rb +19 -1
- data/lib/otto/security/middleware/ip_privacy_middleware.rb +105 -4
- data/lib/otto/security/middleware/rate_limit_middleware.rb +1 -6
- data/lib/otto/security/rate_limiter.rb +81 -46
- data/lib/otto/static.rb +38 -0
- data/lib/otto/utils.rb +50 -0
- data/lib/otto/version.rb +1 -1
- data/lib/otto.rb +130 -13
- data/otto.gemspec +0 -2
- metadata +32 -41
- data/docs/.gitignore +0 -10
- data/docs/1108-STREAMING_ARCHITECTURE_ANALYSIS.md +0 -1105
- data/docs/1108-STREAMING_SUPPORT_SUMMARY.md +0 -376
- data/docs/enrichment.md +0 -128
- data/docs/geo-country.md +0 -181
- data/docs/ipaddr-encoding-quirk.md +0 -34
- data/docs/migrating/v2.0.0-pre1.md +0 -276
- data/docs/migrating/v2.0.0-pre2.md +0 -338
- data/docs/modern-authentication-authorization-landscape.md +0 -558
- data/docs/multi-strategy-authentication-design.md +0 -1401
- data/docs/reverse-proxy-network-services.md +0 -371
data/CHANGELOG.rst
CHANGED
|
@@ -7,6 +7,203 @@ The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`
|
|
|
7
7
|
|
|
8
8
|
<!--scriv-insert-here-->
|
|
9
9
|
|
|
10
|
+
.. _changelog-2.11.0:
|
|
11
|
+
|
|
12
|
+
2.11.0 — 2026-09-12
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Added
|
|
16
|
+
-----
|
|
17
|
+
|
|
18
|
+
- ``Otto#mount_static(prefix, root:)`` serves an explicit directory at a URL
|
|
19
|
+
prefix. See ``docs/guides/routing.md`` for configuration, dispatch
|
|
20
|
+
precedence, and migration from ``add_static_path``. (#267)
|
|
21
|
+
|
|
22
|
+
- ``Otto#not_found=`` and ``Otto#server_error=`` now accept callables for
|
|
23
|
+
per-request fallback responses. A server-error callable can receive the
|
|
24
|
+
exception; see ``docs/guides/routing.md`` for the callback contract. (#272)
|
|
25
|
+
|
|
26
|
+
Security
|
|
27
|
+
--------
|
|
28
|
+
|
|
29
|
+
- Static Rack triples configured with ``Otto#not_found=`` or
|
|
30
|
+
``Otto#server_error=`` are now copied for each request, preventing in-place
|
|
31
|
+
header changes, including ``Set-Cookie``, from being shared between fallback
|
|
32
|
+
responses. (#272)
|
|
33
|
+
|
|
34
|
+
Documentation
|
|
35
|
+
-------------
|
|
36
|
+
|
|
37
|
+
- Documented static-file dispatch precedence and migration from the removed
|
|
38
|
+
``add_static_path`` API in ``docs/guides/routing.md``. Corrected stale
|
|
39
|
+
``routes_static`` cache guidance in ``docs/guides/configuration_freezing.md``.
|
|
40
|
+
(#267)
|
|
41
|
+
|
|
42
|
+
.. _changelog-2.10.0:
|
|
43
|
+
|
|
44
|
+
2.10.0 — 2026-09-04
|
|
45
|
+
===================
|
|
46
|
+
|
|
47
|
+
Added
|
|
48
|
+
-----
|
|
49
|
+
|
|
50
|
+
- ``APIKeyStrategy`` now accepts a block or ``resolver:`` callable instead of a
|
|
51
|
+
static ``api_keys:`` list, enabling database-, repository-, or cache-backed
|
|
52
|
+
key lookup. The resolved account becomes the authenticated user.
|
|
53
|
+
|
|
54
|
+
- ``APIKeyStrategy.digest(key)`` returns a full SHA-256 hex digest for stores
|
|
55
|
+
that look up generated API keys by digest. See
|
|
56
|
+
``docs/guides/authentication.md`` for the resolver contract and credential
|
|
57
|
+
storage guidance.
|
|
58
|
+
|
|
59
|
+
- Directly exposed applications can now set ``trusted_proxies: :none`` or call
|
|
60
|
+
``trust_no_proxies!`` to distrust every peer. Otto then ignores forwarded
|
|
61
|
+
client IPs and strips forwarded host, scheme, and port metadata. Leaving proxy
|
|
62
|
+
trust unconfigured continues to preserve forwarded metadata. Applications
|
|
63
|
+
behind a reverse proxy, including one on loopback, must explicitly trust it.
|
|
64
|
+
The sentinel is only valid as the whole option; a list containing it, such
|
|
65
|
+
as ``['none']``, is rejected at configuration time. See
|
|
66
|
+
``docs/guides/forwarded-authority.md`` for configuration guidance. (#259)
|
|
67
|
+
|
|
68
|
+
Changed
|
|
69
|
+
-------
|
|
70
|
+
|
|
71
|
+
- MCP options now accept String or Symbol keys consistently and fail at boot
|
|
72
|
+
for unknown, conflicting, or invalid values. Constructor gating options must
|
|
73
|
+
be exactly ``true`` or ``false``; nil or blank token values are rejected.
|
|
74
|
+
Before upgrading, check option names against the `MCP guide
|
|
75
|
+
<docs/guides/mcp.md>`__. (#258)
|
|
76
|
+
|
|
77
|
+
- ``enable_mcp!`` now rejects constructor-only gating options and repeated
|
|
78
|
+
enablement. Pass all MCP settings in one ``Otto.new`` or ``enable_mcp!`` call.
|
|
79
|
+
(#258)
|
|
80
|
+
|
|
81
|
+
- MCP ``Rack::Attack`` throttle names are now endpoint-qualified, such as
|
|
82
|
+
``mcp_requests:/_mcp`` and ``mcp_tool_calls:/_mcp``. Update integrations that
|
|
83
|
+
inspect throttle names directly. (#258)
|
|
84
|
+
|
|
85
|
+
- Otto applications in one process that resolve proxied requests must now use
|
|
86
|
+
the same forwarded-header family; incompatible configurations fail during
|
|
87
|
+
configuration. CIDR-based proxy trust supports only ``X-Forwarded-*`` headers; use
|
|
88
|
+
depth-based trust for ``Forwarded`` or both families. See
|
|
89
|
+
``docs/guides/forwarded-authority.md`` for configuration guidance. (#252)
|
|
90
|
+
|
|
91
|
+
- Literal routes now consistently take precedence over static files at the same
|
|
92
|
+
path, regardless of which static files were requested earlier. Static files
|
|
93
|
+
continue to take precedence over dynamic routes. (#260)
|
|
94
|
+
|
|
95
|
+
- MaxMind-backed database paths now require ``maxmind-db ~> 1.2``. Before
|
|
96
|
+
upgrading, declare that range or provide a compatible reader object. See the
|
|
97
|
+
`geo-country <docs/guides/geo-country.md>`__ and
|
|
98
|
+
`enrichment <docs/guides/enrichment.md>`__ guides. (#255)
|
|
99
|
+
|
|
100
|
+
Removed
|
|
101
|
+
-------
|
|
102
|
+
|
|
103
|
+
- Removed ``add_static_path`` and the ``routes_static`` cache interface. Static
|
|
104
|
+
files are discovered directly from the configured ``public`` directory and
|
|
105
|
+
no longer need registration. (#260)
|
|
106
|
+
|
|
107
|
+
- Removed the unused ``rack-parser`` and ``rexml`` runtime dependencies.
|
|
108
|
+
Applications using either gem directly must now declare it themselves.
|
|
109
|
+
(#255)
|
|
110
|
+
|
|
111
|
+
Fixed
|
|
112
|
+
-----
|
|
113
|
+
|
|
114
|
+
- MCP middleware now executes rate limiting, authentication, and schema
|
|
115
|
+
validation in that order. Configured endpoints with a trailing slash are also
|
|
116
|
+
routable. (#258)
|
|
117
|
+
|
|
118
|
+
- Repeated MCP rate-limit configuration no longer produces duplicate throttle
|
|
119
|
+
log entries. (#258)
|
|
120
|
+
|
|
121
|
+
- MCP JSON-RPC errors now consistently use HTTP 400 for protocol errors, 404
|
|
122
|
+
for unknown resources or tools, and 500 for handler or server failures.
|
|
123
|
+
Unknown tools no longer return 500, failing resource handlers no longer
|
|
124
|
+
appear missing, and handler exception details are logged instead of returned
|
|
125
|
+
to clients. (#257)
|
|
126
|
+
|
|
127
|
+
- ``StrategyResult#has_role?`` and ``#has_permission?`` now derive their answer
|
|
128
|
+
from ``#roles`` and ``#permissions``, so the predicates agree with the
|
|
129
|
+
accessors for object-backed users exposing ``#roles`` and for ``Set`` or
|
|
130
|
+
other non-Array collections. A user model defining its own ``#has_role?`` or
|
|
131
|
+
``#has_permission?`` is still consulted first.
|
|
132
|
+
|
|
133
|
+
- MCP rate-limited requests now emit one ``rack.attack`` log entry instead of
|
|
134
|
+
two. (#255)
|
|
135
|
+
|
|
136
|
+
Security
|
|
137
|
+
--------
|
|
138
|
+
|
|
139
|
+
- ``APIKeyStrategy`` now requires exactly one key source and rejects empty
|
|
140
|
+
static key lists at construction. Invalid presented credentials terminate a
|
|
141
|
+
multi-strategy authentication chain. Static keys are compared in constant
|
|
142
|
+
time as fixed-width SHA-256 digests, so configured key lengths are not
|
|
143
|
+
observable through timing. Blank credentials, empty or whitespace-only, are
|
|
144
|
+
rejected before any key source is consulted. (#256)
|
|
145
|
+
|
|
146
|
+
- Successful authentication results no longer expose the raw API key in
|
|
147
|
+
strategy-generated fields. Static-list callers should replace
|
|
148
|
+
``user[:api_key]`` with ``user[:api_key_fingerprint]``. (#256)
|
|
149
|
+
|
|
150
|
+
- ``APIKeyStrategy`` now reads only the configured header by default. Applications
|
|
151
|
+
that still accept query or form parameters must opt in with ``param_name:``;
|
|
152
|
+
see ``docs/guides/authentication.md`` for configuration guidance. (#256)
|
|
153
|
+
|
|
154
|
+
- Fixed MCP bearer-token authentication being silently omitted when configured
|
|
155
|
+
through ``Otto.new`` or with String-keyed options. Deployments exposing MCP
|
|
156
|
+
beyond a trusted local environment should upgrade and verify their
|
|
157
|
+
``auth_tokens`` configuration. Intentionally open endpoints now warn unless
|
|
158
|
+
acknowledged with ``allow_unauthenticated: true``. See the `MCP guide
|
|
159
|
+
<docs/guides/mcp.md>`__. (#258)
|
|
160
|
+
|
|
161
|
+
- MCP authentication now fails closed when its authenticator is unavailable.
|
|
162
|
+
Authentication, validation, and rate limiting also apply only to the exact
|
|
163
|
+
routed MCP endpoint. (#258)
|
|
164
|
+
|
|
165
|
+
- MCP rate limiting now honors configured limits and protects custom endpoints,
|
|
166
|
+
Rack-mounted applications, and distinct MCP endpoints in the same process.
|
|
167
|
+
Mount ``Rack::Attack`` inside the same ``map`` block as Otto, and use distinct
|
|
168
|
+
endpoint paths when applications require isolated counters. (#258)
|
|
169
|
+
|
|
170
|
+
- Static-file serving now rejects symlinks that resolve outside the configured
|
|
171
|
+
public directory, preventing files outside that root from being served on
|
|
172
|
+
first access or after another file has populated the directory cache.
|
|
173
|
+
Symlinks that resolve within the public directory, including a symlinked
|
|
174
|
+
public root, remain supported. (#257)
|
|
175
|
+
|
|
176
|
+
- When proxy trust is configured, forwarded host, scheme, and port metadata
|
|
177
|
+
from untrusted peers is now stripped before it can affect Rack's request
|
|
178
|
+
authority. Requests from trusted peers retain this metadata; see
|
|
179
|
+
``docs/guides/forwarded-authority.md`` for deployment guidance. (#252)
|
|
180
|
+
|
|
181
|
+
- ``IPPrivacyMiddleware`` enforces its own proxy trust posture even when an
|
|
182
|
+
outer instance already resolved ``otto.client_ip``. ``trusted_proxies: :none``
|
|
183
|
+
always strips forwarded authority; a CIDR configuration that can no longer
|
|
184
|
+
match the connecting peer treats it as untrusted and logs a warning. (#259)
|
|
185
|
+
|
|
186
|
+
- ``env['otto.peer_relayed']`` records whether a request carried any relay
|
|
187
|
+
marker header, evaluated before forwarded carriers may be deleted, so
|
|
188
|
+
``Otto::CaddyTLS::LocalhostGuard`` still refuses a relayed loopback call once
|
|
189
|
+
the carriers are stripped. The relay markers cover every carrier the scrub
|
|
190
|
+
deletes, including ``X-Forwarded-Host`` and the other authority headers, not
|
|
191
|
+
only the client-IP carriers. (#259)
|
|
192
|
+
|
|
193
|
+
- MCP now stops configuration with ``Otto::OptionalDependencyError`` rather
|
|
194
|
+
than starting without enabled schema validation or rate limiting. Before
|
|
195
|
+
upgrading, add ``json_schemer ~> 2.0`` and ``rack-attack ~> 6.7`` for the
|
|
196
|
+
default protections, or explicitly set ``enable_validation: false`` or
|
|
197
|
+
``enable_rate_limiting: false`` only when that protection is not required.
|
|
198
|
+
See the `MCP guide <docs/guides/mcp.md>`__. (#255)
|
|
199
|
+
|
|
200
|
+
Documentation
|
|
201
|
+
-------------
|
|
202
|
+
|
|
203
|
+
- Added the `MCP guide <docs/guides/mcp.md>`__ covering secure enablement,
|
|
204
|
+
supported options, authentication, validation, rate limiting, mounted
|
|
205
|
+
applications, and error behavior. (#258)
|
|
206
|
+
|
|
10
207
|
.. _changelog-2.9.0:
|
|
11
208
|
|
|
12
209
|
2.9.0 — 2026-08-18
|
|
@@ -790,6 +987,44 @@ Added
|
|
|
790
987
|
2.0.0 — 2026-03-14
|
|
791
988
|
==================
|
|
792
989
|
|
|
990
|
+
This is the stable release of Otto v2, the culmination of 10 pre-releases
|
|
991
|
+
since September 2025.
|
|
992
|
+
|
|
993
|
+
Highlights
|
|
994
|
+
----------
|
|
995
|
+
|
|
996
|
+
- **Modular architecture**: the core ``Otto`` class is now a thin composition
|
|
997
|
+
of focused modules (Router, FileSafety, Configuration, ErrorHandler,
|
|
998
|
+
UriGenerator).
|
|
999
|
+
- **Security by default**: IP masking, user agent anonymization, CSRF
|
|
1000
|
+
protection, input validation, and backtrace sanitization.
|
|
1001
|
+
- **Privacy by default**: public IP masking, country-level geo-location only
|
|
1002
|
+
(no external APIs), daily-rotating IP hashes for analytics.
|
|
1003
|
+
- **Handler-level authentication**: authentication moved from middleware to
|
|
1004
|
+
``RouteAuthWrapper``, so it runs after routing.
|
|
1005
|
+
- **Configuration freezing**: configuration is frozen after the first request
|
|
1006
|
+
to prevent runtime security bypasses.
|
|
1007
|
+
- **MCP support**: JSON-RPC 2.0 endpoints for CLI automation and integrations.
|
|
1008
|
+
- **Base error classes**: ``NotFoundError``, ``BadRequestError``,
|
|
1009
|
+
``ForbiddenError`` and friends, with automatic HTTP status codes.
|
|
1010
|
+
- **Request/response helpers**: extensible ``Otto::Request`` and
|
|
1011
|
+
``Otto::Response`` with application-specific helper registration.
|
|
1012
|
+
|
|
1013
|
+
Breaking changes
|
|
1014
|
+
----------------
|
|
1015
|
+
|
|
1016
|
+
Individual breaking changes are documented in the pre-release entries below.
|
|
1017
|
+
The migrations most applications need:
|
|
1018
|
+
|
|
1019
|
+
- Logic class constructor: ``initialize(session, user, params, locale)`` →
|
|
1020
|
+
``initialize(context, params, locale)``
|
|
1021
|
+
- Middleware stack: ``otto.middleware_stack <<`` → ``otto.use()``
|
|
1022
|
+
- Request callbacks: ``Otto.on_request_complete`` → ``otto.on_request_complete``
|
|
1023
|
+
(instance method)
|
|
1024
|
+
|
|
1025
|
+
See `docs/migrating/v2.0.0.md <docs/migrating/v2.0.0.md>`__ for the full
|
|
1026
|
+
upgrade guide.
|
|
1027
|
+
|
|
793
1028
|
Added
|
|
794
1029
|
-----
|
|
795
1030
|
|
data/Gemfile
CHANGED
|
@@ -18,9 +18,9 @@ end
|
|
|
18
18
|
# bundle config set with 'optional'
|
|
19
19
|
group :development, :test, optional: true do
|
|
20
20
|
# Keep gems that need to be in both environments
|
|
21
|
-
gem 'json_schemer'
|
|
21
|
+
gem 'json_schemer', '~> 2.0'
|
|
22
22
|
gem 'maxmind-db', '~> 1.2' # Optional geo DB reader; exercised by geo specs
|
|
23
|
-
gem 'rack-attack'
|
|
23
|
+
gem 'rack-attack', '~> 6.7'
|
|
24
24
|
gem 'reek', '~> 6.5'
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ group :development do
|
|
|
29
29
|
gem 'debug'
|
|
30
30
|
gem 'rackup' # Used to boot examples/ apps; not needed by specs
|
|
31
31
|
gem 'rake', '~> 13.4', require: false # Provides `rake release` for release-gem.yml
|
|
32
|
-
gem 'rubocop', '~> 1.
|
|
32
|
+
gem 'rubocop', '~> 1.90.0', require: false
|
|
33
33
|
gem 'rubocop-performance', require: false
|
|
34
34
|
gem 'rubocop-rspec', require: false
|
|
35
35
|
gem 'rubocop-thread_safety', require: false
|
data/Gemfile.lock
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
otto (2.
|
|
4
|
+
otto (2.11.0)
|
|
5
5
|
concurrent-ruby (~> 1.3, < 2.0)
|
|
6
6
|
logger (~> 1, < 2.0)
|
|
7
7
|
loofah (~> 2.20)
|
|
8
8
|
rack (~> 3.1, < 4.0)
|
|
9
|
-
rack-parser (~> 0.7)
|
|
10
|
-
rexml (~> 3.4)
|
|
11
9
|
|
|
12
10
|
GEM
|
|
13
11
|
remote: https://rubygems.org/
|
|
@@ -106,18 +104,16 @@ GEM
|
|
|
106
104
|
date
|
|
107
105
|
stringio
|
|
108
106
|
racc (1.8.1)
|
|
109
|
-
rack (3.2.
|
|
107
|
+
rack (3.2.7)
|
|
110
108
|
rack-attack (6.8.0)
|
|
111
109
|
rack (>= 1.0, < 4)
|
|
112
|
-
rack-parser (0.7.0)
|
|
113
|
-
rack
|
|
114
110
|
rack-test (2.2.0)
|
|
115
111
|
rack (>= 1.3)
|
|
116
112
|
rackup (2.3.1)
|
|
117
113
|
rack (>= 3)
|
|
118
114
|
rainbow (3.1.1)
|
|
119
115
|
rake (13.4.2)
|
|
120
|
-
rbs (4.
|
|
116
|
+
rbs (4.1.3)
|
|
121
117
|
logger
|
|
122
118
|
prism (>= 1.6.0)
|
|
123
119
|
tsort
|
|
@@ -148,8 +144,8 @@ GEM
|
|
|
148
144
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
149
145
|
rspec-support (~> 3.13.0)
|
|
150
146
|
rspec-support (3.13.7)
|
|
151
|
-
rubocop (1.
|
|
152
|
-
json (
|
|
147
|
+
rubocop (1.90.0)
|
|
148
|
+
json (>= 2.3)
|
|
153
149
|
language_server-protocol (~> 3.17.0.2)
|
|
154
150
|
lint_roller (~> 1.1.0)
|
|
155
151
|
parallel (>= 1.10)
|
|
@@ -162,9 +158,9 @@ GEM
|
|
|
162
158
|
rubocop-ast (1.50.0)
|
|
163
159
|
parser (>= 3.3.7.2)
|
|
164
160
|
prism (~> 1.7)
|
|
165
|
-
rubocop-performance (1.
|
|
161
|
+
rubocop-performance (1.27.0)
|
|
166
162
|
lint_roller (~> 1.1)
|
|
167
|
-
rubocop (>= 1.
|
|
163
|
+
rubocop (>= 1.89.0, < 2.0)
|
|
168
164
|
rubocop-ast (>= 1.47.1, < 2.0)
|
|
169
165
|
rubocop-rspec (3.10.2)
|
|
170
166
|
lint_roller (~> 1.1)
|
|
@@ -174,7 +170,7 @@ GEM
|
|
|
174
170
|
lint_roller (~> 1.1)
|
|
175
171
|
rubocop (~> 1.72, >= 1.72.1)
|
|
176
172
|
rubocop-ast (>= 1.44.0, < 2.0)
|
|
177
|
-
ruby-lsp (0.26.
|
|
173
|
+
ruby-lsp (0.26.11)
|
|
178
174
|
language_server-protocol (~> 3.17.0)
|
|
179
175
|
prism (>= 1.2, < 2.0)
|
|
180
176
|
rbs (>= 3, < 5)
|
|
@@ -217,17 +213,17 @@ PLATFORMS
|
|
|
217
213
|
DEPENDENCIES
|
|
218
214
|
benchmark
|
|
219
215
|
debug
|
|
220
|
-
json_schemer
|
|
216
|
+
json_schemer (~> 2.0)
|
|
221
217
|
kramdown
|
|
222
218
|
maxmind-db (~> 1.2)
|
|
223
219
|
otto!
|
|
224
|
-
rack-attack
|
|
220
|
+
rack-attack (~> 6.7)
|
|
225
221
|
rack-test
|
|
226
222
|
rackup
|
|
227
223
|
rake (~> 13.4)
|
|
228
224
|
reek (~> 6.5)
|
|
229
225
|
rspec (~> 3.13)
|
|
230
|
-
rubocop (~> 1.
|
|
226
|
+
rubocop (~> 1.90.0)
|
|
231
227
|
rubocop-performance
|
|
232
228
|
rubocop-rspec
|
|
233
229
|
rubocop-thread_safety
|
data/README.md
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
|
-
# Otto -
|
|
1
|
+
# Otto - All Rack, no Pinion
|
|
2
2
|
|
|
3
|
-
**Define
|
|
4
|
-
|
|
5
|
-
> **v2.0.0-pre6 Available**: This pre-release includes major improvements to middleware management, logging, and request callback handling. See [changelog](CHANGELOG.rst) for details and upgrade notes.
|
|
3
|
+
**Define Rack apps in plain text, with privacy by default and opt-in security features.**
|
|
6
4
|
|
|
7
5
|

|
|
8
6
|
|
|
9
|
-
Otto apps have three files: a rackup file, a Ruby class, and a routes file. The routes file is
|
|
7
|
+
Otto apps have three files: a rackup file, a Ruby class, and a routes file. The routes file is plain text that maps URLs to Ruby methods.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
Requirements: Ruby `>= 3.2, < 4.1` and Rack `>= 3.1, < 4.0`. Ruby 3.2
|
|
12
|
+
remains compatibility-tested after upstream end of life, but receives no
|
|
13
|
+
interpreter security maintenance. See the [runtime and dependency security
|
|
14
|
+
policy](docs/reference/runtime-and-dependency-security.md) for support tiers and
|
|
15
|
+
consumer lockfile requirements.
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
Install Otto and the Rack server CLI, then create the three files shown below:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
gem install otto rackup
|
|
21
|
+
mkdir myapp && cd myapp
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
After creating `routes`, `app.rb`, and `config.ru`, start the app and verify the response:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
rackup config.ru
|
|
28
|
+
# In another terminal:
|
|
29
|
+
curl -i http://127.0.0.1:9292/
|
|
30
|
+
# HTTP/1.1 200 OK
|
|
31
|
+
# ...
|
|
32
|
+
# <h1>Hello Otto</h1>
|
|
14
33
|
```
|
|
15
34
|
|
|
16
35
|
## Why Otto?
|
|
17
36
|
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
37
|
+
- **Privacy by Default**: Masks public IP addresses and anonymizes user agents; country-level geo-location needs no external API
|
|
38
|
+
- **Opt-in Security Features**: CSRF protection, input validation, security headers, and trusted-proxy configuration
|
|
20
39
|
- **Simple Routing**: Define routes in plain-text files with zero configuration overhead
|
|
21
40
|
- **Built-in Authentication**: Multiple strategies including API keys, tokens, role-based access, and custom implementations
|
|
22
41
|
- **Developer Friendly**: Works with any Rack server, minimal dependencies, easy testing and debugging
|
|
@@ -26,10 +45,8 @@ config.ru app.rb routes
|
|
|
26
45
|
# routes
|
|
27
46
|
|
|
28
47
|
GET / App#index
|
|
29
|
-
POST /feedback App#receive_feedback
|
|
30
48
|
GET /product/:id App#show_product
|
|
31
49
|
GET /robots.txt App#robots_text
|
|
32
|
-
GET /404 App#not_found
|
|
33
50
|
```
|
|
34
51
|
|
|
35
52
|
## Ruby Class
|
|
@@ -37,6 +54,8 @@ GET /404 App#not_found
|
|
|
37
54
|
# app.rb
|
|
38
55
|
|
|
39
56
|
class App
|
|
57
|
+
attr_reader :req, :res
|
|
58
|
+
|
|
40
59
|
def initialize(req, res)
|
|
41
60
|
@req, @res = req, res
|
|
42
61
|
end
|
|
@@ -51,7 +70,7 @@ class App
|
|
|
51
70
|
end
|
|
52
71
|
|
|
53
72
|
def robots_text
|
|
54
|
-
res.
|
|
73
|
+
res.headers['content-type'] = 'text/plain'
|
|
55
74
|
rules = 'User-agent: *', 'Disallow: /private/keep/out'
|
|
56
75
|
res.body = rules.join($/)
|
|
57
76
|
end
|
|
@@ -63,9 +82,9 @@ end
|
|
|
63
82
|
# config.ru
|
|
64
83
|
|
|
65
84
|
require 'otto'
|
|
66
|
-
|
|
85
|
+
require_relative 'app'
|
|
67
86
|
|
|
68
|
-
run Otto.new(
|
|
87
|
+
run Otto.new('routes')
|
|
69
88
|
```
|
|
70
89
|
|
|
71
90
|
|
|
@@ -82,7 +101,27 @@ app = Otto.new("./routes", {
|
|
|
82
101
|
})
|
|
83
102
|
```
|
|
84
103
|
|
|
85
|
-
Security features include CSRF protection, input validation, security headers, and trusted proxy configuration.
|
|
104
|
+
Security features include CSRF protection, input validation, security headers, rate limiting, and trusted proxy configuration.
|
|
105
|
+
|
|
106
|
+
### Rate limiting (`rack-attack`)
|
|
107
|
+
|
|
108
|
+
Rate limiting requires `rack-attack` 6.7.0 or newer in the 6.x series. Add the
|
|
109
|
+
optional dependency and mount it before Otto; `enable_rate_limiting!` configures
|
|
110
|
+
rules, while `Rack::Attack` enforces them:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# Gemfile
|
|
114
|
+
gem 'rack-attack', '~> 6.7'
|
|
115
|
+
|
|
116
|
+
# config.ru
|
|
117
|
+
use Rack::Attack
|
|
118
|
+
app = Otto.new('./routes')
|
|
119
|
+
app.enable_rate_limiting!(requests_per_minute: 50)
|
|
120
|
+
run app
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Enabling rate limiting raises `Otto::OptionalDependencyError` at configuration
|
|
124
|
+
time when the gem is missing or outside the supported range.
|
|
86
125
|
|
|
87
126
|
### Content Security Policy (nonce-based emission)
|
|
88
127
|
|
|
@@ -300,7 +339,7 @@ Private and localhost IPs are exempted by default for development convenience, b
|
|
|
300
339
|
```ruby
|
|
301
340
|
otto.configure_ip_privacy(
|
|
302
341
|
geo_header: 'X-Client-Country', # trusted app header, checked first
|
|
303
|
-
geo_db_path: 'data/country.mmdb' # offline fallback (needs
|
|
342
|
+
geo_db_path: 'data/country.mmdb' # offline fallback (needs maxmind-db >= 1.2.0, < 2)
|
|
304
343
|
)
|
|
305
344
|
```
|
|
306
345
|
|
|
@@ -395,8 +434,9 @@ on_demand_tls {
|
|
|
395
434
|
|
|
396
435
|
Secure by default: the endpoint is restricted to the loopback interface (the guard
|
|
397
436
|
authenticates the raw TCP peer, so a spoofed `X-Forwarded-For` cannot help), and
|
|
398
|
-
every layer fails closed. See [
|
|
399
|
-
for
|
|
437
|
+
every layer fails closed. See the [Caddy TLS guide](docs/guides/caddy-tls.md)
|
|
438
|
+
for deployment guidance and [ADR-003](docs/adr/adr-003-caddy-tls-route-based-integration.md)
|
|
439
|
+
for rationale.
|
|
400
440
|
|
|
401
441
|
## Examples
|
|
402
442
|
|
|
@@ -406,7 +446,7 @@ Otto includes comprehensive examples demonstrating different features:
|
|
|
406
446
|
- **[Advanced Routes](examples/advanced_routes/)** - Response types, CSRF exemption, logic classes, and namespaced routing
|
|
407
447
|
- **[Authentication Strategies](examples/authentication_strategies/)** - Token, API key, and role-based authentication
|
|
408
448
|
- **[Security Features](examples/security_features/)** - CSRF protection, input validation, file uploads, and security headers
|
|
409
|
-
- **[MCP Demo](examples/mcp_demo/)** - JSON-RPC 2.0 endpoints for CLI automation and integrations
|
|
449
|
+
- **[MCP Demo](examples/mcp_demo/)** - JSON-RPC 2.0 endpoints for CLI automation and integrations (see the [MCP guide](docs/guides/mcp.md))
|
|
410
450
|
- **[Caddy on-demand TLS](examples/caddy_tls_demo/)** - Reverse-proxy permission endpoint via `Otto::CaddyTLS`
|
|
411
451
|
|
|
412
452
|
### Standalone Tutorials
|
|
@@ -417,32 +457,30 @@ Otto includes comprehensive examples demonstrating different features:
|
|
|
417
457
|
|
|
418
458
|
See the [examples/](examples/) directory for more.
|
|
419
459
|
|
|
420
|
-
## Requirements
|
|
421
460
|
|
|
422
|
-
|
|
423
|
-
- Rack 3.1+
|
|
461
|
+
## Documentation
|
|
424
462
|
|
|
425
|
-
|
|
463
|
+
- **[Documentation map](docs/README.md)** - Capabilities, current guides, and the documentation structure Otto is growing toward
|
|
464
|
+
- **[Runtime and dependency security policy](docs/reference/runtime-and-dependency-security.md)** - Ruby compatibility, dependency-range guarantees, and consumer lockfile auditing
|
|
465
|
+
- **[CHANGELOG.rst](CHANGELOG.rst)** - Version history, breaking changes, and upgrade notes
|
|
426
466
|
|
|
427
|
-
|
|
428
|
-
gem install otto
|
|
429
|
-
```
|
|
467
|
+
## AI-assisted development
|
|
430
468
|
|
|
431
|
-
|
|
469
|
+
AI tools have contributed to Otto since v1.2.0. They are used for implementation, testing, documentation, maintenance, and additional code review.
|
|
432
470
|
|
|
433
|
-
|
|
434
|
-
- **[docs/](docs/)** - Technical guides and migration guides
|
|
435
|
-
- **[CHANGELOG.rst](CHANGELOG.rst)** - Version history, breaking changes, and upgrade notes
|
|
471
|
+
### Tools used
|
|
436
472
|
|
|
437
|
-
|
|
473
|
+
* **Claude Code CLI (Opus and Fable)** - Primary development tool for implementation, test coverage, and adversarial review
|
|
474
|
+
* **Greptile and GitHub Copilot** - Pull request reviews
|
|
475
|
+
* **Claude Desktop routines** - Unattended cloud agents that run on a cron schedule and open pull requests for maintenance chores. Nobody is at the keyboard for these, so they go through the same PR review as human work.
|
|
438
476
|
|
|
439
|
-
|
|
477
|
+
### How it is tracked
|
|
440
478
|
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
479
|
+
* All PR reviews are publicly visible. The `greptile-review` and `claude-review` labels are applied to PRs that have been reviewed.
|
|
480
|
+
* Release notes in [CHANGELOG.rst](CHANGELOG.rst) list AI-assisted work under an "AI Assistance" heading.
|
|
481
|
+
* [AGENTS.md](AGENTS.md) sets the conventions agents follow in this repository.
|
|
444
482
|
|
|
445
|
-
The maintainer
|
|
483
|
+
The maintainer reviews the resulting changes and remains responsible for security, implementation, and releases.
|
|
446
484
|
|
|
447
485
|
## License
|
|
448
486
|
|