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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +5 -0
  3. data/.github/workflows/ci.yml +11 -8
  4. data/.github/workflows/claude-code-review.yml +38 -13
  5. data/.github/workflows/claude.yml +10 -8
  6. data/.github/workflows/code-smells.yml +5 -5
  7. data/.github/workflows/release-gem.yml +2 -2
  8. data/.github/workflows/ruby-lint.yml +3 -3
  9. data/.github/workflows/yardoc.yml +5 -5
  10. data/.gitignore +1 -5
  11. data/.rubocop_todo.yml +7 -5
  12. data/AGENTS.md +22 -1
  13. data/CHANGELOG.rst +203 -0
  14. data/Gemfile +3 -3
  15. data/Gemfile.lock +11 -15
  16. data/README.md +75 -37
  17. data/docs/README.md +166 -0
  18. data/docs/adr/README.md +16 -0
  19. data/docs/adr/adr-001-route-authentication-at-handler-boundary.md +38 -0
  20. data/docs/adr/adr-002-multi-strategy-authentication-and-authorization.md +50 -0
  21. data/docs/adr/adr-003-caddy-tls-route-based-integration.md +48 -0
  22. data/docs/adr/adr-004-separate-compatibility-from-security-maintenance.md +58 -0
  23. data/docs/guides/authentication.md +377 -0
  24. data/docs/guides/caddy-tls.md +205 -0
  25. data/docs/guides/configuration_freezing.md +146 -0
  26. data/docs/guides/enrichment.md +161 -0
  27. data/docs/guides/forwarded-authority.md +249 -0
  28. data/docs/guides/geo-country.md +168 -0
  29. data/docs/guides/ip_privacy.md +39 -0
  30. data/docs/guides/ipaddr-encoding-quirk.md +56 -0
  31. data/docs/guides/mcp.md +282 -0
  32. data/docs/guides/privacy.md +193 -0
  33. data/docs/guides/routing.md +181 -0
  34. data/docs/guides/structured_logging.md +281 -0
  35. data/docs/guides/testing-guide.md +391 -0
  36. data/docs/maintainers/github-actions.md +41 -0
  37. data/docs/maintainers/investigations/.gitignore +2 -0
  38. data/docs/migrating/v2.0.0.md +337 -0
  39. data/docs/reference/authentication.md +290 -0
  40. data/docs/reference/route-syntax.md +181 -0
  41. data/docs/reference/runtime-and-dependency-security.md +86 -0
  42. data/examples/advanced_routes/README.md +43 -57
  43. data/examples/authentication_strategies/README.md +37 -196
  44. data/examples/basic/README.md +24 -39
  45. data/examples/basic/config.ru +0 -1
  46. data/examples/caddy_tls_demo/README.md +8 -2
  47. data/examples/lambda_handlers/README.md +11 -2
  48. data/examples/mcp_demo/README.md +75 -161
  49. data/examples/mcp_demo/config.ru +1 -0
  50. data/examples/security_features/README.md +36 -234
  51. data/lib/otto/caddy_tls/localhost_guard.rb +22 -24
  52. data/lib/otto/core/configuration.rb +36 -22
  53. data/lib/otto/core/file_safety.rb +89 -31
  54. data/lib/otto/core/middleware_stack.rb +36 -36
  55. data/lib/otto/core/router.rb +62 -19
  56. data/lib/otto/env_keys.rb +20 -2
  57. data/lib/otto/mcp/auth/token.rb +10 -4
  58. data/lib/otto/mcp/core.rb +23 -5
  59. data/lib/otto/mcp/endpoint.rb +41 -0
  60. data/lib/otto/mcp/errors.rb +15 -0
  61. data/lib/otto/mcp/options.rb +292 -0
  62. data/lib/otto/mcp/protocol.rb +52 -22
  63. data/lib/otto/mcp/rate_limiting.rb +175 -97
  64. data/lib/otto/mcp/registry.rb +14 -14
  65. data/lib/otto/mcp/schema_validation.rb +20 -12
  66. data/lib/otto/mcp/server.rb +131 -32
  67. data/lib/otto/optional_dependency.rb +57 -0
  68. data/lib/otto/privacy/config.rb +10 -8
  69. data/lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb +22 -5
  70. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +181 -18
  71. data/lib/otto/security/authentication/strategies/permission_strategy.rb +1 -0
  72. data/lib/otto/security/authentication/strategies/role_strategy.rb +1 -0
  73. data/lib/otto/security/authentication/strategies/session_strategy.rb +1 -0
  74. data/lib/otto/security/authentication/strategy_result.rb +58 -28
  75. data/lib/otto/security/config.rb +328 -14
  76. data/lib/otto/security/configurator.rb +36 -6
  77. data/lib/otto/security/core.rb +19 -1
  78. data/lib/otto/security/middleware/ip_privacy_middleware.rb +105 -4
  79. data/lib/otto/security/middleware/rate_limit_middleware.rb +1 -6
  80. data/lib/otto/security/rate_limiter.rb +81 -46
  81. data/lib/otto/utils.rb +50 -0
  82. data/lib/otto/version.rb +1 -1
  83. data/lib/otto.rb +9 -11
  84. data/otto.gemspec +0 -2
  85. metadata +31 -41
  86. data/docs/.gitignore +0 -10
  87. data/docs/1108-STREAMING_ARCHITECTURE_ANALYSIS.md +0 -1105
  88. data/docs/1108-STREAMING_SUPPORT_SUMMARY.md +0 -376
  89. data/docs/enrichment.md +0 -128
  90. data/docs/geo-country.md +0 -181
  91. data/docs/ipaddr-encoding-quirk.md +0 -34
  92. data/docs/migrating/v2.0.0-pre1.md +0 -276
  93. data/docs/migrating/v2.0.0-pre2.md +0 -338
  94. data/docs/modern-authentication-authorization-landscape.md +0 -558
  95. data/docs/multi-strategy-authentication-design.md +0 -1401
  96. data/docs/reverse-proxy-network-services.md +0 -371
