-
Notifications
You must be signed in to change notification settings - Fork 0
[DEMO] A/B test demonstration #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
cc4fc0f to
5a7c840
Compare
5a7c840 to
e1b375d
Compare
7170de9 to
63c21fa
Compare
0b28cac to
947d918
Compare
39584c5 to
336d289
Compare
336d289 to
60b6e78
Compare
9236f6a to
b42adf4
Compare
2aabeff to
fa1fd94
Compare
8fa812d to
b5f200a
Compare
b5f200a to
b741a71
Compare
84f921b to
989f13d
Compare
64f8cc6 to
59926ef
Compare
14c39d5 to
39203fa
Compare
Add reference to readme for how to run Redis The split gem includes an instruction about how to run Redis (via homebrew), but I don't want to replicate those instructures - so this is mostly just a reminder to future-me about how to run it. Ultimately this should be part of the devcontainer, or something similar? Fixup
This sets up a simple UI experiment, defining an alternative set of styles for the search form. The search form starts in the _form.html.erb partial, with most users getting a "standard" class - the experimental group would get the "alt" class, which is rendered differently. The experiment ends when a user reaches a search results page (i.e, they submitted the form and thus got results). Curious folks can force themselves into one or the other pool by using a querystring such as ?ab_test[ui_colors]=alt or ?ab_test[ui_colors]=standard Refactor UI experiment based on recent changes Fixup search controller
This tries implementing a second (concurrent) experiment, which would rely on knowing how often a user clicks an outbound link. That means we need an interstitial redirect in order to conclude the experiment, which happens in the new outbound path. PLEASE NOTE: This isn't working yet, because the redirect is getting caught by Turbo, it seems, and violating CORS policies. As a result, we're just logging the outbound URLs to the console.
39203fa to
fc8866d
Compare
| if params[:url].present? | ||
| ab_finished(:result_format) | ||
|
|
||
| redirect_to params[:url], allow_other_host: true |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, to fix untrusted URL redirection you must avoid redirecting directly to arbitrary user input. Instead, you either (a) map a small, controlled set of keys to full redirect URLs on the server, or (b) validate the supplied URL so that only acceptable targets (for example specific hosts or schemes) are allowed, and reject or replace everything else with a safe default.
For this specific RecordController#out method, the least invasive fix that preserves existing behavior (redirecting to external links for A/B testing) is to validate the params[:url] using Ruby’s URI parsing and an allow‑list of acceptable schemes (typically just http and https). This still allows external redirects but prevents obviously malicious or malformed inputs (e.g., javascript: URLs). Since the controller already opts in to allow_other_host: true, we should not restrict by host without additional business requirements, but we can at least ensure the value parses as a URI and has an allowed scheme, or is a relative path. If validation fails or parsing raises an exception, we redirect to a safe fallback ('/'), just as the current code does in the “no url” case.
Concretely, in app/controllers/record_controller.rb:
- Add a small private helper method, e.g.,
safe_redirect_url, that takes the rawparams[:url], returns aStringif it’s a valid and allowed target, and returnsnilotherwise. It should:require 'uri'at the top of the file to useURI.parse.- Accept
nilor blank values and treat them as invalid (returnnil). - Attempt to parse the URL with
URI.parse. - Allow redirects if:
- the parsed URI has no scheme and no host (a relative URL), or
- the scheme is
httporhttps(case-insensitive).
- Rescue
URI::InvalidURIErrorand treat as invalid.
- Update
outto use this helper: computetarget_url = safe_redirect_url(params[:url]). Iftarget_urlis present, callab_finished(:result_format)andredirect_to target_url, allow_other_host: true; otherwise, redirect to'/'without callingab_finished.
This approach keeps the observable behavior the same for valid, typical outbound links but adds robust validation, addressing the CodeQL finding.
-
Copy modified lines R1-R2 -
Copy modified line R12 -
Copy modified lines R14-R16 -
Copy modified lines R38-R55
| @@ -1,3 +1,5 @@ | ||
| require 'uri' | ||
|
|
||
| class RecordController < ApplicationController | ||
| before_action :validate_id!, only: %i[view] | ||
|
|
||
| @@ -7,10 +9,11 @@ | ||
| # | ||
| # @param url String a URL where the URL will be redirected | ||
| def out | ||
| if params[:url].present? | ||
| ab_finished(:result_format) | ||
| target_url = safe_redirect_url(params[:url]) | ||
|
|
||
| redirect_to params[:url], allow_other_host: true | ||
| if target_url.present? | ||
| ab_finished(:result_format) | ||
| redirect_to target_url, allow_other_host: true | ||
| else | ||
| redirect_to '/' | ||
| end | ||
| @@ -33,6 +35,24 @@ | ||
|
|
||
| private | ||
|
|
||
| def safe_redirect_url(raw_url) | ||
| return nil if raw_url.blank? | ||
|
|
||
| begin | ||
| uri = URI.parse(raw_url) | ||
|
|
||
| # Allow relative URLs (no scheme/host) | ||
| return raw_url if uri.scheme.nil? && uri.host.nil? | ||
|
|
||
| # Allow only HTTP(S) URLs | ||
| return raw_url if %w[http https].include?(uri.scheme&.downcase) | ||
| rescue URI::InvalidURIError | ||
| # fall through to return nil | ||
| end | ||
|
|
||
| nil | ||
| end | ||
|
|
||
| def validate_id! | ||
| return if params[:id]&.strip.present? | ||
|
|
This PR is meant to demonstrate how an A/B testing framework might function within our Rails applications. It is only a demonstration.
To see the experiment
When users visit the application, they'll be randomly selected into one of two groups. Each will be shown one version of the experiment. As of 1/7/25, the two versions look like this:
?ab_test[ui_colors]=standardto the URL.?ab_test[ui_colors]=altto the URL.To see the results
The results dashboard can be seen at the
/splitpath. The Split gem will collect data, and should report results when they become available.Developer
our guide and
all issues introduced by these changes have been resolved or opened as new
issues (link to those issues in the Pull Request details above)
Code Reviewer
(not just this pull request message)
Requires database migrations?
NO
Includes new or updated dependencies?
YES