otto 2.9.0 → 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.
- 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 +7 -5
- data/AGENTS.md +22 -1
- data/CHANGELOG.rst +203 -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 +146 -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 +181 -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 +36 -22
- data/lib/otto/core/file_safety.rb +89 -31
- data/lib/otto/core/middleware_stack.rb +36 -36
- data/lib/otto/core/router.rb +62 -19
- 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/utils.rb +50 -0
- data/lib/otto/version.rb +1 -1
- data/lib/otto.rb +9 -11
- data/otto.gemspec +0 -2
- metadata +31 -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/Gemfile.lock
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
otto (2.
|
|
4
|
+
otto (2.10.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
|
|
data/docs/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Otto documentation
|
|
2
|
+
|
|
3
|
+
Otto is a Rack router whose application model starts with a plain-text route
|
|
4
|
+
file and Ruby handlers. It also provides opt-in security controls,
|
|
5
|
+
privacy-preserving request handling, authentication hooks, and small
|
|
6
|
+
integrations for network services.
|
|
7
|
+
|
|
8
|
+
This page is the documentation map. It is intentionally a map, not an
|
|
9
|
+
exhaustive API reference: public classes, method signatures, and implementation
|
|
10
|
+
details remain in the code and its generated API documentation. Guides should
|
|
11
|
+
explain when to use a capability, its security and operational constraints, and
|
|
12
|
+
the smallest complete configuration that uses it safely.
|
|
13
|
+
|
|
14
|
+
## Capabilities at a glance
|
|
15
|
+
|
|
16
|
+
| Area | What Otto provides | Reader-facing guide or next destination |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| Application model | Plain-text routes; class, instance, Logic-class, and registered lambda handlers; response selection | [Routing](guides/routing.md) and [route syntax](reference/route-syntax.md) |
|
|
19
|
+
| Request lifecycle | Rack integration, middleware ordering, helper registration, lifecycle hooks, and boot-time configuration | `guides/application-lifecycle.md` |
|
|
20
|
+
| Authentication and authorization | Named authentication strategies, ordered multi-strategy fallback, terminal failures, route roles, and resource-level authorization | [Authentication](guides/authentication.md) |
|
|
21
|
+
| Security | CSRF enforcement, request validation, rate limiting, security headers, CSP, error handling, and trusted proxies | `guides/security.md`; [forwarded host authority](guides/forwarded-authority.md) |
|
|
22
|
+
| Runtime and dependencies | Ruby compatibility tiers, upstream security-maintenance limits, dependency-range guarantees, and consumer lockfile auditing | [Runtime and dependency security policy](reference/runtime-and-dependency-security.md) |
|
|
23
|
+
| Privacy and network identity | IP privacy profiles, privacy-safe client signals, country resolution, ASN lookup, and anonymizer classification | [Privacy](guides/privacy.md), [geo-country](guides/geo-country.md), and [enrichment](guides/enrichment.md) |
|
|
24
|
+
| Internationalization | Locale configuration and request locale resolution | `guides/locales.md` |
|
|
25
|
+
| Operations | Structured logging, safe error reporting, static files, and testing Otto applications | `guides/operations.md` |
|
|
26
|
+
| Integrations | Model Context Protocol (MCP) endpoints and Caddy on-demand TLS permission checks | [MCP](guides/mcp.md) and [Caddy TLS](guides/caddy-tls.md) |
|
|
27
|
+
|
|
28
|
+
## Start here today
|
|
29
|
+
|
|
30
|
+
- [Project README](../README.md) — installation and a minimal Rack app.
|
|
31
|
+
- [Runtime and dependency security policy](reference/runtime-and-dependency-security.md)
|
|
32
|
+
— choose a maintained Ruby and keep the application's resolved bundle audited.
|
|
33
|
+
- [Routing guide](guides/routing.md) — choose a handler style and response
|
|
34
|
+
contract.
|
|
35
|
+
- [Authentication guide](guides/authentication.md) — protect routes and separate
|
|
36
|
+
authentication from authorization.
|
|
37
|
+
- [Privacy guide](guides/privacy.md) — privacy profiles and request-safe client
|
|
38
|
+
signals.
|
|
39
|
+
- [Geo-country resolution](guides/geo-country.md) — trusted headers, local MMDB
|
|
40
|
+
fallback, and the privacy model.
|
|
41
|
+
- [ASN and anonymizer enrichment](guides/enrichment.md) — opt-in network signals
|
|
42
|
+
and their database contracts.
|
|
43
|
+
- [Forwarded host authority](guides/forwarded-authority.md) — trust-gated
|
|
44
|
+
handling of `X-Forwarded-Host` and `Forwarded`, and the process-global Rack
|
|
45
|
+
forwarding family.
|
|
46
|
+
- [MCP guide](guides/mcp.md) — enable the JSON-RPC endpoint, require bearer
|
|
47
|
+
tokens, and tune rate limits.
|
|
48
|
+
- [Caddy TLS integration](guides/caddy-tls.md) — deploy the loopback-only
|
|
49
|
+
permission endpoint.
|
|
50
|
+
- [Migration guides](migrating/) — version-specific behavior changes.
|
|
51
|
+
- [Changelog](../CHANGELOG.rst) — release history and upgrade-impacting changes.
|
|
52
|
+
|
|
53
|
+
The [architecture decision records](adr/) preserve durable technical rationale.
|
|
54
|
+
They are **not** the primary entry point for implementing an application:
|
|
55
|
+
|
|
56
|
+
- [ADR-001: Route authentication at the handler boundary](adr/adr-001-route-authentication-at-handler-boundary.md)
|
|
57
|
+
- [ADR-002: Multi-strategy authentication and authorization](adr/adr-002-multi-strategy-authentication-and-authorization.md)
|
|
58
|
+
- [ADR-003: Caddy TLS route-based integration](adr/adr-003-caddy-tls-route-based-integration.md)
|
|
59
|
+
- [ADR-004: Compatibility support and security maintenance](adr/adr-004-separate-compatibility-from-security-maintenance.md)
|
|
60
|
+
- [Ruby `IPAddr#to_s` encoding note](guides/ipaddr-encoding-quirk.md)
|
|
61
|
+
|
|
62
|
+
## Target structure
|
|
63
|
+
|
|
64
|
+
The documentation should grow by reader task and stability, not by the order in
|
|
65
|
+
which implementation work occurred:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
docs/
|
|
69
|
+
├── README.md # this map and current entry point
|
|
70
|
+
├── getting-started.md # first app after the README example
|
|
71
|
+
├── guides/ # stable, task-oriented application guides
|
|
72
|
+
│ ├── routing.md
|
|
73
|
+
│ ├── application-lifecycle.md
|
|
74
|
+
│ ├── authentication.md
|
|
75
|
+
│ ├── security.md
|
|
76
|
+
│ ├── privacy.md
|
|
77
|
+
│ ├── locales.md
|
|
78
|
+
│ └── operations.md
|
|
79
|
+
├── integrations/ # deployment-specific contracts
|
|
80
|
+
│ ├── mcp.md
|
|
81
|
+
│ └── caddy-tls.md
|
|
82
|
+
├── reference/ # compact, stable contracts—not a code mirror
|
|
83
|
+
│ ├── route-syntax.md
|
|
84
|
+
│ ├── configuration.md
|
|
85
|
+
│ ├── request-and-response.md
|
|
86
|
+
│ ├── errors.md
|
|
87
|
+
│ └── runtime-and-dependency-security.md
|
|
88
|
+
├── migrating/ # release-specific upgrade guides
|
|
89
|
+
├── adr/ # accepted architecture decision records
|
|
90
|
+
└── maintainers/
|
|
91
|
+
└── investigations/ # local working notes; untracked by design
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### What belongs where
|
|
95
|
+
|
|
96
|
+
- **Getting started** gets a reader from an installed gem to a running app and
|
|
97
|
+
links to the next common task.
|
|
98
|
+
- **Guides** answer a single application task. They include safe defaults,
|
|
99
|
+
prerequisites, a compact example, verification, and links to the relevant
|
|
100
|
+
compact reference.
|
|
101
|
+
- **Integrations** document an external system's HTTP or deployment contract,
|
|
102
|
+
including trust boundaries and failure behavior.
|
|
103
|
+
- **Reference** records only contracts that must be precise across multiple
|
|
104
|
+
guides: route grammar, configuration names, request/response helpers, error
|
|
105
|
+
behavior, and environment keys. It links to code rather than repeating every
|
|
106
|
+
method.
|
|
107
|
+
- **Migration** documents a release-bound action and its before/after behavior.
|
|
108
|
+
It does not become a general guide.
|
|
109
|
+
- **Architecture decision records** preserve the context, decision, and
|
|
110
|
+
consequences of durable technical choices without asking application developers
|
|
111
|
+
to infer the current contract from a proposal.
|
|
112
|
+
- **Maintainer investigations** preserve unfinished exploration separately from
|
|
113
|
+
accepted decisions and application documentation.
|
|
114
|
+
|
|
115
|
+
## Migration plan for the current directory
|
|
116
|
+
|
|
117
|
+
This is a classification plan, not a request to rewrite every document now.
|
|
118
|
+
Move or replace a document only when its destination guide is ready. Some
|
|
119
|
+
working-tree documents are currently ignored by `docs/.gitignore`; reconcile
|
|
120
|
+
and explicitly track them before treating them as published documentation.
|
|
121
|
+
|
|
122
|
+
| Current material | Target disposition | Reason |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| `geo-country.md`, `enrichment.md` | Fold into `guides/privacy.md`; retain focused pages while they remain useful | They are current, task-oriented, and contain important privacy constraints. |
|
|
125
|
+
| `authentication.md`, `AUTH_STRATEGIES.txt` | Replace with `guides/authentication.md` and `reference/route-syntax.md` | The guide should reflect current strategy results, role rules, ordered fallback, and terminal failures in one maintained place. |
|
|
126
|
+
| `ADVANCED_ROUTES.txt` | Replace with `guides/routing.md` and `reference/route-syntax.md` | Route grammar and target kinds are a public contract; the current quick reference is incomplete for modern handler types and security-gating validation. |
|
|
127
|
+
| `ip_privacy.md`, `structured_logging.md`, `configuration_freezing.md` | Reconcile against current behavior, then fold into privacy, operations, and lifecycle guides | They describe durable concepts, but must be verified before being promoted as canonical documentation. |
|
|
128
|
+
| `reverse-proxy-network-services.md` | Extract `guides/caddy-tls.md`; retain its decision as [ADR-003](adr/adr-003-caddy-tls-route-based-integration.md) | Operators need a concise deployment guide; implementation rationale should remain separately discoverable. |
|
|
129
|
+
| `MCP_IMPLEMENTATION.md` | Done: published as [guides/mcp.md](guides/mcp.md); implementation notes belong under `maintainers/` | The protocol user and the maintainer have different questions. |
|
|
130
|
+
| `multi-strategy-authentication-design.md`, `route-auth-wrapper-resolution.md` | Retain the accepted decisions as [ADR-002](adr/adr-002-multi-strategy-authentication-and-authorization.md) and [ADR-001](adr/adr-001-route-authentication-at-handler-boundary.md) | They explain durable architecture choices rather than the current application contract. |
|
|
131
|
+
| Dated architecture, hardening, enhancement, and streaming files | Move to `maintainers/investigations/`; keep only active proposals in the main tree | Dates and working notes are valuable history but should not compete with supported guides. |
|
|
132
|
+
| `testing-guide.md` | Reconcile with current test support, then publish as `guides/operations.md` or `guides/testing.md` | Testing is an application task, not an implementation design. |
|
|
133
|
+
|
|
134
|
+
## Documentation rules for future changes
|
|
135
|
+
|
|
136
|
+
1. Add a capability to this map only when a stable guide exists, or when the
|
|
137
|
+
README provides the complete safe first-use example.
|
|
138
|
+
2. Update the affected guide and its compact reference in the same change as a
|
|
139
|
+
public behavior change. Use the changelog for the release record, not as a
|
|
140
|
+
substitute for instructions.
|
|
141
|
+
3. Record security defaults, trust boundaries, configuration-freezing timing,
|
|
142
|
+
and failure behavior where they affect use. Those are part of the contract.
|
|
143
|
+
4. Keep a proposal separate from its outcome. When work lands, add a short
|
|
144
|
+
status/outcome note and link from the guide to the decision only when the
|
|
145
|
+
rationale helps a maintainer.
|
|
146
|
+
5. Prefer one canonical page for each task. Other pages should link to it rather
|
|
147
|
+
than copy examples or option lists.
|
|
148
|
+
6. Validate every new command and example against the supported Ruby and Rack
|
|
149
|
+
versions before publishing it as runnable documentation.
|
|
150
|
+
|
|
151
|
+
## First documentation milestone
|
|
152
|
+
|
|
153
|
+
A useful first milestone is intentionally small:
|
|
154
|
+
|
|
155
|
+
1. Publish this map and make it the `README` documentation destination. **Done.**
|
|
156
|
+
2. Write `reference/route-syntax.md` from `Otto::RouteDefinition` and route
|
|
157
|
+
handler behavior, including handler kinds and fail-fast `auth=`, `role=`, and
|
|
158
|
+
`csrf=` option syntax. **Done.**
|
|
159
|
+
3. Publish `guides/routing.md`, `guides/authentication.md`, `guides/privacy.md`,
|
|
160
|
+
and `guides/caddy-tls.md` by reconciling the existing material with the
|
|
161
|
+
current code and specs. **Initial guides done.**
|
|
162
|
+
4. Move completed designs and working investigations behind `maintainers/` so
|
|
163
|
+
the top-level reader journey remains stable. **Done.**
|
|
164
|
+
|
|
165
|
+
That sequence makes Otto's current shape visible quickly while leaving room for
|
|
166
|
+
new capabilities without turning `docs/` into a second, drifting codebase.
|
data/docs/adr/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Architecture decision records
|
|
2
|
+
|
|
3
|
+
Architecture decision records (ADRs) document durable technical decisions that
|
|
4
|
+
shape Otto's architecture. They preserve the context, decision, and consequences
|
|
5
|
+
without replacing the supported application documentation.
|
|
6
|
+
|
|
7
|
+
## Records
|
|
8
|
+
|
|
9
|
+
- [ADR-001: Enforce route authentication at the handler boundary](adr-001-route-authentication-at-handler-boundary.md)
|
|
10
|
+
- [ADR-002: Use ordered authentication strategy chains and two-layer authorization](adr-002-multi-strategy-authentication-and-authorization.md)
|
|
11
|
+
- [ADR-003: Provide Caddy on-demand TLS as a route-based feature integration](adr-003-caddy-tls-route-based-integration.md)
|
|
12
|
+
- [ADR-004: Separate compatibility support from security maintenance](adr-004-separate-compatibility-from-security-maintenance.md)
|
|
13
|
+
|
|
14
|
+
New records use the next three-digit sequence number and describe their status,
|
|
15
|
+
context, decision, and consequences. Update a record's status when a later ADR
|
|
16
|
+
supersedes it; do not rewrite accepted decisions to reflect later changes.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# ADR-001: Enforce route authentication at the handler boundary
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2025-10-10
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Route declarations carry their `auth=` requirements. Authentication middleware
|
|
9
|
+
runs before routing, so it cannot reliably inspect the matched route or enforce
|
|
10
|
+
that requirement. That left route-level authentication dependent on application
|
|
11
|
+
code instead of Otto's routing contract.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
Otto enforces authentication with `RouteAuthWrapper`, which wraps a route handler
|
|
16
|
+
after Otto has resolved the route and before the application handler runs. The
|
|
17
|
+
wrapper reads the route definition, executes its configured authentication
|
|
18
|
+
strategy or strategy chain, and stores the resulting `StrategyResult` in the
|
|
19
|
+
request environment.
|
|
20
|
+
|
|
21
|
+
Routes without `auth=` receive an anonymous `StrategyResult`. This gives Logic
|
|
22
|
+
classes a consistent context while preserving public-route behavior.
|
|
23
|
+
|
|
24
|
+
## Consequences
|
|
25
|
+
|
|
26
|
+
- `auth=` is enforced at the point where the route definition is available.
|
|
27
|
+
- Authentication remains handler-level architecture, not a global middleware
|
|
28
|
+
concern.
|
|
29
|
+
- Logic classes and handlers can rely on `env['otto.strategy_result']` being set
|
|
30
|
+
when authentication is configured.
|
|
31
|
+
- Authentication strategy registration must finish before the first request,
|
|
32
|
+
when Otto freezes configuration.
|
|
33
|
+
|
|
34
|
+
## Related documentation
|
|
35
|
+
|
|
36
|
+
- [Authentication and authorization guide](../guides/authentication.md)
|
|
37
|
+
- [Route syntax reference](../reference/route-syntax.md)
|
|
38
|
+
- [ADR-002: Multi-strategy authentication and authorization](adr-002-multi-strategy-authentication-and-authorization.md)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# ADR-002: Use ordered authentication strategy chains and two-layer authorization
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2025-11
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
An Otto route may need to accept more than one credential mechanism, such as a
|
|
9
|
+
browser session and an API key. Authentication must support that without making
|
|
10
|
+
invalid explicit credentials silently fall back to anonymous access. It must also
|
|
11
|
+
separate broad route access checks from authorization that depends on a loaded
|
|
12
|
+
resource.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Use a comma-separated `auth=` value for an ordered, OR-based strategy chain:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
GET /api/data Api::Data#show auth=session,api_key response=json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Otto validates all named strategies before it executes the chain. Strategies run
|
|
23
|
+
left to right, and the first authenticated result wins. Plain failures allow the
|
|
24
|
+
next strategy to run. An anonymous result, such as `noauth`, is held as a
|
|
25
|
+
fallback until the chain completes. A terminal `AuthFailure` stops the chain and
|
|
26
|
+
returns an authentication failure; it is reserved for explicitly presented
|
|
27
|
+
credentials that were examined and rejected.
|
|
28
|
+
|
|
29
|
+
Use `role=` for broad route-level authorization after authentication. Multiple
|
|
30
|
+
roles use OR logic. Perform ownership, relationship, and other resource-specific
|
|
31
|
+
authorization in a Logic class's `raise_concerns` method; raise
|
|
32
|
+
`Otto::Security::AuthorizationError` when access is denied.
|
|
33
|
+
|
|
34
|
+
## Consequences
|
|
35
|
+
|
|
36
|
+
- A route can accept multiple credential types without duplicate handlers.
|
|
37
|
+
- Strategy declaration order determines the order of ordinary attempts, but a
|
|
38
|
+
terminal failure always fails closed.
|
|
39
|
+
- Unknown strategy names fail before any configured strategy runs, preventing a
|
|
40
|
+
partially configured route from serving traffic.
|
|
41
|
+
- Missing authentication results in `401`; a valid subject denied by a strategy,
|
|
42
|
+
role check, or resource check results in `403`.
|
|
43
|
+
- Applications should configure inexpensive, common strategies first and mark a
|
|
44
|
+
failure terminal only when explicit credentials were rejected.
|
|
45
|
+
|
|
46
|
+
## Related documentation
|
|
47
|
+
|
|
48
|
+
- [Authentication and authorization guide](../guides/authentication.md)
|
|
49
|
+
- [Route syntax reference](../reference/route-syntax.md)
|
|
50
|
+
- [ADR-001: Route authentication at the handler boundary](adr-001-route-authentication-at-handler-boundary.md)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# ADR-003: Provide Caddy on-demand TLS as a route-based feature integration
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-07
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Caddy's on-demand TLS permission check has a fixed HTTP contract, but each
|
|
9
|
+
application previously had to implement routing, input validation, caller
|
|
10
|
+
restriction, response semantics, and failure handling around its domain-allow
|
|
11
|
+
policy. A manually wired handler and guard makes it possible to expose the
|
|
12
|
+
certificate-issuance endpoint without its required protection.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Provide `Otto::CaddyTLS` as an opt-in, feature-named integration. Calling
|
|
17
|
+
`enable_caddy_tls!` registers the permission route and, by default, its
|
|
18
|
+
path-scoped `LocalhostGuard`; the application supplies only the domain allow or
|
|
19
|
+
deny decision.
|
|
20
|
+
|
|
21
|
+
The guard authorizes a direct loopback socket peer, not a client address derived
|
|
22
|
+
from forwarding headers. It also rejects forwarding headers for the protected
|
|
23
|
+
path. The endpoint therefore remains loopback-only even in cross-host
|
|
24
|
+
deployments: run a small permission app alongside Caddy and let the application
|
|
25
|
+
callback use its existing trusted data channel.
|
|
26
|
+
|
|
27
|
+
The integration fails closed: a missing or invalid `domain` is rejected, a falsey
|
|
28
|
+
or exception-raising permission callback denies the request, and enabling the
|
|
29
|
+
integration without a callback raises an error. The default endpoint is
|
|
30
|
+
`/_caddy/tls-permission`.
|
|
31
|
+
|
|
32
|
+
## Consequences
|
|
33
|
+
|
|
34
|
+
- Caddy TLS setup has one code-side entry point that bundles the route, guard,
|
|
35
|
+
and decision callback.
|
|
36
|
+
- The endpoint cannot be reached successfully through a public reverse-proxy
|
|
37
|
+
path merely because the proxy connects to Otto over loopback.
|
|
38
|
+
- `localhost_only: false` is an explicit opt-out; the deployment must then
|
|
39
|
+
provide network-level isolation.
|
|
40
|
+
- A generic network-services registry is not introduced. Future integrations
|
|
41
|
+
should use a feature-specific namespace and promote only mechanisms shared by
|
|
42
|
+
more than one concrete feature.
|
|
43
|
+
|
|
44
|
+
## Related documentation
|
|
45
|
+
|
|
46
|
+
- [Caddy on-demand TLS guide](../guides/caddy-tls.md)
|
|
47
|
+
- [Caddy TLS example](../../examples/caddy_tls_demo/README.md)
|
|
48
|
+
- [`Otto::CaddyTLS::LocalhostGuard`](../../lib/otto/caddy_tls/localhost_guard.rb)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# ADR-004: Separate compatibility support from security maintenance
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-09-04
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Otto declares Ruby and dependency version ranges so that applications can resolve
|
|
9
|
+
a compatible bundle. Those declarations can be mistaken for security guarantees:
|
|
10
|
+
a Ruby version may remain compatible with Otto after upstream security maintenance
|
|
11
|
+
ends, and a gem version may satisfy Otto's dependency range after a new advisory
|
|
12
|
+
makes that version unsafe.
|
|
13
|
+
|
|
14
|
+
Otto's own lockfile and CI matrix test Otto's development resolution and API
|
|
15
|
+
compatibility. They cannot determine or secure the final dependency graph resolved
|
|
16
|
+
by every application that installs Otto. The current Ruby support matrix,
|
|
17
|
+
dependency constraints, and audit procedures also change over time, so they belong
|
|
18
|
+
in maintained reference documentation rather than in a historical decision record.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
Otto treats compatibility support as distinct from upstream security maintenance.
|
|
23
|
+
Compatibility testing means that Otto is expected to work on a runtime or with a
|
|
24
|
+
dependency version; it does not mean that the runtime or dependency still receives
|
|
25
|
+
security fixes.
|
|
26
|
+
|
|
27
|
+
The version ranges in `otto.gemspec` express API compatibility. Their lower bounds
|
|
28
|
+
are not guaranteed security floors, and installation success does not certify a
|
|
29
|
+
resolved bundle as free of known vulnerabilities.
|
|
30
|
+
|
|
31
|
+
Applications that consume Otto own the security auditing and maintenance of their
|
|
32
|
+
resolved lockfiles. Otto may constrain a known-bad version when warranted, but
|
|
33
|
+
maintainer constraints and CI do not replace application-level auditing.
|
|
34
|
+
|
|
35
|
+
Otto continues to test Ruby 3.2 as a compatibility target despite its upstream end
|
|
36
|
+
of life. This preserves compatibility for users who still need that runtime without
|
|
37
|
+
representing Ruby 3.2 as security-maintained.
|
|
38
|
+
|
|
39
|
+
## Consequences
|
|
40
|
+
|
|
41
|
+
- Otto can retain useful compatibility coverage without implying that it provides
|
|
42
|
+
upstream interpreter or dependency security maintenance.
|
|
43
|
+
- Applications with security-maintenance requirements must select an upstream-
|
|
44
|
+
maintained Ruby, audit their complete resolved bundles, and deploy audited
|
|
45
|
+
lockfiles.
|
|
46
|
+
- Dependency lower bounds remain stable compatibility baselines unless an API or
|
|
47
|
+
security issue requires a targeted constraint change.
|
|
48
|
+
- Ruby 3.2 compatibility failures remain release-relevant while it is a blocking
|
|
49
|
+
target, even though applications should not infer that Ruby 3.2 receives security
|
|
50
|
+
fixes.
|
|
51
|
+
- The current support matrix, compatibility ranges, audit commands, and consumer
|
|
52
|
+
procedures may change in the canonical reference without rewriting this ADR. A
|
|
53
|
+
later architectural decision may supersede this record if the policy changes.
|
|
54
|
+
|
|
55
|
+
## Related documentation
|
|
56
|
+
|
|
57
|
+
- [Runtime and dependency security policy](../reference/runtime-and-dependency-security.md)
|
|
58
|
+
— canonical current matrix, compatibility policy, and consumer audit procedures
|