{"record":{"id":"5c9c298d2dc4310a","repo":"instructure/canvas-lms","slug":"assets-url-host-for-app-must-be-one-of-allowed-hosts-join","errorCode":null,"errorMessage":"assets_url host for '#{app}' must be one of: #{ALLOWED_HOSTS.join(\", \")}","messagePattern":"assets_url host for '#(.+?)' must be one of: #(.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":400,"severity":"critical","filePath":"app/controllers/microfrontends_release_tag_override_controller.rb","lineNumber":75,"sourceCode":"\n  def validate_params!\n    override_params = params[:override]\n\n    unless override_params.respond_to?(:each)\n      raise ArgumentError, \"override parameter must be a hash\"\n    end\n\n    override_params.each do |app, assets_url|\n      next if assets_url.blank?\n\n      unless SUPPORTED_APPS.include?(app)\n        raise ArgumentError, \"app '#{app}' must be one of: #{SUPPORTED_APPS.join(\", \")}\"\n      end\n\n      begin\n        uri = URI.parse(assets_url)\n        unless ALLOWED_HOSTS.include?(uri.host)\n          raise ArgumentError, \"assets_url host for '#{app}' must be one of: #{ALLOWED_HOSTS.join(\", \")}\"\n        end\n      rescue URI::InvalidURIError\n        raise ArgumentError, \"assets_url for '#{app}' must be a valid URL\"\n      end\n    end\n  end\nend\n","sourceCodeStart":57,"sourceCodeEnd":83,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/controllers/microfrontends_release_tag_override_controller.rb#L57-L83","documentation":"Raised by validate_params! when the assets_url for an app parses successfully but its host is not in ALLOWED_HOSTS. This prevents overriding release tags to point at attacker-controlled CDNs (supply-chain protection).","triggerScenarios":"Providing an assets_url pointing to an unapproved host (personal CDN, localhost, another environment's host); forgetting the port/scheme differences so uri.host doesn't match the allow-list entry; typos in the hostname.","commonSituations":"Engineers testing with a staging CDN host not in ALLOWED_HOSTS; DNS/CDN migration changing hostnames; trying a local dev URL in a production controller.","solutions":["Use an assets_url whose host is listed in ALLOWED_HOSTS","Add the legitimate new host to ALLOWED_HOSTS in the controller config","Fix hostname typos and ensure the exact host (no port suffix) matches","Deploy overrides only from approved infrastructure"],"exampleFix":"// before\n{ override: { 'k5': 'https://evil-cdn.example.net/assets.js' } }\n// after\n{ override: { 'k5': 'https://cdn.instructure.com/k5/latest/assets.js' } }","handlingStrategy":"validation","validationCode":"const u = new URL(assets_url);\nif (!ALLOWED_HOSTS.includes(u.hostname)) throw new Error(`host ${u.hostname} not allowed`);","typeGuard":null,"tryCatchPattern":"begin\n  validate_params!\nrescue ArgumentError => e\n  render json: { error: e.message, allowed: ALLOWED_HOSTS }, status: :bad_request\nend","preventionTips":["Only use approved CDN hosts for release overrides","Require review to modify ALLOWED_HOSTS","Log rejected hosts for security auditing"],"tags":["security","allowlist","ssrf-prevention"],"backgroundTag":"invalid-url","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}