Skip to content

Commit f4d42dc

Browse files
committed
[#71630] Ensure redirects in the API without security issues
1 parent b69328b commit f4d42dc

File tree

1 file changed

+21
-8
lines changed

1 file changed

+21
-8
lines changed

lib/api/helpers/historical_identifier_redirect.rb

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,29 @@ def redirect_if_historical_identifier(identifier_param, project)
5454
# Only redirect if:
5555
# 1. The parameter is a friendly_id slug (not numeric ID)
5656
# 2. The parameter doesn't match the project's current identifier
57-
if param_value.friendly_id? && param_value != project.identifier
58-
# Reconstruct the current URL with the new identifier
59-
# Handle both path parameters (e.g., /workspaces/old-id) and query parameters (e.g., ?of=old-id)
60-
new_url = request.url.sub(
61-
/([?&]#{identifier_param}=|\/)(#{Regexp.escape(param_value)})(\b|&|$)/,
62-
"\\1#{project.identifier}\\3"
57+
if request.get? && param_value.friendly_id? && param_value != project.identifier
58+
# Reconstruct only the path and query string, not the full URL
59+
# This prevents Host header injection and open redirect attacks
60+
path = request.path
61+
query_string = request.query_string
62+
63+
# Replace the old identifier in the path
64+
new_path = path.sub(
65+
%r{(/)#{Regexp.escape(param_value)}(/|$)},
66+
"\\1#{project.identifier}\\2"
6367
)
6468

65-
# Return 301 Moved Permanently
66-
redirect new_url, permanent: true
69+
# Replace the old identifier in query parameters if present
70+
if query_string.present?
71+
new_query_string = query_string.gsub(
72+
/(\A|&)#{Regexp.escape(identifier_param.to_s)}=#{Regexp.escape(param_value)}(&|\z)/,
73+
"\\1#{identifier_param}=#{CGI.escape(project.identifier)}\\2"
74+
)
75+
new_path += "?#{new_query_string}"
76+
end
77+
78+
# Return 301 Moved Permanently with path-only redirect
79+
redirect new_path, permanent: true
6780
end
6881
end
6982
end

0 commit comments

Comments
 (0)