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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ebb9d40d464e54cb0a25d0014dfa6868905e833c400701739d61463ae56b7265
|
|
4
|
+
data.tar.gz: f111ef655d58256cece33215fe05a7711c5f1bd0e06925c41e58b0e492f6dfb5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1381ef06000d127d7411f50e797dbf14720821873bc8f9b70e1e01a3a232fb7a8f2ebaee7e0ec4ed8a9d61dfc8cf8d67753de21702ef253dd42b4e1736949ea
|
|
7
|
+
data.tar.gz: 7105c5561be5e5f3cfe771b41cc1d576c0ede262cecc92347cc518cbdd0c57deaefca50a923a535661a47ac6a977a1171a6dab9cb3f9ff59ca993c5a1a2d326a
|
data/.github/dependabot.yml
CHANGED
|
@@ -9,6 +9,11 @@ updates:
|
|
|
9
9
|
directory: "/" # Location of package manifests
|
|
10
10
|
schedule:
|
|
11
11
|
interval: "weekly"
|
|
12
|
+
# All action bumps arrive as one PR instead of one per action.
|
|
13
|
+
groups:
|
|
14
|
+
github-actions:
|
|
15
|
+
patterns:
|
|
16
|
+
- "*"
|
|
12
17
|
- package-ecosystem: "bundler"
|
|
13
18
|
directory: "/" # Location of package manifests
|
|
14
19
|
schedule:
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -28,15 +28,18 @@ jobs:
|
|
|
28
28
|
fail-fast: false
|
|
29
29
|
matrix:
|
|
30
30
|
# Each Ruby runs twice: once against the committed Gemfile.lock
|
|
31
|
-
# (
|
|
32
|
-
#
|
|
33
|
-
# gemspec
|
|
34
|
-
#
|
|
35
|
-
# releases that satisfy `~> X.Y` but break Otto at load time -
|
|
31
|
+
# (the reproducible maintainer snapshot) and once with the lockfile
|
|
32
|
+
# removed so Bundler resolves currently available versions inside the
|
|
33
|
+
# gemspec constraints. Neither resolution is a patched security floor;
|
|
34
|
+
# consumers must audit their own lockfiles. The unlocked cells catch
|
|
35
|
+
# upstream releases that satisfy `~> X.Y` but break Otto at load time -
|
|
36
36
|
# e.g. facets 3.2.0 shipping a self-referential
|
|
37
37
|
# `require_relative 'file/write.rb'` against a file deleted in
|
|
38
38
|
# the same release, the reason 2.0.2 exists.
|
|
39
39
|
include:
|
|
40
|
+
# Ruby 3.2 is EOL upstream but remains a blocking compatibility
|
|
41
|
+
# target. Ruby 3.5 and 4.0 remain provisional, non-blocking targets.
|
|
42
|
+
# See docs/reference/runtime-and-dependency-security.md.
|
|
40
43
|
- ruby: "3.2"
|
|
41
44
|
experimental: false
|
|
42
45
|
lockfile: "locked"
|
|
@@ -69,9 +72,9 @@ jobs:
|
|
|
69
72
|
lockfile: "unlocked"
|
|
70
73
|
|
|
71
74
|
steps:
|
|
72
|
-
- uses: actions/checkout@v7.0.1
|
|
75
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
73
76
|
- name: Set up Ruby
|
|
74
|
-
uses: ruby/setup-ruby@v1
|
|
77
|
+
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
75
78
|
continue-on-error: ${{ matrix.experimental }}
|
|
76
79
|
with:
|
|
77
80
|
ruby-version: ${{ matrix.ruby }}
|
|
@@ -80,7 +83,7 @@ jobs:
|
|
|
80
83
|
bundler-cache: ${{ !matrix.experimental && matrix.lockfile == 'locked' }}
|
|
81
84
|
|
|
82
85
|
- name: Setup tmate session
|
|
83
|
-
uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3
|
|
86
|
+
uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3.24
|
|
84
87
|
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
|
|
85
88
|
with:
|
|
86
89
|
detached: true
|
|
@@ -30,13 +30,20 @@ jobs:
|
|
|
30
30
|
# CLAUDE_CODE_OAUTH_TOKEN is empty and the action would fail.
|
|
31
31
|
# 3. The event is a freshly opened PR, a 'claude-review' label add, or a push
|
|
32
32
|
# to a PR that already carries the 'claude-review' label.
|
|
33
|
+
# A manual workflow_dispatch run bypasses 2 and 3 (it can only be started by a
|
|
34
|
+
# user with write access, on a branch in this repo) but still skips bots.
|
|
33
35
|
if: ${{
|
|
34
36
|
!endsWith(github.actor, '[bot]') &&
|
|
35
|
-
!github.event.pull_request.head.repo.fork &&
|
|
36
37
|
(
|
|
37
|
-
|
|
38
|
-
(
|
|
39
|
-
|
|
38
|
+
github.event_name == 'workflow_dispatch' ||
|
|
39
|
+
(
|
|
40
|
+
!github.event.pull_request.head.repo.fork &&
|
|
41
|
+
(
|
|
42
|
+
(github.event.action == 'opened') ||
|
|
43
|
+
(github.event.action == 'labeled' && github.event.label.name == 'claude-review') ||
|
|
44
|
+
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'claude-review'))
|
|
45
|
+
)
|
|
46
|
+
)
|
|
40
47
|
)
|
|
41
48
|
}}
|
|
42
49
|
|
|
@@ -48,13 +55,13 @@ jobs:
|
|
|
48
55
|
|
|
49
56
|
steps:
|
|
50
57
|
- name: Checkout repository
|
|
51
|
-
uses: actions/checkout@v7.0.1
|
|
58
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
52
59
|
with:
|
|
53
60
|
fetch-depth: 1
|
|
54
61
|
|
|
55
62
|
- name: Run Claude Code Review
|
|
56
63
|
id: claude-review
|
|
57
|
-
uses: anthropics/claude-code-action@
|
|
64
|
+
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1.0.216
|
|
58
65
|
with:
|
|
59
66
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
60
67
|
|
|
@@ -62,11 +69,26 @@ jobs:
|
|
|
62
69
|
# runs) -> the CLAUDE_MODEL repo variable -> this built-in default.
|
|
63
70
|
# Pinning a current id avoids the action's frozen default, which 404s
|
|
64
71
|
# ("model: claude-sonnet-4-20250514"). Fall back to Sonnet on overload.
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
# v1 removed the model/fallback_model inputs; both are now CLI flags
|
|
73
|
+
# passed through claude_args.
|
|
74
|
+
claude_args: |
|
|
75
|
+
--model ${{ inputs.model || vars.CLAUDE_MODEL || 'claude-opus-4-6' }}
|
|
76
|
+
--fallback-model ${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}
|
|
77
|
+
|
|
78
|
+
# Prompt for automated review (no @claude mention needed).
|
|
79
|
+
# v1 renamed direct_prompt -> prompt. track_progress restores the
|
|
80
|
+
# tracking/sticky comment that v0 automation mode posted by default; it
|
|
81
|
+
# only applies to pull_request/issue events, so a workflow_dispatch run
|
|
82
|
+
# has no PR to comment on and reports to the job log instead.
|
|
83
|
+
track_progress: true
|
|
84
|
+
prompt: |
|
|
85
|
+
REPO: ${{ github.repository }}
|
|
86
|
+
PR NUMBER: ${{ github.event.pull_request.number || 'none (manual run)' }}
|
|
87
|
+
|
|
88
|
+
If no PR number is given, use `gh` to find an open pull request whose
|
|
89
|
+
head is ${{ github.ref_name }}; if there is none, say so and stop.
|
|
90
|
+
(The checkout is shallow, so compare via the API, not local git.)
|
|
67
91
|
|
|
68
|
-
# Direct prompt for automated review (no @claude mention needed)
|
|
69
|
-
direct_prompt: |
|
|
70
92
|
Please review this pull request and provide feedback on:
|
|
71
93
|
- Code quality and best practices
|
|
72
94
|
- Potential bugs or issues
|
|
@@ -80,9 +102,12 @@ jobs:
|
|
|
80
102
|
use_sticky_comment: true
|
|
81
103
|
|
|
82
104
|
- name: Remove claude-review label
|
|
83
|
-
# Remove label whether success or failure - prevents getting stuck
|
|
84
|
-
|
|
85
|
-
|
|
105
|
+
# Remove label whether success or failure - prevents getting stuck.
|
|
106
|
+
# Only for pull_request events: a workflow_dispatch payload has no
|
|
107
|
+
# issue number, so context.issue.number would be undefined and the
|
|
108
|
+
# removeLabel call would fail (silently, below) with the label intact.
|
|
109
|
+
if: always() && github.event_name == 'pull_request' && github.event.action != 'opened'
|
|
110
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
86
111
|
with:
|
|
87
112
|
script: |
|
|
88
113
|
try {
|
|
@@ -26,13 +26,13 @@ jobs:
|
|
|
26
26
|
actions: read # Required for Claude to read CI results on PRs
|
|
27
27
|
steps:
|
|
28
28
|
- name: Checkout repository
|
|
29
|
-
uses: actions/checkout@v7.0.1
|
|
29
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
30
30
|
with:
|
|
31
31
|
fetch-depth: 1
|
|
32
32
|
|
|
33
33
|
- name: Run Claude Code
|
|
34
34
|
id: claude
|
|
35
|
-
uses: anthropics/claude-code-action@
|
|
35
|
+
uses: anthropics/claude-code-action@d75b94d5ad426cb8546e6628b6f5f19b84e5cce1 # v1.0.216
|
|
36
36
|
with:
|
|
37
37
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
38
|
|
|
@@ -40,8 +40,11 @@ jobs:
|
|
|
40
40
|
# editing this file. Pinning a current id avoids the action's frozen
|
|
41
41
|
# default, which 404s ("model: claude-sonnet-4-20250514"). Fall back to
|
|
42
42
|
# Sonnet if the primary is unavailable or overloaded.
|
|
43
|
-
model
|
|
44
|
-
|
|
43
|
+
# v1 removed the model/fallback_model inputs; both are now CLI flags
|
|
44
|
+
# passed through claude_args.
|
|
45
|
+
claude_args: |
|
|
46
|
+
--model ${{ vars.CLAUDE_MODEL || 'claude-opus-4-6' }}
|
|
47
|
+
--fallback-model ${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}
|
|
45
48
|
|
|
46
49
|
# This is an optional setting that allows Claude to read CI results on PRs
|
|
47
50
|
additional_permissions: |
|
|
@@ -50,7 +53,6 @@ jobs:
|
|
|
50
53
|
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
|
51
54
|
# prompt: 'Update the pull request description to include a summary of changes.'
|
|
52
55
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line
|
|
56
|
-
# claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
|
|
56
|
+
# More claude_args options (tool allowlists, --max-turns, --mcp-config):
|
|
57
|
+
# https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
58
|
+
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line
|
|
@@ -21,10 +21,10 @@ jobs:
|
|
|
21
21
|
|
|
22
22
|
steps:
|
|
23
23
|
- name: Checkout code
|
|
24
|
-
uses: actions/checkout@v7.0.1
|
|
24
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
25
|
|
|
26
26
|
- name: Set up Ruby
|
|
27
|
-
uses: ruby/setup-ruby@v1
|
|
27
|
+
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
28
28
|
with:
|
|
29
29
|
ruby-version: 3.4
|
|
30
30
|
|
|
@@ -71,7 +71,7 @@ jobs:
|
|
|
71
71
|
continue-on-error: true
|
|
72
72
|
|
|
73
73
|
- name: Upload Reek report as artifact
|
|
74
|
-
uses: actions/upload-artifact@v7
|
|
74
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
75
75
|
if: always()
|
|
76
76
|
with:
|
|
77
77
|
name: reek-report
|
|
@@ -88,10 +88,10 @@ jobs:
|
|
|
88
88
|
|
|
89
89
|
steps:
|
|
90
90
|
- name: Checkout code
|
|
91
|
-
uses: actions/checkout@v7.0.1
|
|
91
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
92
92
|
|
|
93
93
|
- name: Set up Ruby
|
|
94
|
-
uses: ruby/setup-ruby@v1
|
|
94
|
+
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
95
95
|
with:
|
|
96
96
|
ruby-version: 3.4
|
|
97
97
|
bundler-cache: true
|
|
@@ -139,7 +139,7 @@ jobs:
|
|
|
139
139
|
persist-credentials: false
|
|
140
140
|
|
|
141
141
|
- name: Set up Ruby
|
|
142
|
-
uses: ruby/setup-ruby@
|
|
142
|
+
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
143
143
|
with:
|
|
144
144
|
bundler-cache: true
|
|
145
145
|
# Pinned to 3.3 (oldest non-experimental Ruby in the CI matrix).
|
|
@@ -169,4 +169,4 @@ jobs:
|
|
|
169
169
|
echo "Releasing otto ${gem_version} from tag ${RELEASE_TAG}"
|
|
170
170
|
|
|
171
171
|
- name: Build and push gem to RubyGems
|
|
172
|
-
uses: rubygems/release-gem@
|
|
172
|
+
uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
|
|
@@ -44,15 +44,15 @@ jobs:
|
|
|
44
44
|
|
|
45
45
|
steps:
|
|
46
46
|
- name: Checkout code
|
|
47
|
-
uses: actions/checkout@v7.0.1
|
|
47
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
48
48
|
|
|
49
|
-
- uses: ruby/setup-ruby@v1
|
|
49
|
+
- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
50
50
|
with:
|
|
51
51
|
ruby-version: ${{ matrix.ruby }}
|
|
52
52
|
bundler-cache: true
|
|
53
53
|
|
|
54
54
|
- name: Setup tmate session
|
|
55
|
-
uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3
|
|
55
|
+
uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3.24
|
|
56
56
|
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
|
|
57
57
|
with:
|
|
58
58
|
detached: true
|
|
@@ -42,12 +42,12 @@ jobs:
|
|
|
42
42
|
|
|
43
43
|
steps:
|
|
44
44
|
- name: Checkout repository
|
|
45
|
-
uses: actions/checkout@v7.0.1
|
|
45
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
46
46
|
with:
|
|
47
47
|
fetch-depth: 0
|
|
48
48
|
|
|
49
49
|
- name: Set up Ruby environment
|
|
50
|
-
uses: ruby/setup-ruby@v1
|
|
50
|
+
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
51
51
|
with:
|
|
52
52
|
ruby-version: '3.4'
|
|
53
53
|
bundler-cache: true
|
|
@@ -82,10 +82,10 @@ jobs:
|
|
|
82
82
|
echo "::endgroup::"
|
|
83
83
|
|
|
84
84
|
- name: Setup GitHub Pages configuration
|
|
85
|
-
uses: actions/configure-pages@v6
|
|
85
|
+
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
|
86
86
|
|
|
87
87
|
- name: Upload documentation artifact
|
|
88
|
-
uses: actions/upload-pages-artifact@v5
|
|
88
|
+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
89
89
|
with:
|
|
90
90
|
path: './doc'
|
|
91
91
|
|
|
@@ -102,7 +102,7 @@ jobs:
|
|
|
102
102
|
steps:
|
|
103
103
|
- name: Deploy to GitHub Pages
|
|
104
104
|
id: deployment
|
|
105
|
-
uses: actions/deploy-pages@v5
|
|
105
|
+
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
|
|
106
106
|
|
|
107
107
|
notify-completion:
|
|
108
108
|
timeout-minutes: 5
|
data/.gitignore
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
.DS_Store
|
|
2
2
|
.bundle
|
|
3
|
-
.byebug*
|
|
4
3
|
.history
|
|
5
4
|
.devcontainer
|
|
6
5
|
.vscode
|
|
7
6
|
*.env
|
|
8
7
|
*.log
|
|
9
|
-
*.md
|
|
10
8
|
*.txt
|
|
11
9
|
.*.json
|
|
12
10
|
!LICENSE.txt
|
|
13
11
|
!spec/fixtures/*.txt
|
|
14
|
-
!examples/**/*.md
|
|
15
|
-
!README.md
|
|
16
|
-
!CLAUDE.md
|
|
17
12
|
.ruby-version
|
|
18
13
|
appendonlydir
|
|
19
14
|
data/
|
|
@@ -27,3 +22,4 @@ vendor
|
|
|
27
22
|
.mcp.json
|
|
28
23
|
.claude
|
|
29
24
|
.serena
|
|
25
|
+
.yardoc
|
data/.rubocop_todo.yml
CHANGED
|
@@ -296,15 +296,11 @@ Lint/DuplicateMethods:
|
|
|
296
296
|
Exclude:
|
|
297
297
|
- 'lib/otto/privacy/geo_resolver.rb'
|
|
298
298
|
|
|
299
|
-
# Offense count:
|
|
299
|
+
# Offense count: 1
|
|
300
300
|
# Configuration parameters: AllowedParentClasses.
|
|
301
301
|
Lint/MissingSuper:
|
|
302
302
|
Exclude:
|
|
303
303
|
- 'lib/otto/mcp/rate_limiting.rb'
|
|
304
|
-
- 'lib/otto/security/authentication/strategies/api_key_strategy.rb'
|
|
305
|
-
- 'lib/otto/security/authentication/strategies/permission_strategy.rb'
|
|
306
|
-
- 'lib/otto/security/authentication/strategies/role_strategy.rb'
|
|
307
|
-
- 'lib/otto/security/authentication/strategies/session_strategy.rb'
|
|
308
304
|
|
|
309
305
|
# Offense count: 12
|
|
310
306
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
@@ -601,6 +597,9 @@ RSpec/DescribeMethod:
|
|
|
601
597
|
- 'spec/otto/initialization_spec.rb'
|
|
602
598
|
- 'spec/otto/locale_config_spec.rb'
|
|
603
599
|
- 'spec/otto/mcp/rate_limiting_spec.rb'
|
|
600
|
+
- 'spec/otto/mcp/protocol_spec.rb'
|
|
601
|
+
- 'spec/otto/mcp/registry_spec.rb'
|
|
602
|
+
- 'spec/otto/mcp/route_parser_spec.rb'
|
|
604
603
|
- 'spec/otto/mcp_route_parsing_spec.rb'
|
|
605
604
|
- 'spec/otto/rate_limiting_spec.rb'
|
|
606
605
|
- 'spec/otto/response_integration_spec.rb'
|
|
@@ -863,6 +862,9 @@ RSpec/SpecFilePathFormat:
|
|
|
863
862
|
- 'spec/otto/file_safety_spec.rb'
|
|
864
863
|
- 'spec/otto/locale_config_spec.rb'
|
|
865
864
|
- 'spec/otto/mcp/rate_limiting_spec.rb'
|
|
865
|
+
- 'spec/otto/mcp/protocol_spec.rb'
|
|
866
|
+
- 'spec/otto/mcp/registry_spec.rb'
|
|
867
|
+
- 'spec/otto/mcp/route_parser_spec.rb'
|
|
866
868
|
- 'spec/otto/mcp/token_auth_spec.rb'
|
|
867
869
|
- 'spec/otto/mcp_route_parsing_spec.rb'
|
|
868
870
|
- 'spec/otto/rate_limiting_spec.rb'
|
data/AGENTS.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
This file provides essential guidance to AI agents when working with Otto.
|
|
4
4
|
|
|
5
|
+
## Project claims and source authority
|
|
6
|
+
|
|
7
|
+
### Attribution
|
|
8
|
+
|
|
9
|
+
Do not infer project terminology or guarantees from repetition. Before attributing a claim to the project, locate an authoritative primary source and provide its exact wording. Treat delivery notes, commit messages, agent output, and documents created or modified during the current task as leads, not evidence. If the wording is absent, call it an interpretation or proposal. Never place paraphrases in quotation marks.
|
|
10
|
+
|
|
11
|
+
### Authoritative sources
|
|
12
|
+
|
|
13
|
+
Authoritative sources must be identified explicitly; repository presence alone does not confer authority. Accepted specifications and ADRs may establish project claims only within their stated scope. Delivery notes, commit messages, issue discussions, summaries, and agent-authored text are non-authoritative unless an authoritative source incorporates them explicitly.
|
|
14
|
+
|
|
15
|
+
### Normative claims
|
|
16
|
+
|
|
17
|
+
For normative claims concerning security, privacy, compatibility, persistence, or data loss:
|
|
18
|
+
|
|
19
|
+
1. Cite the authoritative source and its exact wording.
|
|
20
|
+
2. Distinguish quotations, paraphrases, interpretations, and proposals.
|
|
21
|
+
3. Do not use material created or modified during the current task to validate that task’s claims.
|
|
22
|
+
4. If no authoritative wording exists, report the claim as unsupported.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
5
26
|
## Error Handler Registration
|
|
6
27
|
|
|
7
28
|
Register handlers for expected business logic errors to avoid logging them as 500 errors:
|
|
@@ -56,7 +77,7 @@ Authentication is handled by `RouteAuthWrapper` at the handler level, NOT by mid
|
|
|
56
77
|
|
|
57
78
|
```ruby
|
|
58
79
|
otto.add_auth_strategy('session', SessionStrategy.new)
|
|
59
|
-
otto.add_auth_strategy('apikey', APIKeyStrategy.new)
|
|
80
|
+
otto.add_auth_strategy('apikey', APIKeyStrategy.new(api_keys: ENV.fetch('API_KEYS').split(',')))
|
|
60
81
|
```
|
|
61
82
|
|
|
62
83
|
- Strategy names must be unique
|
data/CHANGELOG.rst
CHANGED
|
@@ -7,6 +7,171 @@ 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.10.0:
|
|
11
|
+
|
|
12
|
+
2.10.0 — 2026-09-04
|
|
13
|
+
===================
|
|
14
|
+
|
|
15
|
+
Added
|
|
16
|
+
-----
|
|
17
|
+
|
|
18
|
+
- ``APIKeyStrategy`` now accepts a block or ``resolver:`` callable instead of a
|
|
19
|
+
static ``api_keys:`` list, enabling database-, repository-, or cache-backed
|
|
20
|
+
key lookup. The resolved account becomes the authenticated user.
|
|
21
|
+
|
|
22
|
+
- ``APIKeyStrategy.digest(key)`` returns a full SHA-256 hex digest for stores
|
|
23
|
+
that look up generated API keys by digest. See
|
|
24
|
+
``docs/guides/authentication.md`` for the resolver contract and credential
|
|
25
|
+
storage guidance.
|
|
26
|
+
|
|
27
|
+
- Directly exposed applications can now set ``trusted_proxies: :none`` or call
|
|
28
|
+
``trust_no_proxies!`` to distrust every peer. Otto then ignores forwarded
|
|
29
|
+
client IPs and strips forwarded host, scheme, and port metadata. Leaving proxy
|
|
30
|
+
trust unconfigured continues to preserve forwarded metadata. Applications
|
|
31
|
+
behind a reverse proxy, including one on loopback, must explicitly trust it.
|
|
32
|
+
The sentinel is only valid as the whole option; a list containing it, such
|
|
33
|
+
as ``['none']``, is rejected at configuration time. See
|
|
34
|
+
``docs/guides/forwarded-authority.md`` for configuration guidance. (#259)
|
|
35
|
+
|
|
36
|
+
Changed
|
|
37
|
+
-------
|
|
38
|
+
|
|
39
|
+
- MCP options now accept String or Symbol keys consistently and fail at boot
|
|
40
|
+
for unknown, conflicting, or invalid values. Constructor gating options must
|
|
41
|
+
be exactly ``true`` or ``false``; nil or blank token values are rejected.
|
|
42
|
+
Before upgrading, check option names against the `MCP guide
|
|
43
|
+
<docs/guides/mcp.md>`__. (#258)
|
|
44
|
+
|
|
45
|
+
- ``enable_mcp!`` now rejects constructor-only gating options and repeated
|
|
46
|
+
enablement. Pass all MCP settings in one ``Otto.new`` or ``enable_mcp!`` call.
|
|
47
|
+
(#258)
|
|
48
|
+
|
|
49
|
+
- MCP ``Rack::Attack`` throttle names are now endpoint-qualified, such as
|
|
50
|
+
``mcp_requests:/_mcp`` and ``mcp_tool_calls:/_mcp``. Update integrations that
|
|
51
|
+
inspect throttle names directly. (#258)
|
|
52
|
+
|
|
53
|
+
- Otto applications in one process that resolve proxied requests must now use
|
|
54
|
+
the same forwarded-header family; incompatible configurations fail during
|
|
55
|
+
configuration. CIDR-based proxy trust supports only ``X-Forwarded-*`` headers; use
|
|
56
|
+
depth-based trust for ``Forwarded`` or both families. See
|
|
57
|
+
``docs/guides/forwarded-authority.md`` for configuration guidance. (#252)
|
|
58
|
+
|
|
59
|
+
- Literal routes now consistently take precedence over static files at the same
|
|
60
|
+
path, regardless of which static files were requested earlier. Static files
|
|
61
|
+
continue to take precedence over dynamic routes. (#260)
|
|
62
|
+
|
|
63
|
+
- MaxMind-backed database paths now require ``maxmind-db ~> 1.2``. Before
|
|
64
|
+
upgrading, declare that range or provide a compatible reader object. See the
|
|
65
|
+
`geo-country <docs/guides/geo-country.md>`__ and
|
|
66
|
+
`enrichment <docs/guides/enrichment.md>`__ guides. (#255)
|
|
67
|
+
|
|
68
|
+
Removed
|
|
69
|
+
-------
|
|
70
|
+
|
|
71
|
+
- Removed ``add_static_path`` and the ``routes_static`` cache interface. Static
|
|
72
|
+
files are discovered directly from the configured ``public`` directory and
|
|
73
|
+
no longer need registration. (#260)
|
|
74
|
+
|
|
75
|
+
- Removed the unused ``rack-parser`` and ``rexml`` runtime dependencies.
|
|
76
|
+
Applications using either gem directly must now declare it themselves.
|
|
77
|
+
(#255)
|
|
78
|
+
|
|
79
|
+
Fixed
|
|
80
|
+
-----
|
|
81
|
+
|
|
82
|
+
- MCP middleware now executes rate limiting, authentication, and schema
|
|
83
|
+
validation in that order. Configured endpoints with a trailing slash are also
|
|
84
|
+
routable. (#258)
|
|
85
|
+
|
|
86
|
+
- Repeated MCP rate-limit configuration no longer produces duplicate throttle
|
|
87
|
+
log entries. (#258)
|
|
88
|
+
|
|
89
|
+
- MCP JSON-RPC errors now consistently use HTTP 400 for protocol errors, 404
|
|
90
|
+
for unknown resources or tools, and 500 for handler or server failures.
|
|
91
|
+
Unknown tools no longer return 500, failing resource handlers no longer
|
|
92
|
+
appear missing, and handler exception details are logged instead of returned
|
|
93
|
+
to clients. (#257)
|
|
94
|
+
|
|
95
|
+
- ``StrategyResult#has_role?`` and ``#has_permission?`` now derive their answer
|
|
96
|
+
from ``#roles`` and ``#permissions``, so the predicates agree with the
|
|
97
|
+
accessors for object-backed users exposing ``#roles`` and for ``Set`` or
|
|
98
|
+
other non-Array collections. A user model defining its own ``#has_role?`` or
|
|
99
|
+
``#has_permission?`` is still consulted first.
|
|
100
|
+
|
|
101
|
+
- MCP rate-limited requests now emit one ``rack.attack`` log entry instead of
|
|
102
|
+
two. (#255)
|
|
103
|
+
|
|
104
|
+
Security
|
|
105
|
+
--------
|
|
106
|
+
|
|
107
|
+
- ``APIKeyStrategy`` now requires exactly one key source and rejects empty
|
|
108
|
+
static key lists at construction. Invalid presented credentials terminate a
|
|
109
|
+
multi-strategy authentication chain. Static keys are compared in constant
|
|
110
|
+
time as fixed-width SHA-256 digests, so configured key lengths are not
|
|
111
|
+
observable through timing. Blank credentials, empty or whitespace-only, are
|
|
112
|
+
rejected before any key source is consulted. (#256)
|
|
113
|
+
|
|
114
|
+
- Successful authentication results no longer expose the raw API key in
|
|
115
|
+
strategy-generated fields. Static-list callers should replace
|
|
116
|
+
``user[:api_key]`` with ``user[:api_key_fingerprint]``. (#256)
|
|
117
|
+
|
|
118
|
+
- ``APIKeyStrategy`` now reads only the configured header by default. Applications
|
|
119
|
+
that still accept query or form parameters must opt in with ``param_name:``;
|
|
120
|
+
see ``docs/guides/authentication.md`` for configuration guidance. (#256)
|
|
121
|
+
|
|
122
|
+
- Fixed MCP bearer-token authentication being silently omitted when configured
|
|
123
|
+
through ``Otto.new`` or with String-keyed options. Deployments exposing MCP
|
|
124
|
+
beyond a trusted local environment should upgrade and verify their
|
|
125
|
+
``auth_tokens`` configuration. Intentionally open endpoints now warn unless
|
|
126
|
+
acknowledged with ``allow_unauthenticated: true``. See the `MCP guide
|
|
127
|
+
<docs/guides/mcp.md>`__. (#258)
|
|
128
|
+
|
|
129
|
+
- MCP authentication now fails closed when its authenticator is unavailable.
|
|
130
|
+
Authentication, validation, and rate limiting also apply only to the exact
|
|
131
|
+
routed MCP endpoint. (#258)
|
|
132
|
+
|
|
133
|
+
- MCP rate limiting now honors configured limits and protects custom endpoints,
|
|
134
|
+
Rack-mounted applications, and distinct MCP endpoints in the same process.
|
|
135
|
+
Mount ``Rack::Attack`` inside the same ``map`` block as Otto, and use distinct
|
|
136
|
+
endpoint paths when applications require isolated counters. (#258)
|
|
137
|
+
|
|
138
|
+
- Static-file serving now rejects symlinks that resolve outside the configured
|
|
139
|
+
public directory, preventing files outside that root from being served on
|
|
140
|
+
first access or after another file has populated the directory cache.
|
|
141
|
+
Symlinks that resolve within the public directory, including a symlinked
|
|
142
|
+
public root, remain supported. (#257)
|
|
143
|
+
|
|
144
|
+
- When proxy trust is configured, forwarded host, scheme, and port metadata
|
|
145
|
+
from untrusted peers is now stripped before it can affect Rack's request
|
|
146
|
+
authority. Requests from trusted peers retain this metadata; see
|
|
147
|
+
``docs/guides/forwarded-authority.md`` for deployment guidance. (#252)
|
|
148
|
+
|
|
149
|
+
- ``IPPrivacyMiddleware`` enforces its own proxy trust posture even when an
|
|
150
|
+
outer instance already resolved ``otto.client_ip``. ``trusted_proxies: :none``
|
|
151
|
+
always strips forwarded authority; a CIDR configuration that can no longer
|
|
152
|
+
match the connecting peer treats it as untrusted and logs a warning. (#259)
|
|
153
|
+
|
|
154
|
+
- ``env['otto.peer_relayed']`` records whether a request carried any relay
|
|
155
|
+
marker header, evaluated before forwarded carriers may be deleted, so
|
|
156
|
+
``Otto::CaddyTLS::LocalhostGuard`` still refuses a relayed loopback call once
|
|
157
|
+
the carriers are stripped. The relay markers cover every carrier the scrub
|
|
158
|
+
deletes, including ``X-Forwarded-Host`` and the other authority headers, not
|
|
159
|
+
only the client-IP carriers. (#259)
|
|
160
|
+
|
|
161
|
+
- MCP now stops configuration with ``Otto::OptionalDependencyError`` rather
|
|
162
|
+
than starting without enabled schema validation or rate limiting. Before
|
|
163
|
+
upgrading, add ``json_schemer ~> 2.0`` and ``rack-attack ~> 6.7`` for the
|
|
164
|
+
default protections, or explicitly set ``enable_validation: false`` or
|
|
165
|
+
``enable_rate_limiting: false`` only when that protection is not required.
|
|
166
|
+
See the `MCP guide <docs/guides/mcp.md>`__. (#255)
|
|
167
|
+
|
|
168
|
+
Documentation
|
|
169
|
+
-------------
|
|
170
|
+
|
|
171
|
+
- Added the `MCP guide <docs/guides/mcp.md>`__ covering secure enablement,
|
|
172
|
+
supported options, authentication, validation, rate limiting, mounted
|
|
173
|
+
applications, and error behavior. (#258)
|
|
174
|
+
|
|
10
175
|
.. _changelog-2.9.0:
|
|
11
176
|
|
|
12
177
|
2.9.0 — 2026-08-18
|
|
@@ -790,6 +955,44 @@ Added
|
|
|
790
955
|
2.0.0 — 2026-03-14
|
|
791
956
|
==================
|
|
792
957
|
|
|
958
|
+
This is the stable release of Otto v2, the culmination of 10 pre-releases
|
|
959
|
+
since September 2025.
|
|
960
|
+
|
|
961
|
+
Highlights
|
|
962
|
+
----------
|
|
963
|
+
|
|
964
|
+
- **Modular architecture**: the core ``Otto`` class is now a thin composition
|
|
965
|
+
of focused modules (Router, FileSafety, Configuration, ErrorHandler,
|
|
966
|
+
UriGenerator).
|
|
967
|
+
- **Security by default**: IP masking, user agent anonymization, CSRF
|
|
968
|
+
protection, input validation, and backtrace sanitization.
|
|
969
|
+
- **Privacy by default**: public IP masking, country-level geo-location only
|
|
970
|
+
(no external APIs), daily-rotating IP hashes for analytics.
|
|
971
|
+
- **Handler-level authentication**: authentication moved from middleware to
|
|
972
|
+
``RouteAuthWrapper``, so it runs after routing.
|
|
973
|
+
- **Configuration freezing**: configuration is frozen after the first request
|
|
974
|
+
to prevent runtime security bypasses.
|
|
975
|
+
- **MCP support**: JSON-RPC 2.0 endpoints for CLI automation and integrations.
|
|
976
|
+
- **Base error classes**: ``NotFoundError``, ``BadRequestError``,
|
|
977
|
+
``ForbiddenError`` and friends, with automatic HTTP status codes.
|
|
978
|
+
- **Request/response helpers**: extensible ``Otto::Request`` and
|
|
979
|
+
``Otto::Response`` with application-specific helper registration.
|
|
980
|
+
|
|
981
|
+
Breaking changes
|
|
982
|
+
----------------
|
|
983
|
+
|
|
984
|
+
Individual breaking changes are documented in the pre-release entries below.
|
|
985
|
+
The migrations most applications need:
|
|
986
|
+
|
|
987
|
+
- Logic class constructor: ``initialize(session, user, params, locale)`` →
|
|
988
|
+
``initialize(context, params, locale)``
|
|
989
|
+
- Middleware stack: ``otto.middleware_stack <<`` → ``otto.use()``
|
|
990
|
+
- Request callbacks: ``Otto.on_request_complete`` → ``otto.on_request_complete``
|
|
991
|
+
(instance method)
|
|
992
|
+
|
|
993
|
+
See `docs/migrating/v2.0.0.md <docs/migrating/v2.0.0.md>`__ for the full
|
|
994
|
+
upgrade guide.
|
|
995
|
+
|
|
793
996
|
Added
|
|
794
997
|
-----
|
|
795
998
|
|
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
|