safe_yaml 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -99,14 +99,15 @@ The way that SafeYAML works is by restricting the kinds of objects that can be d
99
99
  - Booleans
100
100
  - Nils
101
101
 
102
- Additionally, deserialization of symbols can be enabled by calling `YAML.enable_symbol_parsing!`.
102
+ Additionally, deserialization of symbols can be enabled by calling `YAML.enable_symbol_parsing!` (for example, in an initializer).
103
103
 
104
104
  Known Issues
105
105
  ------------
106
106
 
107
107
  Also note that some Ruby libraries, particularly those requiring inter-process communication, leverage YAML's object deserialization functionality and therefore may break or otherwise be impacted by SafeYAML. The following list includes known instances of SafeYAML's interaction with other Ruby gems:
108
108
 
109
- - **Guard**: Uses YAML as a serialization format for notifications. The data serialized uses symbol keys, so calling `YAML.enable_symbol_parsing!` is necessary to allow Guard to work.
109
+ - [**Guard**](https://github.com/guard/guard): Uses YAML as a serialization format for notifications. The data serialized uses symbolic keys, so calling `YAML.enable_symbol_parsing!` is necessary to allow Guard to work.
110
+ - [**sidekiq**](https://github.com/mperham/sidekiq): Uses a YAML configiuration file with symbolic keys, so calling `YAML.enable_symbol_parsing!` should allow it to work.
110
111
 
111
112
  The above list will grow over time, as more issues are discovered.
112
113
 
@@ -119,3 +120,5 @@ Requirements
119
120
  ------------
120
121
 
121
122
  SafeYAML requires Ruby 1.8.7 or newer and works with both [Syck](http://www.ruby-doc.org/stdlib-1.8.7/libdoc/yaml/rdoc/YAML.html) and [Psych](http://github.com/tenderlove/psych).
123
+
124
+ If you are using a version of Ruby where Psych is the default YAML engine (e.g., 1.9.3) but you want to use Syck, be sure to set `YAML::ENGINE.yamler = "syck"` **before** requiring the safe_yaml gem.
data/lib/safe_yaml.rb CHANGED
@@ -10,6 +10,9 @@ require "safe_yaml/transform"
10
10
  require "safe_yaml/version"
11
11
 
12
12
  module SafeYAML
13
+ MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1
14
+ YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck"
15
+
13
16
  OPTIONS = {
14
17
  :enable_symbol_parsing => false,
15
18
  :enable_arbitrary_object_deserialization => false,
@@ -18,14 +21,11 @@ module SafeYAML
18
21
  end
19
22
 
20
23
  module YAML
21
- def self.load_with_options(yaml, options={})
24
+ def self.load_with_options(yaml, *filename_and_options)
25
+ options = filename_and_options.last || {}
22
26
  safe_mode = safe_mode_from_options("load", options)
23
-
24
27
  arguments = [yaml]
25
- if RUBY_VERSION >= "1.9.3"
26
- arguments << options[:filename]
27
- end
28
-
28
+ arguments << filename_and_options.first if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
29
29
  safe_mode ? safe_load(*arguments) : unsafe_load(*arguments)
30
30
  end
31
31
 
@@ -34,15 +34,16 @@ module YAML
34
34
  safe_mode ? safe_load_file(file) : unsafe_load_file(file)
35
35
  end
36
36
 
37
- def self.load_with_filename_and_options(yaml, filename=nil, options={})
38
- load_with_options(yaml, options.merge(:filename => filename))
39
- end
40
-
41
- if RUBY_VERSION >= "1.9.3"
37
+ if SafeYAML::YAML_ENGINE == "psych"
42
38
  require "safe_yaml/psych_handler"
43
39
  def self.safe_load(yaml, filename=nil)
44
40
  safe_handler = SafeYAML::PsychHandler.new
45
- Psych::Parser.new(safe_handler).parse(yaml, filename)
41
+ parser = Psych::Parser.new(safe_handler)
42
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
43
+ parser.parse(yaml, filename)
44
+ else
45
+ parser.parse(yaml)
46
+ end
46
47
  return safe_handler.result
47
48
  end
48
49
 
@@ -51,25 +52,13 @@ module YAML
51
52
  end
52
53
 
53
54
  def self.unsafe_load_file(filename)
54
- # https://github.com/tenderlove/psych/blob/v1.3.2/lib/psych.rb#L296-298
55
- File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f, filename }
56
- end
57
-
58
- elsif RUBY_VERSION == "1.9.2"
59
- require "safe_yaml/psych_handler"
60
- def self.safe_load(yaml)
61
- safe_handler = SafeYAML::PsychHandler.new
62
- Psych::Parser.new(safe_handler).parse(yaml)
63
- return safe_handler.result
64
- end
65
-
66
- def self.safe_load_file(filename)
67
- File.open(filename, 'r:bom|utf-8') { |f| self.safe_load f }
68
- end
69
-
70
- def self.unsafe_load_file(filename)
71
- # https://github.com/tenderlove/psych/blob/v1.2.0/lib/psych.rb#L228-230
72
- File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f }
55
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
56
+ # https://github.com/tenderlove/psych/blob/v1.3.2/lib/psych.rb#L296-298
57
+ File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f, filename }
58
+ else
59
+ # https://github.com/tenderlove/psych/blob/v1.2.2/lib/psych.rb#L231-233
60
+ self.unsafe_load File.open(filename)
61
+ end
73
62
  end
74
63
 
75
64
  else
@@ -92,13 +81,7 @@ module YAML
92
81
 
93
82
  class << self
94
83
  alias_method :unsafe_load, :load
95
-
96
- if RUBY_VERSION >= "1.9.3"
97
- alias_method :load, :load_with_filename_and_options
98
- else
99
- alias_method :load, :load_with_options
100
- end
101
-
84
+ alias_method :load, :load_with_options
102
85
  alias_method :load_file, :load_file_with_options
103
86
 
104
87
  def enable_symbol_parsing?
@@ -124,18 +107,18 @@ module YAML
124
107
  def disable_arbitrary_object_deserialization!
125
108
  SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] = false
