Skip to content

Instantly share code, notes, and snippets.

@chriscz
Last active November 21, 2022 14:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chriscz/d8e073a4d17df9e25ed2f9d6d5b73227 to your computer and use it in GitHub Desktop.
Save chriscz/d8e073a4d17df9e25ed2f9d6d5b73227 to your computer and use it in GitHub Desktop.
After the Rails 5.2.8.1 security release it's required to specify which classes are permitted for deserialization by YAML. However, when it's a high effort task to discover, it's easier to run production in "unsafe" mode for some time and collect which classes are being loaded.
# Place under initializers/yaml_autoloader.rb
class PsychLoaderPatch
include Singleton
def initialize
logfile = File.open(Rails.root.join("log/yaml_disallowed_classes.log").to_s, "a")
logfile.sync = true
@logger = Logger.new(logfile)
@seen_classes = Set.new
@seen_classes_mutex = Mutex.new
end
def resolve_class(klass_name, &super_block)
if klass_name && seen_class?(klass_name)
klass_name.safe_constantize
else
begin
super_block.call
rescue Psych::DisallowedClass
disallowed_class(klass_name)
end
end
end
private
def seen_class?(klass_name)
@seen_classes.include?(klass_name)
end
def seen_class(klass_name)
@seen_classes_mutex.synchronize do
@seen_classes << klass_name
end
end
def disallowed_class(klass_name)
@logger.info(klass_name)
seen_class(klass_name)
klass_name.safe_constantize
end
def self.patch!
Psych::Visitors::ToRuby.prepend Module.new {
def resolve_class(klass_name)
::PsychLoaderPatch.instance.resolve_class(klass_name) { super }
end
}
Psych::ClassLoader::Restricted.prepend Module.new {
def find(klass_name)
::PsychLoaderPatch.instance.resolve_class(klass_name) { super }
end
}
end
end
PsychLoaderPatch.patch!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment