One of our internal tools is just a ruby script that makes use of YAML::Store, which is a version of PStore that uses YAML as the underlying data structure. The script didn’t tie down any particular gem versions and out of the blue we hit the following error:

Tried to load unspecified class: Time (Psych::DisallowedClass)

How to resolve this? What was happening? Well it seems newer versions of Psych the Ruby YAML parser have restrictions built in on which classes they will and wont parse out of YAML, Time is not one that is allowed. You can see this happening with a very simple little snippet:

require 'yaml'
yaml = "---\nfoo: #{Time.now}"
works = YAML.safe_load yaml, permitted_classes: [Time]
puts works    # {"foo"=>2021-08-29 08:31:06 +0100}
fails = YAML.load yaml
puts fails    # Psych::DisallowedClass (Tried to load unspecified class: Time)

After looking around the source and the Internet in general there didn’t appear to be an obvious way to set a globally acceptable list of allowed classes. Hopefully there is one and we’ll identify it or some helpful soul out there will point out the obvious. You can see a few other places where people have been caught out.

Our problem lay in that our failure was burried and we didn’t really want to start hacking around with PStore or rather YAML::Store which is where this should be fixed, since it would probably require a bit more proper work and we needed a quick fix. In the end we opted to monkey patch Psych::ClassLoader::Restricted since it is a very basic and simple class that is unlikly to change much between minor versions. Our monkey patch being:

Psych::ClassLoader::ALLOWED_PSYCH_CLASSES = [ Time ]

module Psych
  class ClassLoader
    ALLOWED_PSYCH_CLASSES = [] unless defined? ALLOWED_PSYCH_CLASSES
    class Restricted < ClassLoader
      def initialize classes, symbols
        @classes = classes + Psych::ClassLoader::ALLOWED_PSYCH_CLASSES.map(&:to_s)
        @symbols = symbols
        super()
      end
    end
  end
end

So all we’re doing is adding in an extra array of accepted classes from a constant. That at least got our script up and running again without excessive work. We then went to look to patch YAML::Store to expose an accepted list of YAML data types / classes. However we were beaten to it and the load method has been substitued with the unsafe_load one which bypassses the class checking! So in newer versions of Ruby than the one we happened to have (running our script) the problem should no longer exist for users of YAML::Store (pstore).

If you wanted or needed just to hack that change in as a monkey patch you could use the following snippet:

class YAML::Store < PStore
  def load(content)
    table = YAML.unsafe_load(content)
    if table == false
      {}
    else
      table
    end
  end
end