126
109
  end
127
- end
128
110
 
129
- private
130
- def self.safe_mode_from_options(method, options={})
131
- safe_mode = options[:safe]
111
+ private
112
+ def safe_mode_from_options(method, options={})
113
+ safe_mode = options[:safe]
132
114
 
133
- if safe_mode.nil?
134
- mode = SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] ? "unsafe" : "safe"
135
- Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{mode} mode." unless SafeYAML::OPTIONS[:suppress_warnings]
136
- safe_mode = !SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
137
- end
115
+ if safe_mode.nil?
116
+ mode = SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] ? "unsafe" : "safe"
117
+ Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{mode} mode." unless SafeYAML::OPTIONS[:suppress_warnings]
118
+ safe_mode = !SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
119
+ end
138
120
 
139
- safe_mode
121
+ safe_mode
122
+ end
140
123
  end
141
124
  end
@@ -1,4 +1,5 @@
1
1
  require "psych"
2
+ require "base64"
2
3
 
3
4
  module SafeYAML
4
5
  class PsychHandler < Psych::Handler
@@ -13,8 +14,8 @@ module SafeYAML
13
14
  @result
14
15
  end
15
16
 
16
- def add_to_current_structure(value, anchor=nil, quoted=nil)
17
- value = Transform.to_proper_type(value, quoted)
17
+ def add_to_current_structure(value, anchor=nil, quoted=nil, tag=nil)
18
+ value = Transform.to_proper_type(value, quoted, tag)
18
19
 
19
20
  @anchors[anchor] = value if anchor
20
21
 
@@ -62,7 +63,7 @@ module SafeYAML
62
63
  end
63
64
 
64
65
  def scalar(value, anchor, tag, plain, quoted, style)
65
- add_to_current_structure(value, anchor, quoted)
66
+ add_to_current_structure(value, anchor, quoted, tag)
66
67
  end
67
68
 
68
69
  def start_mapping(anchor, tag, implicit, style)
@@ -41,7 +41,7 @@ module SafeYAML
41
41
  end
42
42
 
43
43
  def resolve_scalar(node)
44
- Transform.to_proper_type(node.value, QUOTE_STYLES.include?(node.instance_variable_get(:@style)))
44
+ Transform.to_proper_type(node.value, QUOTE_STYLES.include?(node.instance_variable_get(:@style)), node.type_id)
45
45
  end
46
46
  end
47
47
  end
@@ -1,3 +1,5 @@
1
+ require 'base64'
2
+
1
3
  module SafeYAML
2
4
  class Transform
3
5
  TRANSFORMERS = [
@@ -10,7 +12,7 @@ module SafeYAML
10
12
  Transform::ToTime.new
11
13
  ]
12
14
 
13
- def self.to_proper_type(value, quoted=false)
15
+ def self.to_guessed_type(value, quoted=false)
14
16
  return value if quoted
15
17
 
16
18
  if value.is_a?(String)
@@ -22,5 +24,16 @@ module SafeYAML
22
24
 
23
25
  value
24
26
  end
