diff --git a/Gemfile b/Gemfile index 90b4021f63..5e198fe515 100644 --- a/Gemfile +++ b/Gemfile @@ -52,6 +52,7 @@ gem 'puma' gem 'bootsnap', require: false gem 'csv' +gem 'uri', '>= 1.1.1' # CVE-2025-61594 # Extend irb for better output gem 'hirb' diff --git a/Gemfile.lock b/Gemfile.lock index 6bb903f2a6..c7cec6e08b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -539,7 +539,7 @@ GEM unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - uri (1.0.3) + uri (1.1.1) useragent (0.16.11) version_gem (1.1.6) warden (1.2.9) @@ -623,6 +623,7 @@ DEPENDENCIES sprockets-rails sys-filesystem tca_client + uri (>= 1.1.1) webmock RUBY VERSION diff --git a/app/api/api_root.rb b/app/api/api_root.rb index e36e21226e..6f2e2fe704 100644 --- a/app/api/api_root.rb +++ b/app/api/api_root.rb @@ -10,9 +10,6 @@ class ApiRoot < Grape::API format :json before do - header['Access-Control-Allow-Origin'] = '*' - header['Access-Control-Request-Method'] = '*' - Thread.current.thread_variable_set(:ip, request.ip) end diff --git a/config/application.rb b/config/application.rb index fe280a2899..36e403e43c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -244,10 +244,27 @@ def self.fetch_boolean_env(name) Rails.root.join('app/models/d2l') # CORS config + # Configure a strict allowlist. Override per environment via: + # CORS_ALLOWED_ORIGINS="http://localhost:4200,https://frontend.example.edu" + default_cors_origins = [ + 'http://localhost:4200', + "https://#{config.institution[:host]}" + ].uniq + allowed_cors_origins = ENV.fetch('CORS_ALLOWED_ORIGINS', default_cors_origins.join(',')) + .split(',') + .map(&:strip) + .reject(&:empty?) + .uniq + config.middleware.insert_before Warden::Manager, Rack::Cors do allow do - origins '*' - resource '*', headers: :any, methods: %i(get post put delete options) + origins do |source, _env| + allowed_cors_origins.include?(source) + end + + resource '*', + headers: %w[Content-Type Authorization Accept], + methods: %i[get post put delete options] end end diff --git a/config/initializers/uri_credential_leak_mitigation.rb b/config/initializers/uri_credential_leak_mitigation.rb new file mode 100644 index 0000000000..1096c5545a --- /dev/null +++ b/config/initializers/uri_credential_leak_mitigation.rb @@ -0,0 +1,21 @@ +require 'uri' + +# Mitigates credential leakage when combining a credential-bearing base URI +# with a relative URI via URI#+ by stripping userinfo from the merged result. +module UriCredentialLeakMitigation + def +(other) + combined = super + + return combined unless other.respond_to?(:absolute?) && !other.absolute? + return combined unless combined.respond_to?(:user=) || combined.respond_to?(:password=) + + sanitized = combined.dup + sanitized.user = nil if sanitized.respond_to?(:user=) + sanitized.password = nil if sanitized.respond_to?(:password=) + sanitized + rescue URI::InvalidComponentError + combined + end +end + +URI::Generic.prepend(UriCredentialLeakMitigation)