@@ -1,75 +1,96 @@
1
1
  # Otto MCP Demo
2
2
 
3
- This example demonstrates Otto's Model-Controller-Protocol (MCP) feature. MCP provides a standardized JSON-RPC 2.0 endpoint (`/_mcp`) for programmatic access to your application resources and tools.
4
-
5
- MCP is useful for building CLIs, admin interfaces, integrating with AI systems, or allowing other services to interact with your application.
3
+ This example boots Otto's Model Context Protocol (MCP) JSON-RPC 2.0 endpoint at
4
+ `/_mcp`. Use it to verify endpoint initialization alongside ordinary Otto web
5
+ routes.
6
6
 
7
7
  ## What You'll Learn
8
8
 
9
- - How to set up an MCP endpoint for programmatic access
10
- - Exposing application resources via JSON-RPC 2.0
11
- - Securing MCP endpoints with bearer token authentication
12
- - Distinguishing between read-only resources and executable tools
13
- - Organizing methods for both web and MCP interfaces
14
- - How MCP integrates with your existing Otto application
9
+ - How to enable an MCP HTTP endpoint
10
+ - How the endpoint coexists with ordinary Otto web routes
11
+ - How to send a JSON-RPC 2.0 `initialize` request
12
+ - How bearer-token authentication protects the endpoint
13
+ - The current resource and tool limitations described below
15
14
 
16
15
  ## Features Demonstrated
17
16
 
18
- - **MCP Endpoint**: Single `POST /_mcp` endpoint for all interactions
19
- - **Authentication**: Bearer token authentication for secure access
20
- - **Rate Limiting**: Built-in rate limiting to prevent abuse
21
- - **Resources**: Read-only data exposed via `MCP` routes
22
- - **Tools**: Executable actions exposed via `TOOL` routes
23
- - **Web Interface**: Separate web routes coexist with MCP routes
24
- - **JSON-RPC 2.0**: Standard protocol for all MCP interactions
17
+ - **MCP endpoint**: A single `POST /_mcp` endpoint
18
+ - **Web interface**: Separate web routes coexist with the MCP endpoint
19
+ - **JSON-RPC 2.0**: `initialize`, `resources/list`, and `tools/list` requests
25
20
 