27
+
28
+ def self.to_proper_type(value, quoted=false,tag=nil)
29
+ case tag
30
+ when "tag:yaml.org,2002:binary"
31
+ return Base64.decode64(value)
32
+ when "x-private:binary"
33
+ return Base64.decode64(value)
34
+ else
35
+ return self.to_guessed_type(value, quoted)
36
+ end
37
+ end
25
38
  end
26
39
  end
@@ -1,3 +1,3 @@
1
1
  module SafeYAML
2
- VERSION = "0.6.3"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -6,10 +6,16 @@ rvm use 1.8.7@safe_yaml
6
6
  rake spec
7
7
 
8
8
  rvm use 1.9.2@safe_yaml
9
- rake spec
9
+ YAMLER=syck rake spec
10
10
 
11
11
  rvm use 1.9.3@safe_yaml
12
- rake spec
12
+ YAMLER=syck rake spec
13
+
14
+ rvm use 1.9.2@safe_yaml
15
+ YAMLER=psych rake spec
16
+
17
+ rvm use 1.9.3@safe_yaml
18
+ YAMLER=psych rake spec
13
19
 
14
20
  rvm use 2.0.0@safe_yaml
15
- rake spec
21
+ YAMLER=psych rake spec
@@ -1,6 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), "spec_helper")
2
2
 
3
- if RUBY_VERSION >= "1.9.2"
3
+ if SafeYAML::YAML_ENGINE == "psych"
4
4
  require "safe_yaml/psych_handler"
5
5
 
6
6
  describe SafeYAML::PsychHandler do
@@ -16,7 +16,7 @@ describe YAML do
16
16
  end
17
17
 
18
18
  describe "unsafe_load" do
19
- if RUBY_VERSION >= "1.9.3"
19
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
20
20
  it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
21
21
  backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
22
22
  backdoor.should be_exploited_through_setter
@@ -45,6 +45,33 @@ describe YAML do
45
45
  object.should_not be_a(ExploitableBackDoor)
46
46
  end
47
47
 
48
+ context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
49
+ if SafeYAML::YAML_ENGINE == "psych"
50
+ let(:arguments) {
51
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
52
+ ["foo: bar", nil]
53
+ else
54
+ ["foo: bar"]
55
+ end
56
+ }
57
+ it "uses Psych internally to parse YAML" do
58
+ stub_parser = stub(Psych::Parser)
59
+ Psych::Parser.stub(:new).and_return(stub_parser)
60
+ stub_parser.should_receive(:parse).with(*arguments)
61
+ # This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
62
+ YAML.safe_load(*arguments)
63
+ end
64
+ end
65
+
66
+ if SafeYAML::YAML_ENGINE == "syck"
67
+ it "uses Syck internally to parse YAML" do
68
+ YAML.should_receive(:parse).with("foo: bar")
69
+ # This won't work now; we just want to ensure YAML::parse was in fact called.
70
+ YAML.safe_load("foo: bar") rescue nil
71
+ end
72
+ end
73
+ end
74
+
48
75
  it "loads a plain ol' YAML document just fine" do
49
76
  result = YAML.safe_load <<-YAML.unindent
50
77
  foo:
@@ -76,6 +103,30 @@ describe YAML do
76
103
  result.should == [{}, {}, {}]
77
104
  end
78
105
 
106
+ it "works for YAML documents with binary tagged keys" do
107
+ result = YAML.safe_load <<-YAML
108
+ ? !!binary >
109
+ Zm9v
110
+ : "bar"
111
+ ? !!binary >
112
+ YmFy
113
+ : "baz"
114
+ YAML
115
+
116
+ result.should == {"foo" => "bar", "bar" => "baz"}
117
+ end
118
+
119
+ it "works for YAML documents with binary tagged values" do
120
+ result = YAML.safe_load <<-YAML
121
+ "foo": !!binary >
122
+ YmFy
123
+ "bar": !!binary >
124
+ YmF6
125
+ YAML
126
+
127
+ result.should == {"foo" => "bar", "bar" => "baz"}
128
+ end
129
+
79
130
  it "works for YAML documents with sections" do
80
131
  result = YAML.safe_load <<-YAML
81
132
  mysql: &mysql
@@ -134,12 +185,14 @@ describe YAML do
134
185
  end
135
186
 
136
187
  describe "unsafe_load_file" do
137
- if RUBY_VERSION >= "1.9.3"
188
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
138
189
  it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
139
190
  backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
140
191
  backdoor.should be_exploited_through_setter
141
192
  end
193
+ end
142
194
 
195
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.2"
143
196
  it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
144
197
  backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
145
198
  backdoor.should be_exploited_through_init_with
@@ -166,7 +219,7 @@ describe YAML do
166
219
 
167
220
  describe "load" do
