Module: SafeYAML

Defined in:
lib/safe_yaml/load.rb,
lib/safe_yaml/deep.rb,
lib/safe_yaml/load.rb,
lib/safe_yaml/version.rb,
lib/safe_yaml/resolver.rb,
lib/safe_yaml/transform.rb,
lib/safe_yaml/parse/date.rb,
lib/safe_yaml/psych_handler.rb,
lib/safe_yaml/syck_resolver.rb,
lib/safe_yaml/psych_resolver.rb,
lib/safe_yaml/transform/to_nil.rb,
lib/safe_yaml/parse/hexadecimal.rb,
lib/safe_yaml/parse/sexagesimal.rb,
lib/safe_yaml/transform/to_date.rb,
lib/safe_yaml/transform/to_float.rb,
lib/safe_yaml/transform/to_symbol.rb,
lib/safe_yaml/safe_to_ruby_visitor.rb,
lib/safe_yaml/transform/to_boolean.rb,
lib/safe_yaml/transform/to_integer.rb,
lib/safe_yaml/transform/transformation_map.rb

Overview

This needs to be defined up front in case any internal classes need to base their behavior off of this.

Defined Under Namespace

Classes: Deep, Parse, PsychHandler, PsychResolver, Resolver, SafeToRubyVisitor, SyckResolver, Transform

Constant Summary collapse

YAML_ENGINE =
defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck"
LIBYAML_VERSION =
YAML_ENGINE == "psych" && Psych.const_defined?("LIBYAML_VERSION", false) ? Psych::LIBYAML_VERSION : nil
MULTI_ARGUMENT_YAML_LOAD =
YAML.method(:load).arity != 1
DEFAULT_OPTIONS =
Deep.freeze({
  :default_mode         => nil,
  :suppress_warnings    => false,
  :deserialize_symbols  => false,
  :whitelisted_tags     => [],
  :custom_initializers  => {},
  :raise_on_unknown_tag => false
})
OPTIONS =
Deep.copy(DEFAULT_OPTIONS)
PREDEFINED_TAGS =
{}
TRUSTED_TAGS =
Set.new([
  "tag:yaml.org,2002:binary",
  "tag:yaml.org,2002:bool#no",
  "tag:yaml.org,2002:bool#yes",
  "tag:yaml.org,2002:float",
  "tag:yaml.org,2002:float#fix",
  "tag:yaml.org,2002:int",
  "tag:yaml.org,2002:map",
  "tag:yaml.org,2002:null",
  "tag:yaml.org,2002:seq",
  "tag:yaml.org,2002:str",
  "tag:yaml.org,2002:timestamp",
  "tag:yaml.org,2002:timestamp#ymd"
]).freeze
VERSION =
"1.0.2"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.check_libyaml_versionObject



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/safe_yaml/load.rb', line 9

def self.check_libyaml_version
  if YAML_ENGINE == "psych" && (LIBYAML_VERSION.nil? || LIBYAML_VERSION < "0.1.6")
    Kernel.warn <<-EOWARNING.gsub(/^ +/, '  ')

      \e[33mSafeYAML Warning\e[39m
      \e[33m----------------\e[39m

      \e[31mYou appear to have an outdated version of libyaml (#{LIBYAML_VERSION}) installed on your system.\e[39m

      Prior to 0.1.6, libyaml is vulnerable to a heap overflow exploit from malicious YAML payloads.

      For more info, see:
      https://www.ruby-lang.org/en/news/2014/03/29/heap-overflow-in-yaml-uri-escape-parsing-cve-2014-2525/

      The easiest thing to do right now is probably to update Psych to the latest version and enable
      the 'bundled-libyaml' option, which will install a vendored libyaml with the vulnerability patched:

      \e[32mgem install psych -- --enable-bundled-libyaml\e[39m

    EOWARNING
  end
end

.load(yaml, options = {}) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/safe_yaml/load.rb', line 159

def self.load(yaml, filename=nil, options={})
  # If the user hasn't whitelisted any tags, we can go with this implementation which is
  # significantly faster.
  if (options && options[:whitelisted_tags] || SafeYAML::OPTIONS[:whitelisted_tags]).empty?
    safe_handler = SafeYAML::PsychHandler.new(options) do |result|
      return result
    end
    arguments_for_parse = [yaml]
    arguments_for_parse << filename if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
    Psych::Parser.new(safe_handler).parse(*arguments_for_parse)
    return safe_handler.result

  else
    safe_resolver = SafeYAML::PsychResolver.new(options)
    tree = SafeYAML::MULTI_ARGUMENT_YAML_LOAD ?
      Psych.parse(yaml, filename) :
      Psych.parse(yaml)
    return safe_resolver.resolve_node(tree)
  end
end

.load_file(filename, options = {}) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
# File 'lib/safe_yaml/load.rb', line 180

def self.load_file(filename, options={})
  if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
    File.open(filename, 'r:bom|utf-8') { |f| self.load(f, filename, options) }

  else
    # Ruby pukes on 1.9.2 if we try to open an empty file w/ 'r:bom|utf-8';
    # so we'll not specify those flags here. This mirrors the behavior for
    # unsafe_load_file so it's probably preferable anyway.
    self.load File.open(filename), nil, options
  end
end

.restore_defaults!Object



87
88
89
# File 'lib/safe_yaml/load.rb', line 87

def restore_defaults!
  OPTIONS.clear.merge!(Deep.copy(DEFAULT_OPTIONS))
end

.tag_safety_check!(tag, options) ⇒ Object



91
92
93
94
95
96
# File 'lib/safe_yaml/load.rb', line 91

def tag_safety_check!(tag, options)
  return if tag.nil? || tag == "!"
  if options[:raise_on_unknown_tag] && !options[:whitelisted_tags].include?(tag) && !tag_is_explicitly_trusted?(tag)
    raise "Unknown YAML tag '#{tag}'"
  end
end

.whitelist!(*classes) ⇒ Object



98
99
100
101
102
# File 'lib/safe_yaml/load.rb', line 98

def whitelist!(*classes)
  classes.each do |klass|
    whitelist_class!(klass)
  end
end

.whitelist_class!(klass) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/safe_yaml/load.rb', line 104

def whitelist_class!(klass)
  raise "#{klass} not a Class" unless klass.is_a?(::Class)

  klass_name = klass.name
  raise "#{klass} cannot be anonymous" if klass_name.nil? || klass_name.empty?

  # Whitelist any built-in YAML tags supplied by Syck or Psych.
  predefined_tag = PREDEFINED_TAGS[klass]
  if predefined_tag
    OPTIONS[:whitelisted_tags] << predefined_tag
    return
  end

  # Exception is exceptional (har har).
  tag_class  = klass < Exception ? "exception" : "object"

  tag_prefix = case YAML_ENGINE
               when "psych" then "!ruby/#{tag_class}"
               when "syck"  then "tag:ruby.yaml.org,2002:#{tag_class}"
               else raise "unknown YAML_ENGINE #{YAML_ENGINE}"
               end
  OPTIONS[:whitelisted_tags] << "#{tag_prefix}:#{klass_name}"
end

Instance Method Details

#tag_is_explicitly_trusted?(tag) ⇒ Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/safe_yaml/load.rb', line 129

def tag_is_explicitly_trusted?(tag)
  false
end