26
21
  ## How to Run
27
22
 
28
- ### Using rackup (recommended)
23
+ Run this example from an Otto source checkout. It requires Ruby 3.2 through
24
+ 4.0, Bundler, and the development dependencies: `rackup` is a development
25
+ dependency in the root `Gemfile` and is not installed with the released `otto`
26
+ gem. MCP enables schema validation and rate limiting by default. Those features
27
+ require `json_schemer` 2.0.0 or newer in the 2.x series and `rack-attack` 6.7.0
28
+ or newer in the 6.x series:
29
29
 
30
- ```sh
31
- cd examples/mcp_demo
32
- rackup config.ru
30
+ ```ruby
31
+ # Gemfile
32
+ gem 'json_schemer', '~> 2.0'
33
+ gem 'rack-attack', '~> 6.7'
33
34
  ```
34
35
 
35
- ### Using thin
36
+ If either enabled feature's gem is missing or incompatible, MCP setup raises
37
+ `Otto::OptionalDependencyError` during configuration. Pass
38
+ `enable_validation: false` or `enable_rate_limiting: false` to `enable_mcp!`, or
39
+ alongside `mcp_enabled: true` in the `Otto.new` options, only when that protection
40
+ is intentionally disabled. To enforce the configured limits, mount
41
+ `Rack::Attack` before Otto in `config.ru`, as this example does.
36
42
 
37
43
  ```sh
44
+ cd /path/to/otto
45
+ bundle config set with development
46
+ bundle install
38
47
  cd examples/mcp_demo
39
- thin -R config.ru -p 9292 start
48
+ bundle exec rackup config.ru
40
49
  ```
41
50
 
42
- The server will start on `http://localhost:9292`.
43
51
 
44
- - **Web interface**: Navigate to `http://localhost:9292` in your browser
45
- - **MCP endpoint**: Send JSON-RPC 2.0 requests to `http://localhost:9292/_mcp`
52
+ The server listens at `http://localhost:9292` by default.
53
+
54
+ - **Web interface**: Open `http://localhost:9292/`.
55
+ - **Health check**: `curl -i http://localhost:9292/health` returns `200` and `OK`.
56
+ - **MCP endpoint**: Send JSON-RPC 2.0 requests to `http://localhost:9292/_mcp`.
46
57
 
47
58
  ## Authentication
48
59
 
49
- All MCP requests require bearer token authentication via the `Authorization` header.
60
+ `config.ru` configures two bearer tokens, and Otto enforces them: every request
61
+ to `/_mcp` must send `Authorization: Bearer demo-token-123` (or
62
+ `X-MCP-Token: demo-token-123`). Requests without a valid token get HTTP `401`
63
+ and a JSON-RPC `Unauthorized` error. The `requests_per_minute` and
64
+ `tools_per_minute` values in `config.ru` are applied as configured. See the
65
+ [MCP guide](../../docs/guides/mcp.md) for the full option list.
66
+
67
+ ## Current Limitations
50
68
 
51
- Valid tokens:
52
- - `demo-token-123` - Standard user
53
- - `another-token-456` - Alternative user
69
+ Although `routes` contains `MCP /users` and `TOOL /create_user` declarations,
70
+ the current route-loading path does not register them. Therefore
71
+ `resources/list` and `tools/list` both return empty arrays, and `resources/read`
72
+ or `tools/call` for those names fails. This README documents the runnable
73
+ endpoint behavior; do not use this example as a resource or tool integration
74
+ template until those routes are registered.
54
75
 
55
76
  ## Interacting with the MCP Endpoint
56
77
 
57
78
  All MCP interactions use the `POST /_mcp` endpoint. Each request is a JSON-RPC 2.0 request with:
58
79
 
59
80
  - `jsonrpc`: Always `"2.0"`