168
221
  let (:arguments) {
169
- if RUBY_VERSION >= "1.9.3"
222
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
170
223
  ["foo: bar", nil]
171
224
  else
172
225
  ["foo: bar"]
data/spec/shared_specs.rb CHANGED
@@ -161,6 +161,7 @@ module SharedSpecs
161
161
  end
162
162
  end
163
163
 
164
+ # This does in fact appear to be a Ruby version thing as opposed to a YAML engine thing.
164
165
  context "for Ruby version #{RUBY_VERSION}" do
165
166
  if RUBY_VERSION >= "1.9.2"
166
167
  it "translates valid time values" do
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,12 @@ ROOT = File.join(HERE, "..")
4
4
  $LOAD_PATH << File.join(ROOT, "lib")
5
5
  $LOAD_PATH << File.join(HERE, "support")
6
6
 
7
+ if ENV["YAMLER"]
8
+ require "yaml"
9
+ YAML::ENGINE.yamler = ENV["YAMLER"]
10
+ puts "Running specs in Ruby #{RUBY_VERSION} with '#{YAML::ENGINE.yamler}' YAML engine."
11
+ end
12
+
7
13
  require "safe_yaml"
8
14
  require "heredoc_unindent"
9
15
 
@@ -1,6 +1,6 @@
1
1
  require File.join(File.dirname(__FILE__), "spec_helper")
2
2
 
3
- if RUBY_VERSION < "1.9.2"
3
+ if SafeYAML::YAML_ENGINE == "syck"
4
4
  require "safe_yaml/syck_resolver"
5
5
 
6
6
  describe SafeYAML::SyckResolver do
@@ -1,7 +1,7 @@
1
1
  require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
2
 
3
3
  describe SafeYAML::Transform::ToTime do
4
- # YAML don't parse times prior to 1.9.
4
+ # It seems both Psych and Syck parse times starting w/ Ruby 1.9.2.
5
5
  if RUBY_VERSION >= "1.9.2"
6
6
  it "returns true when the value matches a valid Time" do
7
7
  subject.transform?("2013-01-01 10:00:00")[0].should == true
metadata CHANGED
@@ -1,23 +1,32 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: safe_yaml
3
- version: !ruby/object:Gem::Version
4
- version: 0.6.3
3
+ version: !ruby/object:Gem::Version
4
+ hash: 3
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 7
9
+ - 0
10
+ version: 0.7.0
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Dan Tao
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2013-02-06 00:00:00.000000000 Z
17
+
18
+ date: 2013-02-08 00:00:00 Z
13
19
  dependencies: []
14
- description: Parse YAML safely, without that pesky arbitrary object deserialization
15
- vulnerability
20
+
21
+ description: Parse YAML safely, without that pesky arbitrary object deserialization vulnerability
16
22
  email: daniel.tao@gmail.com
17
23
  executables: []
24
+
18
25
  extensions: []
26
+
19
27
  extra_rdoc_files: []
20
- files:
28
+
29
+ files:
21
30
  - .gitignore
22
31
  - .travis.yml
23
32
  - Gemfile
@@ -52,32 +61,41 @@ files:
52
61
  - spec/transform/to_symbol_spec.rb
53
62
  - spec/transform/to_time_spec.rb
54
63
  homepage: http://dtao.github.com/safe_yaml/
55
- licenses:
64
+ licenses:
56
65
  - MIT
57
66
  post_install_message:
58
67
  rdoc_options: []
59
- require_paths:
68
+
69
+ require_paths:
60
70
  - lib
61
- required_ruby_version: !ruby/object:Gem::Requirement
71
+ required_ruby_version: !ruby/object:Gem::Requirement
62
72
  none: false
63
- requirements:
64
- - - ! '>='
65
- - !ruby/object:Gem::Version
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 57
77
+ segments:
78
+ - 1
79
+ - 8
80
+ - 7
66
81
  version: 1.8.7
67
- required_rubygems_version: !ruby/object:Gem::Requirement
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
83
  none: false
69
- requirements:
70
- - - ! '>='
71
- - !ruby/object:Gem::Version
72
- version: '0'
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
73
91
  requirements: []
92
+
74
93
  rubyforge_project:
75
- rubygems_version: 1.8.24
94
+ rubygems_version: 1.8.25
76
95
  signing_key:
77
96
  specification_version: 3
78
- summary: SameYAML provides an alternative implementation of YAML.load suitable for
79
- accepting user input in Ruby applications.
80
- test_files:
97
+ summary: SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.
98
+ test_files:
81
99
  - spec/exploit.1.9.2.yaml
82
100
  - spec/exploit.1.9.3.yaml
83
101
  - spec/psych_handler_spec.rb