61 lines
2 KiB
Ruby
61 lines
2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# == Sanitizable concern
|
|
#
|
|
# This concern adds HTML sanitization and validation to models. The intention is
|
|
# to help prevent XSS attacks in the event of a by-pass in the frontend
|
|
# sanitizer due to a configuration issue or a vulnerability in the sanitizer.
|
|
# This approach is commonly referred to as defense-in-depth.
|
|
#
|
|
# Example:
|
|
#
|
|
# module Dast
|
|
# class Profile < ApplicationRecord
|
|
# include Sanitizable
|
|
#
|
|
# sanitizes! :name, :description
|
|
|
|
module Sanitizable
|
|
extend ActiveSupport::Concern
|
|
|
|
class_methods do
|
|
def sanitize(input)
|
|
return unless input
|
|
|
|
# We return the input unchanged to avoid escaping pre-escaped HTML fragments.
|
|
# Please see gitlab-org/gitlab#293634 for an example.
|
|
return input unless input == CGI.unescapeHTML(input.to_s)
|
|
|
|
CGI.unescapeHTML(Sanitize.fragment(input))
|
|
end
|
|
|
|
def sanitizes!(*attrs)
|
|
instance_eval do
|
|
before_validation do
|
|
attrs.each do |attr|
|
|
input = public_send(attr) # rubocop: disable GitlabSecurity/PublicSend
|
|
|
|
public_send("#{attr}=", self.class.sanitize(input)) # rubocop: disable GitlabSecurity/PublicSend
|
|
end
|
|
end
|
|
|
|
validates_each(*attrs) do |record, attr, input|
|
|
# We reject pre-escaped HTML fragments as invalid to avoid saving them
|
|
# to the database.
|
|
unless input.to_s == CGI.unescapeHTML(input.to_s)
|
|
record.errors.add(attr, 'cannot contain escaped HTML entities')
|
|
end
|
|
|
|
# This method raises an exception on failure so perform this
|
|
# last if multiple errors should be returned.
|
|
Gitlab::Utils.check_path_traversal!(input.to_s)
|
|
|
|
rescue Gitlab::Utils::DoubleEncodingError
|
|
record.errors.add(attr, 'cannot contain escaped components')
|
|
rescue Gitlab::Utils::PathTraversalAttackError
|
|
record.errors.add(attr, "cannot contain a path traversal component")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|