60
- - `method`: The RPC method name (derived from route path)
81
+ - `method`: The RPC method name
61
82
  - `id`: Request ID (for matching responses)
62
83
  - `params`: Optional parameters as an object
63
84
 
64
85
  Required headers:
65
- - `Authorization: Bearer <token>`
66
86
  - `Content-Type: application/json`
87
+ - `Authorization: Bearer demo-token-123` (or `X-MCP-Token: demo-token-123`)
67
88
 
68
89
  Example:
69
90
  ```sh
70
91
  curl -X POST http://localhost:9292/_mcp \
71
- -H 'Authorization: Bearer demo-token-123' \
72
92
  -H 'Content-Type: application/json' \
93
+ -H 'Authorization: Bearer demo-token-123' \
73
94
  -d '{
74
95
  "jsonrpc": "2.0",
75
96
  "method": "initialize",
@@ -84,156 +105,53 @@ The `initialize` method is a built-in MCP method that returns information about
84
105
 
85
106
  ```sh
86
107
  curl -X POST http://localhost:9292/_mcp \
87
- -H 'Authorization: Bearer demo-token-123' \
88
108
  -H 'Content-Type: application/json' \
109
+ -H 'Authorization: Bearer demo-token-123' \
89
110
  -d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{}}'
90
111
  ```
91
112
 
92
- ### Resource: List Users
113
+ ## Verification
93
114
 
94
- This calls the `UserAPI.mcp_list_users` method defined as an `MCP` route. The method name for the JSON-RPC call is derived from the route path (`/users` -> `users/list`).
115
+ A successful `initialize` request returns `result.protocolVersion`,
116
+ `result.capabilities`, and `result.serverInfo` with the same request ID. To
117
+ confirm the current registry state, run:
95
118
 
96
119
  ```sh
97
120
  curl -X POST http://localhost:9292/_mcp \
98
- -H 'Authorization: Bearer demo-token-123' \
99
- -H 'Content-Type: application/json' \
100
- -d '{"jsonrpc":"2.0","method":"users/list","id":2}'
101
- ```
102
-
103
- ### Tool: Create User
104
-
105
- This calls the `UserAPI.mcp_create_user` method defined as a `TOOL` route. The method name is derived from the route path (`/create_user` -> `create_user`).
121
+ -H 'Content-Type: application/json' \
122
+ -H 'Authorization: Bearer demo-token-123' \
123
+ -d '{"jsonrpc":"2.0","method":"resources/list","id":2}'
106
124
 
107
- ```sh
108
125
  curl -X POST http://localhost:9292/_mcp \
