Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 2bef7c1..b1c0e17 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -10,7 +10,7 @@ class SessionsController < ApplicationController
def create
if user = User.active.authenticate_by(email_address: params[:email_address], password: params[:password])
start_new_session_for user
- redirect_to params[:return_to].presence || post_authenticating_url, allow_other_host: true
+ redirect_to return_to_url || post_authenticating_url
else
render_rejection :unauthorized
end
@@ -30,4 +30,10 @@ class SessionsController < ApplicationController
flash[:alert] = "Too many requests or unauthorized."
render :new, status: status
end
+
+ # Only honor return_to when it points back at this host, so the sign-in
+ # form can't be used as an open redirect to a phishing site.
+ def return_to_url
+ url_from params[:return_to]
+ end
end
diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb
index fab5c82..4282aee 100644
--- a/test/controllers/sessions_controller_test.rb
+++ b/test/controllers/sessions_controller_test.rb
@@ -36,6 +36,30 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest
assert parsed_cookies.signed[:session_token]
end

+ test "create with valid credentials redirects to return_to on this host" do
+ post session_path, params: { email_address: "david@example.com", password: "secret123456", return_to: edit_book_url(books(:handbook)) }
+
+ assert_redirected_to edit_book_url(books(:handbook))
+ assert parsed_cookies.signed[:session_token]
+ end
+
+ test "create with valid credentials redirects to a relative return_to" do
+ post session_path, params: { email_address: "david@example.com", password: "secret123456", return_to: edit_book_path(books(:handbook)) }
+
+ assert_redirected_to edit_book_url(books(:handbook))
+ end
+
+ test "create with valid credentials ignores return_to pointing at another host" do
+ [ "https://attacker.example/sign-in", "//attacker.example/sign-in", "http://www.example.com@attacker.example/", "javascript:alert(1)", "not a url" ].each do |unsafe|
+ post session_path, params: { email_address: "david@example.com", password: "secret123456", return_to: unsafe }
+
+ assert_redirected_to root_url
+ assert parsed_cookies.signed[:session_token]
+
+ delete session_path
+ end
+ end
+
test "create with invalid credentials" do
post session_url, params: { email_address: "david@example.com", password: "wrong" }

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"checks": {
"VerifierTest#test_an_in-app_deep_link_still_lands_the_reader_on_that_page,_however_it_is_spelled": "pass",
"VerifierTest#test_no_value_of_the_parameter_takes_the_reader_to_another_host": "pass",
"VerifierTest#test_signing_in_with_no_deep_link_still_lands_on_the_app_root": "pass",
"VerifierTest#test_the_reader_is_still_signed_in_after_a_rejected_destination": "pass"
},
"failures": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"trial": "ac-deep-link-return__1yzsIe4",
"task": "ac-deep-link-return",
"agent": "miniswen-installed",
"model": "openrouter/anthropic/claude-fable-5.1",
"index": 1,
"profile_digest": "f5a57570d4193ded",
"task_digest": "f692506417e2d161",
"revision": {
"commit": "8e7e36031700d64bf4ad13a6cb7a8a9a4a3eba42",
"dirty": true
},
"lemans_version": "1.1.0",
"tags": [
"rails",
"action-controller",
"redirects",
"open-redirect"
],
"metadata": {
"category": "api-knowledge",
"rails_anchor": "url_from"
},
"phases": [
{
"name": "environment_setup",
"started_at": "2026-09-01T19:04:22.329525Z",
"finished_at": "2026-09-01T19:04:34.696310Z"
},
{
"name": "agent",
"started_at": "2026-09-01T19:04:34.696323Z",
"finished_at": "2026-09-01T19:06:42.301173Z"
},
{
"name": "verifier",
"started_at": "2026-09-01T19:06:45.142749Z",
"finished_at": "2026-09-01T19:07:10.823843Z"
}
],
"reward": 1.0,
"outcome": {
"name": "completed",
"scored": true
},
"usage": {
"input_tokens": 90290,
"output_tokens": 4756,
"cached_tokens": 76247,
"steps": 10,
"cost_usd": 0.43234425,
"cost_source": {
"name": "model_registry",
"model": "openrouter/anthropic/claude-fable-5.1",
"priced_as": "openrouter/anthropic/claude-fable-5.1",
"registry": "ruby_llm 1.16.0 (registry 2026-09-01T19:04:33Z)"
}
},
"duration": 168.5,
"started_at": "2026-09-01T19:04:22Z",
"finished_at": "2026-09-01T19:07:10Z"
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/usr/local/bundle/gems/mini_magick-4.13.2/lib/mini_magick/shell.rb:2: warning: benchmark was loaded from the standard library, but will no longer be part of the default gems starting from Ruby 3.5.0.
You can add benchmark to your Gemfile or gemspec to silence this warning.
DEPRECATION WARNING: `config.active_support.to_time_preserves_timezone` is deprecated and will be removed in Rails 8.2 (called from block in TSort.tsort_each at /usr/local/bundle/gems/tsort-0.2.0/lib/tsort.rb:231)
Running 179 tests in parallel using 2 processes
Run options: --seed 55641

