otto 2.3.0 → 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 +33 -10
- data/.github/workflows/claude.yml +9 -2
- 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 +84 -0
- data/Gemfile +4 -1
- data/Gemfile.lock +23 -15
- data/README.md +96 -0
- data/Rakefile +21 -0
- data/docs/.gitignore +1 -0
- data/docs/migrating/v2.3.0.md +50 -3
- 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/configuration.rb +9 -1
- 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 +293 -2
- data/lib/otto/security/configurator.rb +53 -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 +133 -18
- data/lib/otto/version.rb +1 -1
- data/lib/otto.rb +26 -3
- metadata +24 -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: |
|
|
@@ -51,7 +74,7 @@ jobs:
|
|
|
51
74
|
- Security concerns
|
|
52
75
|
- Test coverage
|
|
53
76
|
|
|
54
|
-
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
|
77
|
+
Use the repository's CLAUDE.md or AGENTS.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
|
55
78
|
|
|
56
79
|
# Use sticky comments to reuse the same comment on subsequent pushes to the same PR
|
|
57
80
|
use_sticky_comment: true
|
|
@@ -26,16 +26,23 @@ 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
|
|
|
33
33
|
- name: Run Claude Code
|
|
34
34
|
id: claude
|
|
35
|
-
uses: anthropics/claude-code-action@
|
|
35
|
+
uses: anthropics/claude-code-action@beta
|
|
36
36
|
with:
|
|
37
37
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
38
|
|
|
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' }}"
|
|
45
|
+
|
|
39
46
|
# This is an optional setting that allows Claude to read CI results on PRs
|
|
40
47
|
additional_permissions: |
|
|
41
48
|
actions: read
|
|
@@ -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,90 @@ 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
|
+
|
|
69
|
+
.. _changelog-2.3.1:
|
|
70
|
+
|
|
71
|
+
2.3.1 — 2026-06-22
|
|
72
|
+
==================
|
|
73
|
+
|
|
74
|
+
Added
|
|
75
|
+
-----
|
|
76
|
+
|
|
77
|
+
- Depth mode (``Otto::Security::Config#trusted_proxy_depth``) can now count hops
|
|
78
|
+
from a configurable forwarded header, via a new ``#trusted_proxy_header``
|
|
79
|
+
accessor: ``X-Forwarded-For`` (default), ``Forwarded`` (RFC 7239), or ``Both``
|
|
80
|
+
(``Forwarded`` when it carries a ``for=``, otherwise ``X-Forwarded-For``).
|
|
81
|
+
Settable through ``Otto::Security::Configurator#configure`` and the
|
|
82
|
+
``trusted_proxy_header`` option of ``Otto.new`` / ``configure_security``; an
|
|
83
|
+
unrecognized value raises ``ArgumentError`` at assignment. This reaches parity
|
|
84
|
+
with OneTimeSecret's ``site.network.trusted_proxy.header``. (delano/otto#150,
|
|
85
|
+
onetimesecret#3436)
|
|
86
|
+
|
|
87
|
+
AI Assistance
|
|
88
|
+
-------------
|
|
89
|
+
|
|
90
|
+
- RFC 7239 ``Forwarded`` / ``Both`` depth-header support designed and implemented
|
|
91
|
+
with AI pair programming, with per-header, quoted-IPv6, and ``Both``-precedence
|
|
92
|
+
test coverage.
|
|
93
|
+
|
|
10
94
|
.. _changelog-2.3.0:
|
|
11
95
|
|
|
12
96
|
2.3.0 — 2026-06-21
|
data/Gemfile
CHANGED
|
@@ -27,7 +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 '
|
|
30
|
+
gem 'rake', '~> 13.4', require: false # Provides `rake release` for release-gem.yml
|
|
31
|
+
gem 'rubocop', '~> 1.88.0', require: false
|
|
31
32
|
gem 'rubocop-performance', require: false
|
|
32
33
|
gem 'rubocop-rspec', require: false
|
|
33
34
|
gem 'rubocop-thread_safety', require: false
|
|
@@ -35,4 +36,6 @@ group :development do
|
|
|
35
36
|
gem 'stackprof', require: false
|
|
36
37
|
gem 'syntax_tree', require: false
|
|
37
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
|
|
38
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,6 +115,7 @@ GEM
|
|
|
113
115
|
rackup (2.3.1)
|
|
114
116
|
rack (>= 3)
|
|
115
117
|
rainbow (3.1.1)
|
|
118
|
+
rake (13.4.2)
|
|
116
119
|
rbs (4.0.2)
|
|
117
120
|
logger
|
|
118
121
|
prism (>= 1.6.0)
|
|
@@ -144,7 +147,7 @@ GEM
|
|
|
144
147
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
145
148
|
rspec-support (~> 3.13.0)
|
|
146
149
|
rspec-support (3.13.7)
|
|
147
|
-
rubocop (1.
|
|
150
|
+
rubocop (1.88.0)
|
|
148
151
|
json (~> 2.3)
|
|
149
152
|
language_server-protocol (~> 3.17.0.2)
|
|
150
153
|
lint_roller (~> 1.1.0)
|
|
@@ -162,9 +165,10 @@ GEM
|
|
|
162
165
|
lint_roller (~> 1.1)
|
|
163
166
|
rubocop (>= 1.75.0, < 2.0)
|
|
164
167
|
rubocop-ast (>= 1.47.1, < 2.0)
|
|
165
|
-
rubocop-rspec (3.
|
|
168
|
+
rubocop-rspec (3.10.2)
|
|
166
169
|
lint_roller (~> 1.1)
|
|
167
|
-
|
|
170
|
+
regexp_parser (>= 2.0)
|
|
171
|
+
rubocop (~> 1.86, >= 1.86.2)
|
|
168
172
|
rubocop-thread_safety (0.7.3)
|
|
169
173
|
lint_roller (~> 1.1)
|
|
170
174
|
rubocop (~> 1.72, >= 1.72.1)
|
|
@@ -196,6 +200,7 @@ GEM
|
|
|
196
200
|
unicode-emoji (~> 4.1)
|
|
197
201
|
unicode-emoji (4.2.0)
|
|
198
202
|
user_agent_parser (2.21.0)
|
|
203
|
+
yard (0.9.44)
|
|
199
204
|
zeitwerk (2.7.3)
|
|
200
205
|
|
|
201
206
|
PLATFORMS
|
|
@@ -212,13 +217,15 @@ DEPENDENCIES
|
|
|
212
217
|
benchmark
|
|
213
218
|
debug
|
|
214
219
|
json_schemer
|
|
220
|
+
kramdown
|
|
215
221
|
otto!
|
|
216
222
|
rack-attack
|
|
217
223
|
rack-test
|
|
218
224
|
rackup
|
|
225
|
+
rake (~> 13.4)
|
|
219
226
|
reek (~> 6.5)
|
|
220
227
|
rspec (~> 3.13)
|
|
221
|
-
rubocop (~> 1.
|
|
228
|
+
rubocop (~> 1.88.0)
|
|
222
229
|
rubocop-performance
|
|
223
230
|
rubocop-rspec
|
|
224
231
|
rubocop-thread_safety
|
|
@@ -227,6 +234,7 @@ DEPENDENCIES
|
|
|
227
234
|
syntax_tree
|
|
228
235
|
tryouts (~> 3.7.1)
|
|
229
236
|
user_agent_parser (~> 2.18)
|
|
237
|
+
yard (~> 0.9)
|
|
230
238
|
|
|
231
239
|
BUNDLED WITH
|
|
232
240
|
2.7.1
|