109
- -H 'Authorization: Bearer demo-token-123' \
110
- -H 'Content-Type: application/json' \
111
- -d '{
112
- "jsonrpc": "2.0",
113
- "method": "create_user",
114
- "id": 3,
115
- "params": {
116
- "name": "Charlie",
117
- "email": "charlie@example.com"
118
- }
119
- }'
120
- ```
121
-
122
- ## Expected Output
123
-
124
- ### Successful Initialize Request
125
- ```json
126
- {
127
- "jsonrpc": "2.0",
128
- "result": {
129
- "resources": [
130
- {
131
- "uri": "users",
132
- "name": "User List",
133
- "description": "List all users"
134
- }
135
- ],
136
- "tools": [
137
- {
138
- "name": "create_user",
139
- "description": "Create a new user",
140
- "inputSchema": {
141
- "type": "object",
142
- "properties": {
143
- "name": { "type": "string" },
144
- "email": { "type": "string" }
145
- }
146
- }
147
- }
148
- ]
149
- },
150
- "id": 1
151
- }
152
- ```
153
-
154
- ### Successful Resource Request
155
- ```json
156
- {
157
- "jsonrpc": "2.0",
158
- "result": {
159
- "users": [
160
- { "id": 1, "name": "Alice", "email": "alice@example.com" },
161
- { "id": 2, "name": "Bob", "email": "bob@example.com" }
162
- ]
163
- },
164
- "id": 2
165
- }
166
- ```
167
-
168
- ### Successful Tool Execution
169
- ```json
170
- {
171
- "jsonrpc": "2.0",
172
- "result": {
173
- "user": {
174
- "id": 3,
175
- "name": "Charlie",
176
- "email": "charlie@example.com"
177
- }
178
- },
179
- "id": 3
180
- }
126
+ -H 'Content-Type: application/json' \
127
+ -H 'Authorization: Bearer demo-token-123' \
128
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":3}'
181
129
  ```
182
130
 
183
- ### Authentication Failure
184
- ```json
185
- {
186
- "jsonrpc": "2.0",
187
- "error": {
188
- "code": -32003,
189
- "message": "Unauthorized"
190
- },
191
- "id": 1
192
- }
193
- ```
131
+ Each request returns `"jsonrpc":"2.0"` and an empty `resources` or `tools`
132
+ array in the current checkout.
194
133
 
195
134
  ## File Structure
196
135
 
197
136
  - `README.md`: This file
198
137
  - `app.rb`: Application logic
199
- - `DemoApp`: Web interface with HTML pages
200
- - `UserAPI`: MCP handlers for resources and tools
138
+ - `DemoApp`: Web interface and health check
139
+ - `UserAPI`: Intended MCP resource and tool handlers (not registered by the current route-loading path)
201
140
  - `config.ru`: Rack configuration (loads Otto, enables MCP)
202
141
  - `routes`: Route definitions for web and MCP routes
203
142
 
204
- ## Route Types
143
+ ## Routes
205
144
 
206
- ### Web Routes
207
- Regular HTTP routes for the web interface:
208
- ```
209
- GET / DemoApp#welcome
210
- GET /users DemoApp#list_users
211
- ```
212
-
213
- ### MCP Resource Routes
214
- Read-only resources exposed via MCP:
215
- ```
216
- MCP /users UserAPI#mcp_list_users
217
- ```
218
- - Called via: `POST /_mcp` with method `users/list`
145
+ The ordinary web routes in `routes` are active:
219
146
 
220
- ### MCP Tool Routes
221
- Executable operations exposed via MCP:
222
147
  ```
