otto 2.3.1 → 2.4.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 +1 -1
- data/.github/workflows/ci.yml +7 -1
- data/.github/workflows/claude-code-review.yml +32 -9
- data/.github/workflows/claude.yml +7 -5
- data/.github/workflows/code-smells.yml +2 -2
- data/.github/workflows/release-gem.yml +12 -2
- data/.github/workflows/ruby-lint.yml +66 -0
- data/.github/workflows/yardoc.yml +117 -0
- data/.yardopts +15 -0
- data/CHANGELOG.rst +59 -0
- data/Gemfile +4 -2
- data/Gemfile.lock +23 -17
- data/README.md +96 -0
- data/docs/.gitignore +1 -0
- data/docs/reverse-proxy-network-services.md +358 -0
- data/examples/caddy_tls_demo/README.md +100 -0
- data/examples/caddy_tls_demo/app.rb +41 -0
- data/examples/caddy_tls_demo/config.ru +31 -0
- data/examples/caddy_tls_demo/routes +9 -0
- data/examples/caddy_tls_demo/standalone.ru +38 -0
- data/lib/otto/caddy_tls/core.rb +74 -0
- data/lib/otto/caddy_tls/localhost_guard.rb +158 -0
- data/lib/otto/caddy_tls/server.rb +149 -0
- data/lib/otto/caddy_tls.rb +7 -0
- data/lib/otto/core/middleware_management.rb +7 -7
- data/lib/otto/core/middleware_stack.rb +39 -5
- data/lib/otto/core/router.rb +4 -8
- data/lib/otto/security/config.rb +227 -2
- data/lib/otto/security/configurator.rb +38 -0
- data/lib/otto/security/core.rb +62 -0
- data/lib/otto/security/csp/parser.rb +120 -0
- data/lib/otto/security/csp/report.rb +147 -0
- data/lib/otto/security/csp/report_middleware.rb +120 -0
- data/lib/otto/security/csp.rb +19 -0
- data/lib/otto/security/middleware/ip_privacy_middleware.rb +72 -7
- data/lib/otto/security.rb +1 -0
- data/lib/otto/utils.rb +36 -0
- data/lib/otto/version.rb +1 -1
- data/lib/otto.rb +26 -3
- metadata +23 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e50cca678689db3e2447e76a8a1b12f3b709305d4f73908cb06dfe6883adc596
|
|
4
|
+
data.tar.gz: a6093388b74ffbd098c222a026c1f1cf439c8039c75a0d55b663a0874669c6ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ea5a8acda9a29c09d3a16a050050c2e9b9a1517c7f63e24bb7bc910801a7a07874fff1215550a740cef2c9c3698bc77378083b33f47794a7668b77feff9e6e67
|
|
7
|
+
data.tar.gz: 62cfbe824d5d0d9e159424a6d930217f05b8b01de7142108a6a80953207cca9037a3e63d6ae5c16585dda3ff7b8a9b2d070b593f748563c0a5a20ed852add37f
|
data/.github/dependabot.yml
CHANGED
data/.github/workflows/ci.yml
CHANGED
|
@@ -37,6 +37,9 @@ jobs:
|
|
|
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"
|
|
41
|
+
experimental: false
|
|
42
|
+
lockfile: "locked"
|
|
40
43
|
- ruby: "3.3"
|
|
41
44
|
experimental: false
|
|
42
45
|
lockfile: "locked"
|
|
@@ -49,6 +52,9 @@ jobs:
|
|
|
49
52
|
- ruby: "4.0"
|
|
50
53
|
experimental: true
|
|
51
54
|
lockfile: "locked"
|
|
55
|
+
- ruby: "3.2"
|
|
56
|
+
experimental: false
|
|
57
|
+
lockfile: "unlocked"
|
|
52
58
|
- ruby: "3.3"
|
|
53
59
|
experimental: false
|
|
54
60
|
lockfile: "unlocked"
|
|
@@ -63,7 +69,7 @@ jobs:
|
|
|
63
69
|
lockfile: "unlocked"
|
|
64
70
|
|
|
65
71
|
steps:
|
|
66
|
-
- uses: actions/checkout@
|
|
72
|
+
- uses: actions/checkout@v7.0.0
|
|
67
73
|
- name: Set up Ruby
|
|
68
74
|
uses: ruby/setup-ruby@v1
|
|
69
75
|
continue-on-error: ${{ matrix.experimental }}
|
|
@@ -4,6 +4,16 @@ on:
|
|
|
4
4
|
pull_request:
|
|
5
5
|
types: [opened, synchronize, labeled]
|
|
6
6
|
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
model:
|
|
9
|
+
description: "Claude model for this run (overrides the CLAUDE_MODEL repo variable)"
|
|
10
|
+
type: choice
|
|
11
|
+
required: false
|
|
12
|
+
default: claude-opus-4-6
|
|
13
|
+
options:
|
|
14
|
+
- claude-opus-4-6
|
|
15
|
+
- claude-sonnet-4-6
|
|
16
|
+
- claude-haiku-4-5-20251001
|
|
7
17
|
|
|
8
18
|
jobs:
|
|
9
19
|
claude-review:
|
|
@@ -14,10 +24,21 @@ jobs:
|
|
|
14
24
|
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
15
25
|
|
|
16
26
|
runs-on: ubuntu-latest
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
# Run only when ALL of the following hold:
|
|
28
|
+
# 1. Author is not a bot (renovate, dependabot, etc. surface as 'name[bot]').
|
|
29
|
+
# 2. PR head is not from a fork. Fork PRs run without repository secrets, so
|
|
30
|
+
# CLAUDE_CODE_OAUTH_TOKEN is empty and the action would fail.
|
|
31
|
+
# 3. The event is a freshly opened PR, a 'claude-review' label add, or a push
|
|
32
|
+
# to a PR that already carries the 'claude-review' label.
|
|
33
|
+
if: ${{
|
|
34
|
+
!endsWith(github.actor, '[bot]') &&
|
|
35
|
+
!github.event.pull_request.head.repo.fork &&
|
|
36
|
+
(
|
|
37
|
+
(github.event.action == 'opened') ||
|
|
38
|
+
(github.event.action == 'labeled' && github.event.label.name == 'claude-review') ||
|
|
39
|
+
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'claude-review'))
|
|
40
|
+
)
|
|
41
|
+
}}
|
|
21
42
|
|
|
22
43
|
permissions:
|
|
23
44
|
contents: read
|
|
@@ -27,7 +48,7 @@ jobs:
|
|
|
27
48
|
|
|
28
49
|
steps:
|
|
29
50
|
- name: Checkout repository
|
|
30
|
-
uses: actions/checkout@
|
|
51
|
+
uses: actions/checkout@v7.0.0
|
|
31
52
|
with:
|
|
32
53
|
fetch-depth: 1
|
|
33
54
|
|
|
@@ -37,10 +58,12 @@ jobs:
|
|
|
37
58
|
with:
|
|
38
59
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
39
60
|
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
|
|
61
|
+
# Primary model precedence: the workflow_dispatch "model" input (manual
|
|
62
|
+
# runs) -> the CLAUDE_MODEL repo variable -> this built-in default.
|
|
63
|
+
# Pinning a current id avoids the action's frozen default, which 404s
|
|
64
|
+
# ("model: claude-sonnet-4-20250514"). Fall back to Sonnet on overload.
|
|
65
|
+
model: "${{ inputs.model || vars.CLAUDE_MODEL || 'claude-opus-4-6' }}"
|
|
66
|
+
fallback_model: "${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}"
|
|
44
67
|
|
|
45
68
|
# Direct prompt for automated review (no @claude mention needed)
|
|
46
69
|
direct_prompt: |
|
|
@@ -26,7 +26,7 @@ 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@
|
|
29
|
+
uses: actions/checkout@v7.0.0
|
|
30
30
|
with:
|
|
31
31
|
fetch-depth: 1
|
|
32
32
|
|
|
@@ -36,10 +36,12 @@ jobs:
|
|
|
36
36
|
with:
|
|
37
37
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
38
|
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
39
|
+
# Primary model: set the CLAUDE_MODEL repo variable to override without
|
|
40
|
+
# editing this file. Pinning a current id avoids the action's frozen
|
|
41
|
+
# default, which 404s ("model: claude-sonnet-4-20250514"). Fall back to
|
|
42
|
+
# Sonnet if the primary is unavailable or overloaded.
|
|
43
|
+
model: "${{ vars.CLAUDE_MODEL || 'claude-opus-4-6' }}"
|
|
44
|
+
fallback_model: "${{ vars.CLAUDE_FALLBACK_MODEL || 'claude-sonnet-4-6' }}"
|
|
43
45
|
|
|
44
46
|
# This is an optional setting that allows Claude to read CI results on PRs
|
|
45
47
|
additional_permissions: |
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
|
|
22
22
|
steps:
|
|
23
23
|
- name: Checkout code
|
|
24
|
-
uses: actions/checkout@
|
|
24
|
+
uses: actions/checkout@v7.0.0
|
|
25
25
|
|
|
26
26
|
- name: Set up Ruby
|
|
27
27
|
uses: ruby/setup-ruby@v1
|
|
@@ -88,7 +88,7 @@ jobs:
|
|
|
88
88
|
|
|
89
89
|
steps:
|
|
90
90
|
- name: Checkout code
|
|
91
|
-
uses: actions/checkout@
|
|
91
|
+
uses: actions/checkout@v7.0.0
|
|
92
92
|
|
|
93
93
|
- name: Set up Ruby
|
|
94
94
|
uses: ruby/setup-ruby@v1
|
|
@@ -114,6 +114,16 @@ jobs:
|
|
|
114
114
|
runs-on: ubuntu-latest
|
|
115
115
|
timeout-minutes: 10
|
|
116
116
|
|
|
117
|
+
# `rake` lives in the optional :development group (see Gemfile), so a plain
|
|
118
|
+
# `bundle install` skips it and rubygems/release-gem's `bundle exec rake
|
|
119
|
+
# release` dies with "rake is not currently included in the bundle". Opt the
|
|
120
|
+
# group in for the whole job: setup-ruby's cached `bundle install` reads
|
|
121
|
+
# BUNDLE_WITH so rake is installed, and the release step's `bundle exec`
|
|
122
|
+
# reads it too so rake stays in the resolved bundle. This mirrors the
|
|
123
|
+
# `bundle config set --local with` that ci.yml uses for the same groups.
|
|
124
|
+
env:
|
|
125
|
+
BUNDLE_WITH: development
|
|
126
|
+
|
|
117
127
|
environment:
|
|
118
128
|
name: rubygems.org
|
|
119
129
|
url: https://rubygems.org/gems/otto
|
|
@@ -124,7 +134,7 @@ jobs:
|
|
|
124
134
|
|
|
125
135
|
steps:
|
|
126
136
|
- name: Checkout
|
|
127
|
-
uses: actions/checkout@
|
|
137
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
128
138
|
with:
|
|
129
139
|
persist-credentials: false
|
|
130
140
|
|
|
@@ -159,4 +169,4 @@ jobs:
|
|
|
159
169
|
echo "Releasing otto ${gem_version} from tag ${RELEASE_TAG}"
|
|
160
170
|
|
|
161
171
|
- name: Build and push gem to RubyGems
|
|
162
|
-
uses: rubygems/release-gem@
|
|
172
|
+
uses: rubygems/release-gem@052cc82692552de3ef2b81fd670e41d13cba8092 # v1.4.0
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# .github/workflows/ruby-lint.yml
|
|
2
|
+
|
|
3
|
+
name: Ruby Lint
|
|
4
|
+
|
|
5
|
+
# Runs RuboCop across the supported Ruby versions. Linting is informational:
|
|
6
|
+
# every dependency/lint step is continue-on-error, so style findings surface as
|
|
7
|
+
# annotations without blocking merges.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches:
|
|
12
|
+
- fix/*
|
|
13
|
+
- rel/*
|
|
14
|
+
pull_request:
|
|
15
|
+
branches:
|
|
16
|
+
- main
|
|
17
|
+
- develop
|
|
18
|
+
- feature/*
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
inputs:
|
|
21
|
+
debug_enabled:
|
|
22
|
+
type: boolean
|
|
23
|
+
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
|
|
24
|
+
required: false
|
|
25
|
+
default: false
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
lint:
|
|
32
|
+
timeout-minutes: 10 # prevent hung jobs
|
|
33
|
+
|
|
34
|
+
runs-on: ubuntu-24.04
|
|
35
|
+
|
|
36
|
+
strategy:
|
|
37
|
+
fail-fast: true
|
|
38
|
+
matrix:
|
|
39
|
+
ruby: ['3.2', '3.3', '3.4', '3.5', '4.0']
|
|
40
|
+
continue-on-error: [true]
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- name: Checkout code
|
|
44
|
+
uses: actions/checkout@v6.0.3
|
|
45
|
+
|
|
46
|
+
- uses: ruby/setup-ruby@v1
|
|
47
|
+
with:
|
|
48
|
+
ruby-version: ${{ matrix.ruby }}
|
|
49
|
+
bundler-cache: true
|
|
50
|
+
|
|
51
|
+
- name: Setup tmate session
|
|
52
|
+
uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3
|
|
53
|
+
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
|
|
54
|
+
with:
|
|
55
|
+
detached: true
|
|
56
|
+
|
|
57
|
+
- name: Install dependencies
|
|
58
|
+
continue-on-error: ${{ matrix.continue-on-error }}
|
|
59
|
+
run: |
|
|
60
|
+
bundle config path vendor/bundle
|
|
61
|
+
bundle install --jobs 4 --retry 3
|
|
62
|
+
|
|
63
|
+
- name: Run Rubocop
|
|
64
|
+
continue-on-error: ${{ matrix.continue-on-error }}
|
|
65
|
+
run: |
|
|
66
|
+
bundle exec rubocop --config .rubocop.yml --format json --fail-level warning
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
name: Generate and Deploy YARD Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- 'lib/**/*'
|
|
9
|
+
- 'docs/**/*'
|
|
10
|
+
- 'README.md'
|
|
11
|
+
- 'CHANGELOG.md'
|
|
12
|
+
- 'CHANGELOG.rst'
|
|
13
|
+
- 'LICENSE.txt'
|
|
14
|
+
- '.yardopts'
|
|
15
|
+
- '.github/workflows/yardoc.yml'
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
pages: write
|
|
21
|
+
id-token: write
|
|
22
|
+
|
|
23
|
+
# Allow only one concurrent deployment, skipping runs queued between the run
|
|
24
|
+
# in-progress and latest queued. Do NOT cancel in-progress runs so production
|
|
25
|
+
# deployments can complete.
|
|
26
|
+
concurrency:
|
|
27
|
+
group: "pages"
|
|
28
|
+
cancel-in-progress: false
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
build-docs:
|
|
32
|
+
timeout-minutes: 10
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
name: Generate YARD Documentation
|
|
35
|
+
env:
|
|
36
|
+
# otto declares yard/kramdown in a `group :development` whose name also
|
|
37
|
+
# appears in an `optional: true` block, so Bundler treats `development`
|
|
38
|
+
# as optional and the default `bundle install` skips it (yard never
|
|
39
|
+
# installs -> `bundle exec yard` is "command not found"). Mirror otto's
|
|
40
|
+
# ci.yml convention so setup-ruby installs the dev/test gems.
|
|
41
|
+
BUNDLE_WITH: "development:test"
|
|
42
|
+
|
|
43
|
+
steps:
|
|
44
|
+
- name: Checkout repository
|
|
45
|
+
uses: actions/checkout@v6.0.3
|
|
46
|
+
with:
|
|
47
|
+
fetch-depth: 0
|
|
48
|
+
|
|
49
|
+
- name: Set up Ruby environment
|
|
50
|
+
uses: ruby/setup-ruby@v1
|
|
51
|
+
with:
|
|
52
|
+
ruby-version: '3.4'
|
|
53
|
+
bundler-cache: true
|
|
54
|
+
|
|
55
|
+
- name: Generate documentation
|
|
56
|
+
# Uses the repository's committed .yardopts as the single source of
|
|
57
|
+
# truth for the output dir, includes/excludes, markup, and tags.
|
|
58
|
+
run: |
|
|
59
|
+
echo "::group::YARD Documentation Generation"
|
|
60
|
+
bundle exec yard stats --list-undoc || true
|
|
61
|
+
bundle exec yard doc
|
|
62
|
+
echo "::endgroup::"
|
|
63
|
+
|
|
64
|
+
- name: Disable Jekyll processing
|
|
65
|
+
# YARD emits files and directories that begin with underscores; the
|
|
66
|
+
# .nojekyll marker stops GitHub Pages from stripping them.
|
|
67
|
+
run: touch doc/.nojekyll
|
|
68
|
+
|
|
69
|
+
- name: Validate documentation output
|
|
70
|
+
run: |
|
|
71
|
+
echo "::group::Documentation Validation"
|
|
72
|
+
if [ ! -d "doc" ]; then
|
|
73
|
+
echo "Error: documentation directory 'doc' was not generated." >&2
|
|
74
|
+
echo "Ensure .yardopts sets '--output-dir doc'." >&2
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
if [ ! -f "doc/index.html" ]; then
|
|
78
|
+
echo "Warning: doc/index.html not found"
|
|
79
|
+
fi
|
|
80
|
+
echo "Generated HTML files: $(find doc -name '*.html' | wc -l)"
|
|
81
|
+
echo "Total documentation size: $(du -sh doc/ | cut -f1)"
|
|
82
|
+
echo "::endgroup::"
|
|
83
|
+
|
|
84
|
+
- name: Setup GitHub Pages configuration
|
|
85
|
+
uses: actions/configure-pages@v6
|
|
86
|
+
|
|
87
|
+
- name: Upload documentation artifact
|
|
88
|
+
uses: actions/upload-pages-artifact@v5
|
|
89
|
+
with:
|
|
90
|
+
path: './doc'
|
|
91
|
+
|
|
92
|
+
deploy-pages:
|
|
93
|
+
timeout-minutes: 10
|
|
94
|
+
environment:
|
|
95
|
+
name: github-pages
|
|
96
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
97
|
+
runs-on: ubuntu-latest
|
|
98
|
+
needs: build-docs
|
|
99
|
+
outputs:
|
|
100
|
+
page_url: ${{ steps.deployment.outputs.page_url }}
|
|
101
|
+
|
|
102
|
+
steps:
|
|
103
|
+
- name: Deploy to GitHub Pages
|
|
104
|
+
id: deployment
|
|
105
|
+
uses: actions/deploy-pages@v5
|
|
106
|
+
|
|
107
|
+
notify-completion:
|
|
108
|
+
timeout-minutes: 5
|
|
109
|
+
runs-on: ubuntu-latest
|
|
110
|
+
needs: [build-docs, deploy-pages]
|
|
111
|
+
if: success()
|
|
112
|
+
|
|
113
|
+
steps:
|
|
114
|
+
- name: Documentation deployment summary
|
|
115
|
+
run: |
|
|
116
|
+
echo "::notice title=Documentation Deployed::YARD documentation successfully deployed to GitHub Pages"
|
|
117
|
+
echo "::notice title=Access URL::Documentation available at: ${{ needs.deploy-pages.outputs.page_url }}"
|
data/.yardopts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
--readme README.md
|
|
2
|
+
--title "Otto Ruby Library Documentation"
|
|
3
|
+
--protected
|
|
4
|
+
--no-private
|
|
5
|
+
--markup markdown
|
|
6
|
+
--markup-provider kramdown
|
|
7
|
+
--output-dir doc
|
|
8
|
+
--exclude lib/otto/version.rb
|
|
9
|
+
lib/**/*.rb
|
|
10
|
+
-
|
|
11
|
+
README.md
|
|
12
|
+
CHANGELOG.rst
|
|
13
|
+
LICENSE.txt
|
|
14
|
+
docs/**/*.md
|
|
15
|
+
examples/*.rb
|
data/CHANGELOG.rst
CHANGED
|
@@ -7,6 +7,65 @@ 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.4.0:
|
|
11
|
+
|
|
12
|
+
2.4.0 — 2026-07-01
|
|
13
|
+
==================
|
|
14
|
+
|
|
15
|
+
Added
|
|
16
|
+
-----
|
|
17
|
+
|
|
18
|
+
- ``Otto#enable_csp_reporting!(report_uri, endpoint_url: nil, &block)`` —
|
|
19
|
+
turnkey CSP violation reporting. Emits a ``report-uri`` directive and, with
|
|
20
|
+
``endpoint_url:``, a ``report-to`` directive plus ``Reporting-Endpoints``
|
|
21
|
+
header. Parses legacy ``application/csp-report`` and Reporting API
|
|
22
|
+
``application/reports+json`` payloads into ``Otto::Security::CSP::Report``
|
|
23
|
+
and invokes the callback per violation. Opt-in. (delano/otto#174)
|
|
24
|
+
|
|
25
|
+
- ``MiddlewareStack`` ``:outermost`` position, for middleware that must run
|
|
26
|
+
ahead of all others regardless of registration order.
|
|
27
|
+
|
|
28
|
+
- ``Otto::CaddyTLS``: an opt-in Caddy on-demand TLS permission endpoint,
|
|
29
|
+
enabled with ``otto.enable_caddy_tls! { |domain| ... }``. (delano/otto#175)
|
|
30
|
+
|
|
31
|
+
Fixed
|
|
32
|
+
-----
|
|
33
|
+
|
|
34
|
+
- ``IPPrivacyMiddleware`` no longer writes ``nil`` into CGI-style Rack env
|
|
35
|
+
keys (e.g. ``HTTP_REFERER``, ``HTTP_USER_AGENT``, ``REMOTE_ADDR``) when
|
|
36
|
+
redacting request data, which violated the Rack SPEC and tripped
|
|
37
|
+
``Rack::Lint``. Empty anonymized values now delete the key instead of
|
|
38
|
+
setting it to ``nil``, and a request with no resolvable client IP no
|
|
39
|
+
longer gets a ``nil`` ``REMOTE_ADDR``. (delano/otto#167)
|
|
40
|
+
|
|
41
|
+
- ``Otto::Security::CSP::ReportMiddleware`` no longer turns a downstream
|
|
42
|
+
error on a non-report request into an empty ``204``.
|
|
43
|
+
|
|
44
|
+
Security
|
|
45
|
+
--------
|
|
46
|
+
|
|
47
|
+
- The ``Otto::CaddyTLS`` permission endpoint is loopback-only by default and
|
|
48
|
+
fails closed. (delano/otto#175)
|
|
49
|
+
|
|
50
|
+
- Security middleware registered through the ``otto.security.*``
|
|
51
|
+
Configurator after ``Otto.new`` now actually runs on the request chain —
|
|
52
|
+
previously CSRF, request validation, rate limiting, and CSP reporting
|
|
53
|
+
silently went unenforced.
|
|
54
|
+
|
|
55
|
+
AI Assistance
|
|
56
|
+
-------------
|
|
57
|
+
|
|
58
|
+
- CSP violation reporting (``report-uri`` / ``report-to``), the
|
|
59
|
+
``:outermost`` middleware position, and the Configurator
|
|
60
|
+
middleware-registration fix were designed and implemented with AI
|
|
61
|
+
assistance.
|
|
62
|
+
|
|
63
|
+
- ``Otto::CaddyTLS`` designed, implemented, and reviewed with AI assistance.
|
|
64
|
+
|
|
65
|
+
- The Rack SPEC ``nil``-into-CGI-key fix — including the sibling
|
|
66
|
+
``REMOTE_ADDR`` masking bug and ``Rack::Lint`` test coverage — diagnosed
|
|
67
|
+
and fixed with AI assistance.
|
|
68
|
+
|
|
10
69
|
.. _changelog-2.3.1:
|
|
11
70
|
|
|
12
71
|
2.3.1 — 2026-06-22
|
data/Gemfile
CHANGED
|
@@ -27,8 +27,8 @@ group :development do
|
|
|
27
27
|
gem 'benchmark'
|
|
28
28
|
gem 'debug'
|
|
29
29
|
gem 'rackup' # Used to boot examples/ apps; not needed by specs
|
|
30
|
-
gem 'rake', '~> 13.
|
|
31
|
-
gem 'rubocop', '~> 1.
|
|
30
|
+
gem 'rake', '~> 13.4', require: false # Provides `rake release` for release-gem.yml
|
|
31
|
+
gem 'rubocop', '~> 1.88.0', require: false
|
|
32
32
|
gem 'rubocop-performance', require: false
|
|
33
33
|
gem 'rubocop-rspec', require: false
|
|
34
34
|
gem 'rubocop-thread_safety', require: false
|
|
@@ -36,4 +36,6 @@ group :development do
|
|
|
36
36
|
gem 'stackprof', require: false
|
|
37
37
|
gem 'syntax_tree', require: false
|
|
38
38
|
gem 'tryouts', '~> 3.7.1', require: false
|
|
39
|
+
gem 'yard', '~> 0.9', require: false # API docs for yardoc.yml
|
|
40
|
+
gem 'kramdown', require: false # Markdown provider for YARD
|
|
39
41
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
otto (2.
|
|
4
|
+
otto (2.4.0)
|
|
5
5
|
concurrent-ruby (~> 1.3, < 2.0)
|
|
6
6
|
logger (~> 1, < 2.0)
|
|
7
7
|
loofah (~> 2.20)
|
|
@@ -15,7 +15,7 @@ GEM
|
|
|
15
15
|
ast (2.4.3)
|
|
16
16
|
benchmark (0.5.0)
|
|
17
17
|
bigdecimal (4.1.1)
|
|
18
|
-
concurrent-ruby (1.3.
|
|
18
|
+
concurrent-ruby (1.3.7)
|
|
19
19
|
crass (1.0.6)
|
|
20
20
|
date (3.5.1)
|
|
21
21
|
debug (1.11.1)
|
|
@@ -59,12 +59,14 @@ GEM
|
|
|
59
59
|
prism (>= 1.3.0)
|
|
60
60
|
rdoc (>= 4.0.0)
|
|
61
61
|
reline (>= 0.4.2)
|
|
62
|
-
json (2.19.
|
|
62
|
+
json (2.19.9)
|
|
63
63
|
json_schemer (2.5.0)
|
|
64
64
|
bigdecimal
|
|
65
65
|
hana (~> 1.3)
|
|
66
66
|
regexp_parser (~> 2.0)
|
|
67
67
|
simpleidn (~> 0.2)
|
|
68
|
+
kramdown (2.5.2)
|
|
69
|
+
rexml (>= 3.4.4)
|
|
68
70
|
language_server-protocol (3.17.0.5)
|
|
69
71
|
lint_roller (1.1.0)
|
|
70
72
|
logger (1.7.0)
|
|
@@ -72,21 +74,21 @@ GEM
|
|
|
72
74
|
crass (~> 1.0.2)
|
|
73
75
|
nokogiri (>= 1.12.0)
|
|
74
76
|
minitest (5.26.0)
|
|
75
|
-
nokogiri (1.19.
|
|
77
|
+
nokogiri (1.19.4-aarch64-linux-gnu)
|
|
76
78
|
racc (~> 1.4)
|
|
77
|
-
nokogiri (1.19.
|
|
79
|
+
nokogiri (1.19.4-aarch64-linux-musl)
|
|
78
80
|
racc (~> 1.4)
|
|
79
|
-
nokogiri (1.19.
|
|
81
|
+
nokogiri (1.19.4-arm-linux-gnu)
|
|
80
82
|
racc (~> 1.4)
|
|
81
|
-
nokogiri (1.19.
|
|
83
|
+
nokogiri (1.19.4-arm-linux-musl)
|
|
82
84
|
racc (~> 1.4)
|
|
83
|
-
nokogiri (1.19.
|
|
85
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
84
86
|
racc (~> 1.4)
|
|
85
|
-
nokogiri (1.19.
|
|
87
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
86
88
|
racc (~> 1.4)
|
|
87
|
-
nokogiri (1.19.
|
|
89
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
88
90
|
racc (~> 1.4)
|
|
89
|
-
nokogiri (1.19.
|
|
91
|
+
nokogiri (1.19.4-x86_64-linux-musl)
|
|
90
92
|
racc (~> 1.4)
|
|
91
93
|
parallel (1.28.0)
|
|
92
94
|
parser (3.3.11.1)
|
|
@@ -113,7 +115,7 @@ GEM
|
|
|
113
115
|
rackup (2.3.1)
|
|
114
116
|
rack (>= 3)
|
|
115
117
|
rainbow (3.1.1)
|
|
116
|
-
rake (13.
|
|
118
|
+
rake (13.4.2)
|
|
117
119
|
rbs (4.0.2)
|
|
118
120
|
logger
|
|
119
121
|
prism (>= 1.6.0)
|
|
@@ -145,7 +147,7 @@ GEM
|
|
|
145
147
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
146
148
|
rspec-support (~> 3.13.0)
|
|
147
149
|
rspec-support (3.13.7)
|
|
148
|
-
rubocop (1.
|
|
150
|
+
rubocop (1.88.0)
|
|
149
151
|
json (~> 2.3)
|
|
150
152
|
language_server-protocol (~> 3.17.0.2)
|
|
151
153
|
lint_roller (~> 1.1.0)
|
|
@@ -163,9 +165,10 @@ GEM
|
|
|
163
165
|
lint_roller (~> 1.1)
|
|
164
166
|
rubocop (>= 1.75.0, < 2.0)
|
|
165
167
|
rubocop-ast (>= 1.47.1, < 2.0)
|
|
166
|
-
rubocop-rspec (3.
|
|
168
|
+
rubocop-rspec (3.10.2)
|
|
167
169
|
lint_roller (~> 1.1)
|
|
168
|
-
|
|
170
|
+
regexp_parser (>= 2.0)
|
|
171
|
+
rubocop (~> 1.86, >= 1.86.2)
|
|
169
172
|
rubocop-thread_safety (0.7.3)
|
|
170
173
|
lint_roller (~> 1.1)
|
|
171
174
|
rubocop (~> 1.72, >= 1.72.1)
|
|
@@ -197,6 +200,7 @@ GEM
|
|
|
197
200
|
unicode-emoji (~> 4.1)
|
|
198
201
|
unicode-emoji (4.2.0)
|
|
199
202
|
user_agent_parser (2.21.0)
|
|
203
|
+
yard (0.9.44)
|
|
200
204
|
zeitwerk (2.7.3)
|
|
201
205
|
|
|
202
206
|
PLATFORMS
|
|
@@ -213,14 +217,15 @@ DEPENDENCIES
|
|
|
213
217
|
benchmark
|
|
214
218
|
debug
|
|
215
219
|
json_schemer
|
|
220
|
+
kramdown
|
|
216
221
|
otto!
|
|
217
222
|
rack-attack
|
|
218
223
|
rack-test
|
|
219
224
|
rackup
|
|
220
|
-
rake (~> 13.
|
|
225
|
+
rake (~> 13.4)
|
|
221
226
|
reek (~> 6.5)
|
|
222
227
|
rspec (~> 3.13)
|
|
223
|
-
rubocop (~> 1.
|
|
228
|
+
rubocop (~> 1.88.0)
|
|
224
229
|
rubocop-performance
|
|
225
230
|
rubocop-rspec
|
|
226
231
|
rubocop-thread_safety
|
|
@@ -229,6 +234,7 @@ DEPENDENCIES
|
|
|
229
234
|
syntax_tree
|
|
230
235
|
tryouts (~> 3.7.1)
|
|
231
236
|
user_agent_parser (~> 2.18)
|
|
237
|
+
yard (~> 0.9)
|
|
232
238
|
|
|
233
239
|
BUNDLED WITH
|
|
234
240
|
2.7.1
|