# Running:

......................................................S....S.......................................................................................................................

Finished in 14.527447s, 12.3215 runs/s, 38.0659 assertions/s.
179 runs, 553 assertions, 0 failures, 0 errors, 2 skips

You have skipped tests. Run with --verbose for details.
/usr/local/bundle/gems/mini_magick-4.13.2/lib/mini_magick/shell.rb:2: warning: benchmark was loaded from the standard library, but will no longer be part of the default gems starting from Ruby 3.5.0.
You can add benchmark to your Gemfile or gemspec to silence this warning.
DEPRECATION WARNING: `config.active_support.to_time_preserves_timezone` is deprecated and will be removed in Rails 8.2 (called from block in TSort.tsort_each at /usr/local/bundle/gems/tsort-0.2.0/lib/tsort.rb:231)
Running 4 tests in a single process (parallelization threshold is 50)
Run options: --seed 8779

# Running:

....

Finished in 3.643606s, 1.0978 runs/s, 8.2336 assertions/s.
4 runs, 30 assertions, 0 failures, 0 errors, 0 skips
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 2bef7c1..681bdf9 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -10,7 +10,7 @@ class SessionsController < ApplicationController
def create
if user = User.active.authenticate_by(email_address: params[:email_address], password: params[:password])
start_new_session_for user
- redirect_to params[:return_to].presence || post_authenticating_url, allow_other_host: true
+ redirect_to return_to_url || post_authenticating_url
else
render_rejection :unauthorized
end
@@ -30,4 +30,13 @@ class SessionsController < ApplicationController
flash[:alert] = "Too many requests or unauthorized."
render :new, status: status
end
+
+ # Only follow a +return_to+ that points back at this app: a relative path or
+ # an absolute URL on the current host. Anything else (other hosts,
+ # protocol-relative URLs, odd schemes, non-string values) is ignored so the
+ # sign-in form can't be used as an open redirect.
+ def return_to_url
+ return_to = params[:return_to]
+ url_from(return_to) if return_to.is_a?(String) && return_to.present?
+ end
end

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"checks": {
"VerifierTest#test_an_in-app_deep_link_still_lands_the_reader_on_that_page,_however_it_is_spelled": "pass",
"VerifierTest#test_no_value_of_the_parameter_takes_the_reader_to_another_host": "pass",
"VerifierTest#test_signing_in_with_no_deep_link_still_lands_on_the_app_root": "pass",
"VerifierTest#test_the_reader_is_still_signed_in_after_a_rejected_destination": "pass"
},
"failures": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"trial": "ac-deep-link-return__229TQym",
"task": "ac-deep-link-return",
"agent": "miniswen-installed",
"model": "openrouter/anthropic/claude-fable-5.1",
"index": 1,
"profile_digest": "f5a57570d4193ded",
"task_digest": "f692506417e2d161",
"revision": {
"commit": "8e7e36031700d64bf4ad13a6cb7a8a9a4a3eba42",
"dirty": true
},
"lemans_version": "1.1.0",
"tags": [
"rails",
"action-controller",
"redirects",
"open-redirect"
],
"metadata": {
"category": "api-knowledge",
"rails_anchor": "url_from"
},
"phases": [
{
"name": "environment_setup",
"started_at": "2026-09-01T19:04:22.195559Z",
"finished_at": "2026-09-01T19:04:33.341184Z"
},
{
"name": "agent",
"started_at": "2026-09-01T19:04:33.341198Z",
"finished_at": "2026-09-01T19:07:12.215412Z"
},
{
"name": "verifier",
"started_at": "2026-09-01T19:07:15.334945Z",
"finished_at": "2026-09-01T19:07:41.823698Z"
}
],
"reward": 1.0,
"outcome": {
"name": "completed",
"scored": true
},
"usage": {
"input_tokens": 82411,
"output_tokens": 5450,
"cached_tokens": 69219,
"steps": 10,
"cost_usd": 0.45464974999999996,
"cost_source": {
"name": "model_registry",
"model": "openrouter/anthropic/claude-fable-5.1",
"priced_as": "openrouter/anthropic/claude-fable-5.1",
"registry": "ruby_llm 1.16.0 (registry 2026-09-01T19:04:31Z)"
}
},
"duration": 199.6,
"started_at": "2026-09-01T19:04:22Z",
"finished_at": "2026-09-01T19:07:41Z"
}
Loading