223
- TOOL /create_user UserAPI#mcp_create_user
148
+ GET / DemoApp.index
149
+ GET /health DemoApp.health
224
150
  ```
225
- - Called via: `POST /_mcp` with method `create_user`
226
151
 
227
- ## Understanding MCP Method Names
228
-
229
- MCP route paths are converted to method names:
230
-
231
- | Route Type | Path | Method Name | Handler |
232
- |-----------|------|-------------|---------|
233
- | MCP | `/users` | `users/list` | `mcp_list_users` |
234
- | MCP | `/users/:id` | `users/get` | `mcp_get_user` |
235
- | TOOL | `/create_user` | `create_user` | `mcp_create_user` |
236
- | TOOL | `/users/:id/update` | `users/update` | `mcp_update_user` |
152
+ The file also contains `MCP /users UserAPI.mcp_list_users` and
153
+ `TOOL /create_user UserAPI.mcp_create_user`. See [Current Limitations](#current-limitations):
154
+ they are not registered by this checkout's route-loading path.
237
155
 
238
156
  ## Next Steps
239
157
 
@@ -241,7 +159,3 @@ MCP route paths are converted to method names:
241
159
  - Integrate with AI systems that support MCP
242
160
  - Combine with [Authentication](../authentication_strategies/) for role-based MCP access
243
161
  - Explore [Advanced Routes](../advanced_routes/) for more routing patterns
244
-
245
- ## Further Reading
246
-
247
- - [CLAUDE.md](../../CLAUDE.md#mcp) - Detailed MCP documentation (if available)
@@ -14,4 +14,5 @@ app = Otto.new('routes', {
14
14
  # The `mcp_enabled: true` flag automatically sets up the /_mcp endpoint.
15
15
  # The routes file maps MCP and TOOL methods to classes.
16
16
 
17
+ use Rack::Attack
17
18
  run app
@@ -1,265 +1,67 @@
1
1
  # Otto Security Features Example
2
2
 
3
- This example demonstrates Otto's built-in security features, showing best practices for CSRF protection, input validation, file upload handling, and security headers.
3
+ This source-checkout example configures CSRF protection, request validation, request-size and parameter limits, trusted proxies, and response security headers. It provides forms for feedback, file-upload metadata, and profile input so you can observe those protections.
4
4
 
5
- ## What You'll Learn
5
+ ## Prerequisites
6
6
 
7
- - Enabling and using CSRF protection
8
- - Input validation for preventing injection attacks
9
- - XSS prevention through output escaping
10
- - Secure file upload handling with filename sanitization
11
- - Adding security headers (CSP, HSTS, etc.)
12
- - Request limiting to prevent denial-of-service
13
- - Trusted proxy configuration for reverse proxies
14
- - Privacy features (IP masking, user agent anonymization)
7
+ Run this example from an Otto source checkout. Its `config.ru` loads Otto from `../../lib`, rather than from an installed gem.
15
8
 
16
- ## Security Features Demonstrated
17
-
18
- ### CSRF Protection
19
- All POST/PUT/DELETE requests include CSRF tokens in forms:
20
- ```ruby
21
- <form method="post">
22
- <input type="hidden" name="_csrf_token" value="<%= @req.csrf_token %>">
23
- <input type="text" name="message">
24
- </form>
25
- ```
26
-
27
- ### Input Validation
28
- Server-side validation of user-submitted data:
29
- - Length limits (max 1000 chars for messages)
30
- - Character restrictions (no HTML tags)
31
- - Required field validation
32
- - Type validation
33
-
34
- ### XSS Prevention
35
- All output is properly escaped:
36
- ```ruby
37
- @res.body = "<h1>#{ERB::Util.html_escape(user_input)}</h1>"
38
- ```
39
-
40
- ### Secure File Uploads
41
- File uploads are validated and sanitized:
42
- - File type checking (whitelist approach)
43
- - Size limits (prevent large uploads)
44
- - Filename sanitization (remove path traversal)
45
- - Safe storage location
46
-
47
- ### Security Headers
48
- Automatic security headers are sent with responses:
49
- - `Content-Security-Policy` - Prevents inline scripts
50
- - `Strict-Transport-Security` - Enforces HTTPS
51
- - `X-Frame-Options` - Prevents clickjacking
52
- - `X-Content-Type-Options` - Prevents MIME sniffing
53
-
54
- ### Request Limiting
55
- Configure limits to prevent DOS attacks:
56
- - Maximum request size
57
- - Maximum parameter keys
58
- - Maximum parameter depth
59
-
60
- ### Trusted Proxies
61
- Configure reverse proxy IPs for X-Forwarded-For headers:
62
- ```ruby
63
- app.add_trusted_proxy('10.0.0.0/8')
64
- app.add_trusted_proxy(/^192\.168\./)
65
- ```
66
-
67
- ### Privacy by Default
68
- Automatic privacy features:
69
- - Public IP masking (203.0.113.50 → 203.0.113.0)
70
- - User agent anonymization (versions stripped)
71
- - Country-level geo-location only
72
- - Private/localhost IPs NOT masked by default (configurable via `configure_ip_privacy()`)
73
-
74
- ## How to Run
75
-
76
- ### Using rackup (recommended)
9
+ You need a supported Ruby version and Bundler. `rackup` is a development dependency in the root `Gemfile`, so enable the `development` group before installing dependencies:
77
10
 
78
11
  ```sh
79
- cd examples/security_features
80
- rackup config.ru -p 10770
12
+ # From the repository root
13
+ bundle config set with development
14
+ bundle install
81
15
  ```
82
16
 
83
- ### Using thin
17
+ ## Run the example
84
18
 
85
19
  ```sh
20
+ # From the repository root
86
21
  cd examples/security_features
87
- thin -e dev -R config.ru -p 10770 start
88
- ```
89
-
90
- Open your browser and navigate to `http://localhost:10770`.
91
-
92
- ## Testing Security Features
93
-
94
- ### XSS Prevention
95
-
96
- Try entering `<script>alert("XSS")</script>` in form fields:
97
- - The script tag is rendered as text, not executed
98
- - You'll see it displayed as literal HTML tags
99
- - Browser's developer tools show escaped HTML
100
-
101
- ### Input Validation
102
-
103
- Test validation rules:
104
- - Submit a message > 1000 characters (fails)
105
- - Submit special characters like `<>` (fails)
106
- - Submit valid text (succeeds)
107
-
108
- ### CSRF Protection
109
-
110
- Examine form submissions:
111
- - All POST forms include a `_csrf_token` hidden field
112
- - Each request has a unique token
113
- - Removing the token causes 403 Forbidden
114
- - Browser's developer tools show token in form data
115
-
116
- ### File Uploads
117
-
118
- Test file upload security:
119
- - Try uploading an executable file (rejected)
120
- - Try uploading a legitimate image (accepted)
121
- - Check saved filename (sanitized, safe)
122
- - Verify file permissions and location
123
-
124
- ### Security Headers
125
-
126
- Check response headers:
127
- - Open browser's Network tab in developer tools
128
- - Click any response to view headers
129
- - Look for security headers in response
130
- - Visit `/headers` endpoint to see all headers
131
-
132
- ## Expected Output
133
-
134
- ### Successful Form Submission
22
+ bundle exec rackup config.ru -p 10770
135
23
  ```
136
- POST /feedback HTTP/1.1
137
- Content-Type: application/x-www-form-urlencoded
138
24
 
139
- _csrf_token=abc123...
140
- message=Hello+world
25
+ Open `http://127.0.0.1:10770/`. Keep the server running and use a second terminal for the checks below.
141
26
 
142
- HTTP/1.1 302 Found
143
- Location: http://localhost:10770/
144
- Content-Security-Policy: default-src 'self'
145
- Strict-Transport-Security: max-age=31536000
146
- ```
147
-
148
- ### Failed CSRF Validation
149
- ```
150
- HTTP/1.1 403 Forbidden
151
- Content-Type: text/plain
27
+ ## What this example configures
152
28
 
153
- CSRF token validation failed
154
- ```
29
+ `config.ru` enables CSRF protection and request validation, then sets these limits:
155
30
 
156
- ### Failed Input Validation
157
- ```
158
- HTTP/1.1 400 Bad Request
159
- Content-Type: text/plain
31
+ - Maximum request size: 5 MiB
32
+ - Maximum parameter nesting depth: 10
33
+ - Maximum parameter keys: 50
160
34
 
161
- Message is too long (max 1000 characters)
162
- ```
35
+ It trusts the listed loopback and private-network proxy ranges. Adjust `trusted_proxies` for a real deployment; do not copy this development-oriented list without confirming the proxies that can reach your application.
163
36
 
164
- ### Security Headers Response
165
- ```
166
- Content-Security-Policy: default-src 'self'
167
- Strict-Transport-Security: max-age=31536000
168
- X-Frame-Options: SAMEORIGIN
169
- X-Content-Type-Options: nosniff
170
- Referrer-Policy: strict-origin-when-cross-origin
171
- ```
37
+ Every route receives the configured headers:
172
38
 
173
- ## File Structure
39
+ - `Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'`
40
+ - `Strict-Transport-Security: max-age=31536000; includeSubDomains`
41
+ - `X-Frame-Options: DENY`
174
42
 
175
- - `README.md`: This file
176
- - `app.rb`: Main application logic with security implementations
177
- - Form validation and escaping
178
- - File upload handling
179
- - Header configuration
180
- - `config.ru`: Rack configuration with security features enabled
181
- - `routes`: URL routes mapped to SecureApp class methods
182
-
183
- ## Key Configuration
184
-
185
- In `config.ru`:
186
- ```ruby
187
- app = Otto.new("./routes")
188
-
189
- # Enable security features
190
- app.enable_csrf_protection!
191
-
192
- # Configure request limits
193
- app.security_config.request_size_limit = 1.megabyte
194
- app.security_config.max_parameter_keys = 100
195
- app.security_config.max_parameter_depth = 5
196
-
197
- # Add trusted proxies if behind reverse proxy
198
- app.add_trusted_proxy('10.0.0.0/8')
199
-
200
- # Security headers
201
- app.add_security_header('X-Custom-Header', 'value')
202
- ```
43
+ ## Verify the protections
203
44
 
204
- ## Common Attack Scenarios
45
+ Use the diagnostic route to inspect request information and the response headers. A POST without a CSRF token exercises the CSRF rejection path:
205
46
 
206
- ### XSS Attack
207
- ```javascript
208
- <img src=x onerror="alert('XSS')">
209
- ```
210
- **Result**: Safely displayed as text, not executed
211
-
212
- ### SQL Injection
213
- ```sql
214
- '; DROP TABLE users; --
215
- ```
216
- **Result**: Stored as literal text, invalid SQL
217
-
218
- ### Path Traversal
219
- ```
220
- ../../../../../../etc/passwd
221
- ```
222
- **Result**: Filename sanitized to just `etc-passwd`
223
-
224
- ### Large Request
225
- ```
226
- POST with 10MB body
47
+ ```sh
48
+ curl -i http://127.0.0.1:10770/headers
49
+ curl -i -X POST http://127.0.0.1:10770/feedback -d 'message=test'
227
50
  ```
228
- **Result**: Rejected with 413 Payload Too Large
229
51
 
230
- ## Best Practices Demonstrated
52
+ The feedback handler rejects script-like input and limits messages to 1,000 characters; profile fields have their own limits and the email field must match the example's basic format check. The upload form demonstrates filename sanitization and request handling. It displays metadata and does **not** permanently store uploaded files; it does not implement a file-type allowlist.
231
53
 
232
- 1. **Defense in Depth**: Multiple layers of security
233
- 2. **Input Validation**: Whitelist approach (allow only safe input)
234
- 3. **Output Escaping**: Escape all user-controlled output
235
- 4. **CSRF Tokens**: Unique tokens for each request
236
- 5. **Security Headers**: Prevent common attack vectors
237
- 6. **File Upload Safety**: Validate type and sanitize names
238
- 7. **Request Limiting**: Prevent denial-of-service
54
+ ### Current limitation
239
55
 
240
- ## Testing with curl
241
-
242
- ```sh
243
- # Test CSRF protection (will fail without token)
244
- curl -X POST http://localhost:10770/feedback \
245
- -d "message=test"
246
-
247
- # Test with valid CSRF token (get token from form first)
248
- curl -X POST http://localhost:10770/feedback \
249
- -d "_csrf_token=<token>" \
250
- -d "message=test"
251
-
252
- # Test input validation
253
- curl -X POST http://localhost:10770/feedback \
254
- -d "message=$(python -c 'print(\"x\" * 2000)')"
255
- ```
56
+ The home page generates a CSRF token during its first request. Otto freezes configuration at that point, and the generated-secret warning then attempts to modify the frozen security configuration. As a result, `GET /` currently fails with `FrozenError`, so browser-based valid-form verification is unavailable until that example or the initialization sequence is corrected.
256
57
 
257
- ## Next Steps
58
+ ## Files to inspect
258
59
 
259
- - Review the application code to see implementation details
260
- - Explore other examples for different features
60
+ - `config.ru` creates the Otto application and sets the security configuration.
61
+ - `routes` defines the form and diagnostic endpoints.
62
+ - `app.rb` renders CSRF form fields and handles validation, escaped output, and uploaded-file metadata.
261
63
 
262
- ## Further Reading
64
+ ## Next steps
263
65
 
264
- - [CLAUDE.md](../../CLAUDE.md#security-features) - Security configuration reference
265
- - [IP Privacy](../../CLAUDE.md#ip-privacy-privacy-by-default) - Privacy configuration
66
+ - See the [authentication strategies example](../authentication_strategies/README.md) for route authentication.
67
+ - Read the [security configuration in the main README](../../README.md#security-features).