puppet 3.2.1 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (78) hide show
  1. data/install.rb +1 -1
  2. data/lib/puppet.rb +11 -0
  3. data/lib/puppet/indirector/report/processor.rb +1 -1
  4. data/lib/puppet/indirector/report/rest.rb +7 -0
  5. data/lib/puppet/indirector/resource/rest.rb +8 -0
  6. data/lib/puppet/indirector/rest.rb +80 -52
  7. data/lib/puppet/indirector/run/rest.rb +6 -0
  8. data/lib/puppet/network/formats.rb +20 -10
  9. data/lib/puppet/network/http/handler.rb +1 -1
  10. data/lib/puppet/node.rb +1 -1
  11. data/lib/puppet/node/facts.rb +23 -4
  12. data/lib/puppet/resource.rb +2 -4
  13. data/lib/puppet/resource/status.rb +28 -0
  14. data/lib/puppet/run.rb +24 -2
  15. data/lib/puppet/status.rb +6 -2
  16. data/lib/puppet/transaction/event.rb +19 -0
  17. data/lib/puppet/transaction/report.rb +40 -0
  18. data/lib/puppet/util/log.rb +19 -0
  19. data/lib/puppet/util/metric.rb +6 -0
  20. data/lib/puppet/util/monkey_patches.rb +0 -15
  21. data/lib/puppet/vendor.rb +55 -0
  22. data/lib/puppet/vendor/load_safe_yaml.rb +1 -0
  23. data/lib/puppet/vendor/require_vendored.rb +5 -0
  24. data/lib/puppet/vendor/safe_yaml/CHANGES.md +104 -0
  25. data/lib/puppet/vendor/safe_yaml/Gemfile +11 -0
  26. data/lib/puppet/vendor/safe_yaml/LICENSE.txt +22 -0
  27. data/lib/puppet/vendor/safe_yaml/README.md +179 -0
  28. data/lib/puppet/vendor/safe_yaml/Rakefile +6 -0
  29. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml.rb +253 -0
  30. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/deep.rb +34 -0
  31. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/date.rb +27 -0
  32. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/hexadecimal.rb +12 -0
  33. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/parse/sexagesimal.rb +26 -0
  34. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_handler.rb +92 -0
  35. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/psych_resolver.rb +52 -0
  36. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/resolver.rb +94 -0
  37. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/safe_to_ruby_visitor.rb +17 -0
  38. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_hack.rb +36 -0
  39. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_node_monkeypatch.rb +43 -0
  40. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/syck_resolver.rb +38 -0
  41. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform.rb +41 -0
  42. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_boolean.rb +21 -0
  43. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_date.rb +11 -0
  44. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_float.rb +33 -0
  45. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_integer.rb +25 -0
  46. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_nil.rb +18 -0
  47. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/to_symbol.rb +13 -0
  48. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/transform/transformation_map.rb +47 -0
  49. data/lib/puppet/vendor/safe_yaml/lib/safe_yaml/version.rb +3 -0
  50. data/lib/puppet/vendor/safe_yaml/run_specs_all_ruby_versions.sh +21 -0
  51. data/lib/puppet/vendor/safe_yaml/safe_yaml.gemspec +18 -0
  52. data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.2.yaml +2 -0
  53. data/lib/puppet/vendor/safe_yaml/spec/exploit.1.9.3.yaml +2 -0
  54. data/lib/puppet/vendor/safe_yaml/spec/psych_resolver_spec.rb +10 -0
  55. data/lib/puppet/vendor/safe_yaml/spec/resolver_specs.rb +250 -0
  56. data/lib/puppet/vendor/safe_yaml/spec/safe_yaml_spec.rb +702 -0
  57. data/lib/puppet/vendor/safe_yaml/spec/spec_helper.rb +18 -0
  58. data/lib/puppet/vendor/safe_yaml/spec/support/exploitable_back_door.rb +29 -0
  59. data/lib/puppet/vendor/safe_yaml/spec/syck_resolver_spec.rb +10 -0
  60. data/lib/puppet/vendor/safe_yaml/spec/transform/base64_spec.rb +11 -0
  61. data/lib/puppet/vendor/safe_yaml/spec/transform/to_date_spec.rb +34 -0
  62. data/lib/puppet/vendor/safe_yaml/spec/transform/to_float_spec.rb +42 -0
  63. data/lib/puppet/vendor/safe_yaml/spec/transform/to_integer_spec.rb +59 -0
  64. data/lib/puppet/vendor/safe_yaml/spec/transform/to_symbol_spec.rb +49 -0
  65. data/lib/puppet/vendor/safe_yaml_patches.rb +9 -0
  66. data/lib/puppet/version.rb +1 -1
  67. data/spec/lib/puppet_spec/matchers.rb +8 -0
  68. data/spec/unit/application/facts_spec.rb +1 -0
  69. data/spec/unit/file_serving/metadata_spec.rb +20 -28
  70. data/spec/unit/indirector/report/rest_spec.rb +41 -0
  71. data/spec/unit/indirector/rest_spec.rb +307 -334
  72. data/spec/unit/network/formats_spec.rb +36 -27
  73. data/spec/unit/network/http/handler_spec.rb +3 -12
  74. data/spec/unit/node_spec.rb +14 -0
  75. data/spec/unit/resource_spec.rb +5 -35
  76. data/spec/unit/run_spec.rb +25 -6
  77. data/spec/unit/status_spec.rb +6 -0
  78. metadata +2566 -2521
@@ -0,0 +1,13 @@
1
+ module SafeYAML
2
+ class Transform
3
+ class ToSymbol
4
+ MATCHER = /\A:"?(\w+)"?\Z/.freeze
5
+
6
+ def transform?(value, options=nil)
7
+ options ||= SafeYAML::OPTIONS
8
+ return false unless options[:deserialize_symbols] && MATCHER.match(value)
9
+ return true, $1.to_sym
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ module SafeYAML
2
+ class Transform
3
+ module TransformationMap
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ class CaseAgnosticMap < Hash
9
+ def initialize(*args)
10
+ super
11
+ end
12
+
13
+ def include?(key)
14
+ super(key.downcase)
15
+ end
16
+
17
+ def [](key)
18
+ super(key.downcase)
19
+ end
20
+
21
+ # OK, I actually don't think it's all that important that this map be
22
+ # frozen.
23
+ def freeze
24
+ self
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ def set_predefined_values(predefined_values)
30
+ if SafeYAML::YAML_ENGINE == "syck"
31
+ expanded_map = predefined_values.inject({}) do |hash, (key, value)|
32
+ hash[key] = value
33
+ hash[key.capitalize] = value
34
+ hash[key.upcase] = value
35
+ hash
36
+ end
37
+ else
38
+ expanded_map = CaseAgnosticMap.new
39
+ expanded_map.merge!(predefined_values)
40
+ end
41
+
42
+ self.const_set(:PREDEFINED_VALUES, expanded_map.freeze)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module SafeYAML
2
+ VERSION = "0.9.2"
3
+ end
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+
3
+ [[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"
4
+
5
+ rvm use 1.8.7@safe_yaml
6
+ rake spec
7
+
8
+ rvm use 1.9.2@safe_yaml
9
+ YAMLER=syck rake spec
10
+
11
+ rvm use 1.9.3@safe_yaml
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
19
+
20
+ rvm use 2.0.0@safe_yaml
21
+ YAMLER=psych rake spec
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), "lib", "safe_yaml", "version")
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "safe_yaml"
6
+ gem.version = SafeYAML::VERSION
7
+ gem.authors = "Dan Tao"
8
+ gem.email = "daniel.tao@gmail.com"
9
+ gem.description = %q{Parse YAML safely, without that pesky arbitrary object deserialization vulnerability}
10
+ gem.summary = %q{SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.}
11
+ gem.homepage = "http://dtao.github.com/safe_yaml/"
12
+ gem.license = "MIT"
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.test_files = gem.files.grep(%r{^spec/})
15
+ gem.require_paths = ["lib"]
16
+
17
+ gem.required_ruby_version = ">= 1.8.7"
18
+ end
@@ -0,0 +1,2 @@
1
+ --- !ruby/object:ExploitableBackDoor
2
+ foo: bar
@@ -0,0 +1,2 @@
1
+ --- !ruby/hash:ExploitableBackDoor
2
+ foo: bar
@@ -0,0 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ if SafeYAML::YAML_ENGINE == "psych"
4
+ require "safe_yaml/psych_resolver"
5
+
6
+ describe SafeYAML::PsychResolver do
7
+ include ResolverSpecs
8
+ let(:resolver) { SafeYAML::PsychResolver.new }
9
+ end
10
+ end
@@ -0,0 +1,250 @@
1
+ module ResolverSpecs
2
+ def self.included(base)
3
+ base.module_eval do
4
+ let(:resolver) { nil }
5
+ let(:result) { @result }
6
+
7
+ def parse(yaml)
8
+ tree = YAML.parse(yaml.unindent)
9
+ @result = resolver.resolve_node(tree)
10
+ end
11
+
12
+ # Isn't this how I should've been doing it all along?
13
+ def parse_and_test(yaml)
14
+ parse(yaml)
15
+ @result.should == YAML.unsafe_load(yaml)
16
+ end
17
+
18
+ context "by default" do
19
+ it "translates maps to hashes" do
20
+ parse <<-YAML
21
+ potayto: potahto
22
+ tomayto: tomahto
23
+ YAML
24
+
25
+ result.should == {
26
+ "potayto" => "potahto",
27
+ "tomayto" => "tomahto"
28
+ }
29
+ end
30
+
31
+ it "translates sequences to arrays" do
32
+ parse <<-YAML
33
+ - foo
34
+ - bar
35
+ - baz
36
+ YAML
37
+
38
+ result.should == ["foo", "bar", "baz"]
39
+ end
40
+
41
+ it "translates most values to strings" do
42
+ parse "string: value"
43
+ result.should == { "string" => "value" }
44
+ end
45
+
46
+ it "does not deserialize symbols" do
47
+ parse ":symbol: value"
48
+ result.should == { ":symbol" => "value" }
49
+ end
50
+
51
+ it "translates valid integral numbers to integers" do
52
+ parse "integer: 1"
53
+ result.should == { "integer" => 1 }
54
+ end
55
+
56
+ it "translates valid decimal numbers to floats" do
57
+ parse "float: 3.14"
58
+ result.should == { "float" => 3.14 }
59
+ end
60
+
61
+ it "translates valid dates" do
62
+ parse "date: 2013-01-24"
63
+ result.should == { "date" => Date.parse("2013-01-24") }
64
+ end
65
+
66
+ it "translates valid true/false values to booleans" do
67
+ parse <<-YAML
68
+ - yes
69
+ - true
70
+ - no
71
+ - false
72
+ YAML
73
+
74
+ result.should == [true, true, false, false]
75
+ end
76
+
77
+ it "translates valid nulls to nil" do
78
+ parse <<-YAML
79
+ -
80
+ - ~
81
+ - null
82
+ YAML
83
+
84
+ result.should == [nil] * 3
85
+ end
86
+
87
+ it "matches the behavior of the underlying YAML engine w/ respect to capitalization of boolean values" do
88
+ parse_and_test <<-YAML
89
+ - true
90
+ - True
91
+ - TRUE
92
+ - tRue
93
+ - TRue
94
+ - False
95
+ - FALSE
96
+ - fAlse
97
+ - FALse
98
+ YAML
99
+
100
+ # using Syck: [true, true, true, "tRue", "TRue", false, false, "fAlse", "FALse"]
101
+ # using Psych: all booleans
102
+ end
103
+
104
+ it "matches the behavior of the underlying YAML engine w/ respect to capitalization of nil values" do
105
+ parse_and_test <<-YAML
106
+ - Null
107
+ - NULL
108
+ - nUll
109
+ - NUll
110
+ YAML
111
+
112
+ # using Syck: [nil, nil, "nUll", "NUll"]
113
+ # using Psych: all nils
114
+ end
115
+
116
+ it "translates quoted empty strings to strings (not nil)" do
117
+ parse "foo: ''"
118
+ result.should == { "foo" => "" }
119
+ end
120
+
121
+ it "correctly reverse-translates strings encoded via #to_yaml" do
122
+ parse "5.10".to_yaml
123
+ result.should == "5.10"
124
+ end
125
+
126
+ it "does not specially parse any double-quoted strings" do
127
+ parse <<-YAML
128
+ - "1"
129
+ - "3.14"
130
+ - "true"
131
+ - "false"
132
+ - "2013-02-03"
133
+ - "2013-02-03 16:27:00 -0600"
134
+ YAML
135
+
136
+ result.should == ["1", "3.14", "true", "false", "2013-02-03", "2013-02-03 16:27:00 -0600"]
137
+ end
138
+
139
+ it "does not specially parse any single-quoted strings" do
140
+ parse <<-YAML
141
+ - '1'
142
+ - '3.14'
143
+ - 'true'
144
+ - 'false'
145
+ - '2013-02-03'
146
+ - '2013-02-03 16:27:00 -0600'
147
+ YAML
148
+
149
+ result.should == ["1", "3.14", "true", "false", "2013-02-03", "2013-02-03 16:27:00 -0600"]
150
+ end
151
+
152
+ it "deals just fine with nested maps" do
153
+ parse <<-YAML
154
+ foo:
155
+ bar:
156
+ marco: polo
157
+ YAML
158
+
159
+ result.should == { "foo" => { "bar" => { "marco" => "polo" } } }
160
+ end
161
+
162
+ it "deals just fine with nested sequences" do
163
+ parse <<-YAML
164
+ - foo
165
+ -
166
+ - bar1
167
+ - bar2
168
+ -
169
+ - baz1
170
+ - baz2
171
+ YAML
172
+
173
+ result.should == ["foo", ["bar1", "bar2", ["baz1", "baz2"]]]
174
+ end
175
+
176
+ it "applies the same transformations to keys as to values" do
177
+ parse <<-YAML
178
+ foo: string
179
+ :bar: symbol
180
+ 1: integer
181
+ 3.14: float
182
+ 2013-01-24: date
183
+ YAML
184
+
185
+ result.should == {
186
+ "foo" => "string",
187
+ ":bar" => "symbol",
188
+ 1 => "integer",
189
+ 3.14 => "float",
190
+ Date.parse("2013-01-24") => "date",
191
+ }
192
+ end
193
+
194
+ it "applies the same transformations to elements in sequences as to all values" do
195
+ parse <<-YAML
196
+ - foo
197
+ - :bar
198
+ - 1
199
+ - 3.14
200
+ - 2013-01-24
201
+ YAML
202
+
203
+ result.should == ["foo", ":bar", 1, 3.14, Date.parse("2013-01-24")]
204
+ end
205
+ end
206
+
207
+ context "for Ruby version #{RUBY_VERSION}" do
208
+ it "translates valid time values" do
209
+ parse "time: 2013-01-29 05:58:00 -0800"
210
+ result.should == { "time" => Time.utc(2013, 1, 29, 13, 58, 0) }
211
+ end
212
+
213
+ it "applies the same transformation to elements in sequences" do
214
+ parse "- 2013-01-29 05:58:00 -0800"
215
+ result.should == [Time.utc(2013, 1, 29, 13, 58, 0)]
216
+ end
217
+
218
+ it "applies the same transformation to keys" do
219
+ parse "2013-01-29 05:58:00 -0800: time"
220
+ result.should == { Time.utc(2013, 1, 29, 13, 58, 0) => "time" }
221
+ end
222
+ end
223
+
224
+ context "with symbol deserialization enabled" do
225
+ before :each do
226
+ SafeYAML::OPTIONS[:deserialize_symbols] = true
227
+ end
228
+
229
+ after :each do
230
+ SafeYAML.restore_defaults!
231
+ end
232
+
233
+ it "translates values starting with ':' to symbols" do
234
+ parse "symbol: :value"
235
+ result.should == { "symbol" => :value }
236
+ end
237
+
238
+ it "applies the same transformation to keys" do
239
+ parse ":bar: symbol"
240
+ result.should == { :bar => "symbol" }
241
+ end
242
+
243
+ it "applies the same transformation to elements in sequences" do
244
+ parse "- :bar"
245
+ result.should == [:bar]
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,702 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ require "exploitable_back_door"
4
+
5
+ describe YAML do
6
+ # Essentially stolen from:
7
+ # https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/core_ext/kernel/reporting.rb#L10-25
8
+ def silence_warnings
9
+ $VERBOSE = nil; yield
10
+ ensure
11
+ $VERBOSE = true
12
+ end
13
+
14
+ def safe_load_round_trip(object, options={})
15
+ yaml = object.to_yaml
16
+ if SafeYAML::YAML_ENGINE == "psych"
17
+ YAML.safe_load(yaml, nil, options)
18
+ else
19
+ YAML.safe_load(yaml, options)
20
+ end
21
+ end
22
+
23
+ before :each do
24
+ SafeYAML.restore_defaults!
25
+ end
26
+
27
+ after :each do
28
+ SafeYAML.restore_defaults!
29
+ end
30
+
31
+ describe "unsafe_load" do
32
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
33
+ it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
34
+ backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
35
+ backdoor.should be_exploited_through_setter
36
+ end
37
+
38
+ it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
39
+ backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
40
+ backdoor.should be_exploited_through_init_with
41
+ end
42
+ end
43
+
44
+ it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
45
+ backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
46
+ backdoor.should be_exploited_through_ivars
47
+ end
48
+
49
+ context "with special whitelisted tags defined" do
50
+ before :each do
51
+ SafeYAML::whitelist!(OpenStruct)
52
+ end
53
+
54
+ it "effectively ignores the whitelist (since everything is whitelisted)" do
55
+ result = YAML.unsafe_load <<-YAML.unindent
56
+ --- !ruby/object:OpenStruct
57
+ table:
58
+ :backdoor: !ruby/object:ExploitableBackDoor
59
+ foo: bar
60
+ YAML
61
+
62
+ result.should be_a(OpenStruct)
63
+ result.backdoor.should be_exploited_through_ivars
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "safe_load" do
69
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
70
+ object = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
71
+ object.should_not be_a(ExploitableBackDoor)
72
+ end
73
+
74
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
75
+ object = YAML.safe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
76
+ object.should_not be_a(ExploitableBackDoor)
77
+ end
78
+
79
+ context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
80
+ if SafeYAML::YAML_ENGINE == "psych"
81
+ let(:options) { nil }
82
+ let(:arguments) { ["foo: bar", nil, options] }
83
+
84
+ context "when no tags are whitelisted" do
85
+ it "constructs a SafeYAML::PsychHandler to resolve nodes as they're parsed, for optimal performance" do
86
+ Psych::Parser.should_receive(:new).with an_instance_of(SafeYAML::PsychHandler)
87
+ # This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
88
+ YAML.safe_load(*arguments) rescue nil
89
+ end
90
+ end
91
+
92
+ context "when whitelisted tags are specified" do
93
+ let(:options) {
94
+ { :whitelisted_tags => ["foo"] }
95
+ }
96
+
97
+ it "instead uses Psych to construct a full tree before examining the nodes" do
98
+ Psych.should_receive(:parse)
99
+ # This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
100
+ YAML.safe_load(*arguments) rescue nil
101
+ end
102
+ end
103
+ end
104
+
105
+ if SafeYAML::YAML_ENGINE == "syck"
106
+ it "uses Syck internally to parse YAML" do
107
+ YAML.should_receive(:parse).with("foo: bar")
108
+ # This won't work now; we just want to ensure YAML::parse was in fact called.
109
+ YAML.safe_load("foo: bar") rescue nil
110
+ end
111
+ end
112
+ end
113
+
114
+ it "loads a plain ol' YAML document just fine" do
115
+ result = YAML.safe_load <<-YAML.unindent
116
+ foo:
117
+ number: 1
118
+ string: Hello, there!
119
+ symbol: :blah
120
+ sequence:
121
+ - hi
122
+ - bye
123
+ YAML
124
+
125
+ result.should == {
126
+ "foo" => {
127
+ "number" => 1,
128
+ "string" => "Hello, there!",
129
+ "symbol" => ":blah",
130
+ "sequence" => ["hi", "bye"]
131
+ }
132
+ }
133
+ end
134
+
135
+ it "works for YAML documents with anchors and aliases" do
136
+ result = YAML.safe_load <<-YAML
137
+ - &id001 {}
138
+ - *id001
139
+ - *id001
140
+ YAML
141
+
142
+ result.should == [{}, {}, {}]
143
+ end
144
+
145
+ it "works for YAML documents with binary tagged keys" do
146
+ result = YAML.safe_load <<-YAML
147
+ ? !!binary >
148
+ Zm9v
149
+ : "bar"
150
+ ? !!binary >
151
+ YmFy
152
+ : "baz"
153
+ YAML
154
+
155
+ result.should == {"foo" => "bar", "bar" => "baz"}
156
+ end
157
+
158
+ it "works for YAML documents with binary tagged values" do
159
+ result = YAML.safe_load <<-YAML
160
+ "foo": !!binary >
161
+ YmFy
162
+ "bar": !!binary >
163
+ YmF6
164
+ YAML
165
+
166
+ result.should == {"foo" => "bar", "bar" => "baz"}
167
+ end
168
+
169
+ it "works for YAML documents with binary tagged array values" do
170
+ result = YAML.safe_load <<-YAML
171
+ - !binary |-
172
+ Zm9v
173
+ - !binary |-
174
+ YmFy
175
+ YAML
176
+
177
+ result.should == ["foo", "bar"]
178
+ end
179
+
180
+ it "works for YAML documents with sections" do
181
+ result = YAML.safe_load <<-YAML
182
+ mysql: &mysql
183
+ adapter: mysql
184
+ pool: 30
185
+ login: &login
186
+ username: user
187
+ password: password123
188
+ development: &development
189
+ <<: *mysql
190
+ <<: *login
191
+ host: localhost
192
+ YAML
193
+
194
+ result.should == {
195
+ "mysql" => {
196
+ "adapter" => "mysql",
197
+ "pool" => 30
198
+ },
199
+ "login" => {
200
+ "username" => "user",
201
+ "password" => "password123"
202
+ },
203
+ "development" => {
204
+ "adapter" => "mysql",
205
+ "pool" => 30,
206
+ "username" => "user",
207
+ "password" => "password123",
208
+ "host" => "localhost"
209
+ }
210
+ }
211
+ end
212
+
213
+ it "correctly prefers explicitly defined values over default values from included sections" do
214
+ # Repeating this test 100 times to increase the likelihood of running into an issue caused by
215
+ # non-deterministic hash key enumeration.
216
+ 100.times do
217
+ result = YAML.safe_load <<-YAML
218
+ defaults: &defaults
219
+ foo: foo
220
+ bar: bar
221
+ baz: baz
222
+ custom:
223
+ <<: *defaults
224
+ bar: custom_bar
225
+ baz: custom_baz
226
+ YAML
227
+
228
+ result["custom"].should == {
229
+ "foo" => "foo",
230
+ "bar" => "custom_bar",
231
+ "baz" => "custom_baz"
232
+ }
233
+ end
234
+ end
235
+
236
+ it "works with multi-level inheritance" do
237
+ result = YAML.safe_load <<-YAML
238
+ defaults: &defaults
239
+ foo: foo
240
+ bar: bar
241
+ baz: baz
242
+ custom: &custom
243
+ <<: *defaults
244
+ bar: custom_bar
245
+ baz: custom_baz
246
+ grandcustom: &grandcustom
247
+ <<: *custom
248
+ YAML
249
+
250
+ result.should == {
251
+ "defaults" => { "foo" => "foo", "bar" => "bar", "baz" => "baz" },
252
+ "custom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" },
253
+ "grandcustom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" }
254
+ }
255
+ end
256
+
257
+ it "returns false when parsing an empty document" do
258
+ result = YAML.safe_load ""
259
+ result.should == false
260
+ end
261
+
262
+ context "with custom initializers defined" do
263
+ before :each do
264
+ if SafeYAML::YAML_ENGINE == "psych"
265
+ SafeYAML::OPTIONS[:custom_initializers] = {
266
+ "!set" => lambda { Set.new },
267
+ "!hashiemash" => lambda { Hashie::Mash.new }
268
+ }
269
+ else
270
+ SafeYAML::OPTIONS[:custom_initializers] = {
271
+ "tag:yaml.org,2002:set" => lambda { Set.new },
272
+ "tag:yaml.org,2002:hashiemash" => lambda { Hashie::Mash.new }
273
+ }
274
+ end
275
+ end
276
+
277
+ it "will use a custom initializer to instantiate an array-like class upon deserialization" do
278
+ result = YAML.safe_load <<-YAML.unindent
279
+ --- !set
280
+ - 1
281
+ - 2
282
+ - 3
283
+ YAML
284
+
285
+ result.should be_a(Set)
286
+ result.to_a.should =~ [1, 2, 3]
287
+ end
288
+
289
+ it "will use a custom initializer to instantiate a hash-like class upon deserialization" do
290
+ result = YAML.safe_load <<-YAML.unindent
291
+ --- !hashiemash
292
+ foo: bar
293
+ YAML
294
+
295
+ result.should be_a(Hashie::Mash)
296
+ result.to_hash.should == { "foo" => "bar" }
297
+ end
298
+ end
299
+
300
+ context "with special whitelisted tags defined" do
301
+ before :each do
302
+ SafeYAML::whitelist!(OpenStruct)
303
+
304
+ # Necessary for deserializing OpenStructs properly.
305
+ SafeYAML::OPTIONS[:deserialize_symbols] = true
306
+ end
307
+
308
+ it "will allow objects to be deserialized for whitelisted tags" do
309
+ result = YAML.safe_load("--- !ruby/object:OpenStruct\ntable:\n foo: bar\n")
310
+ result.should be_a(OpenStruct)
311
+ result.instance_variable_get(:@table).should == { "foo" => "bar" }
312
+ end
313
+
314
+ it "will not deserialize objects without whitelisted tags" do
315
+ result = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
316
+ result.should_not be_a(ExploitableBackDoor)
317
+ result.should == { "foo" => "bar" }
318
+ end
319
+
320
+ it "will not allow non-whitelisted objects to be embedded within objects with whitelisted tags" do
321
+ result = YAML.safe_load <<-YAML.unindent
322
+ --- !ruby/object:OpenStruct
323
+ table:
324
+ :backdoor: !ruby/object:ExploitableBackDoor
325
+ foo: bar
326
+ YAML
327
+
328
+ result.should be_a(OpenStruct)
329
+ result.backdoor.should_not be_a(ExploitableBackDoor)
330
+ result.backdoor.should == { "foo" => "bar" }
331
+ end
332
+
333
+ context "with the :raise_on_unknown_tag option enabled" do
334
+ before :each do
335
+ SafeYAML::OPTIONS[:raise_on_unknown_tag] = true
336
+ end
337
+
338
+ after :each do
339
+ SafeYAML.restore_defaults!
340
+ end
341
+
342
+ it "raises an exception if a non-nil, non-whitelisted tag is encountered" do
343
+ lambda {
344
+ YAML.safe_load <<-YAML.unindent
345
+ --- !ruby/object:Unknown
346
+ foo: bar
347
+ YAML
348
+ }.should raise_error
349
+ end
350
+
351
+ it "checks all tags, even those within objects with trusted tags" do
352
+ lambda {
353
+ YAML.safe_load <<-YAML.unindent
354
+ --- !ruby/object:OpenStruct
355
+ table:
356
+ :backdoor: !ruby/object:Unknown
357
+ foo: bar
358
+ YAML
359
+ }.should raise_error
360
+ end
361
+
362
+ it "does not raise an exception as long as all tags are whitelisted" do
363
+ result = YAML.safe_load <<-YAML.unindent
364
+ --- !ruby/object:OpenStruct
365
+ table:
366
+ :backdoor:
367
+ string: foo
368
+ integer: 1
369
+ float: 3.14
370
+ symbol: :bar
371
+ date: 2013-02-20
372
+ array: []
373
+ hash: {}
374
+ YAML
375
+
376
+ result.should be_a(OpenStruct)
377
+ result.backdoor.should == {
378
+ "string" => "foo",
379
+ "integer" => 1,
380
+ "float" => 3.14,
381
+ "symbol" => :bar,
382
+ "date" => Date.parse("2013-02-20"),
383
+ "array" => [],
384
+ "hash" => {}
385
+ }
386
+ end
387
+
388
+ it "does not raise an exception on the non-specific '!' tag" do
389
+ result = nil
390
+ expect { result = YAML.safe_load "--- ! 'foo'" }.to_not raise_error
391
+ result.should == "foo"
392
+ end
393
+
394
+ context "with whitelisted custom class" do
395
+ class SomeClass
396
+ attr_accessor :foo
397
+ end
398
+ let(:instance) { SomeClass.new }
399
+
400
+ before do
401
+ SafeYAML::whitelist!(SomeClass)
402
+ instance.foo = 'with trailing whitespace: '
403
+ end
404
+
405
+ it "does not raise an exception on the non-specific '!' tag" do
406
+ result = nil
407
+ expect { result = YAML.safe_load(instance.to_yaml) }.to_not raise_error
408
+ result.foo.should == 'with trailing whitespace: '
409
+ end
410
+ end
411
+ end
412
+ end
413
+
414
+ context "when options are passed direclty to #load which differ from the defaults" do
415
+ let(:default_options) { {} }
416
+
417
+ before :each do
418
+ SafeYAML::OPTIONS.merge!(default_options)
419
+ end
420
+
421
+ context "(for example, when symbol deserialization is enabled by default)" do
422
+ let(:default_options) { { :deserialize_symbols => true } }
423
+
424
+ it "goes with the default option when it is not overridden" do
425
+ silence_warnings do
426
+ YAML.load(":foo: bar").should == { :foo => "bar" }
427
+ end
428
+ end
429
+
430
+ it "allows the default option to be overridden on a per-call basis" do
431
+ silence_warnings do
432
+ YAML.load(":foo: bar", :deserialize_symbols => false).should == { ":foo" => "bar" }
433
+ YAML.load(":foo: bar", :deserialize_symbols => true).should == { :foo => "bar" }
434
+ end
435
+ end
436
+ end
437
+
438
+ context "(or, for example, when certain tags are whitelisted)" do
439
+ let(:default_options) {
440
+ {
441
+ :deserialize_symbols => true,
442
+ :whitelisted_tags => SafeYAML::YAML_ENGINE == "psych" ?
443
+ ["!ruby/object:OpenStruct"] :
444
+ ["tag:ruby.yaml.org,2002:object:OpenStruct"]
445
+ }
446
+ }
447
+
448
+ it "goes with the default option when it is not overridden" do
449
+ result = safe_load_round_trip(OpenStruct.new(:foo => "bar"))
450
+ result.should be_a(OpenStruct)
451
+ result.foo.should == "bar"
452
+ end
453
+
454
+ it "allows the default option to be overridden on a per-call basis" do
455
+ result = safe_load_round_trip(OpenStruct.new(:foo => "bar"), :whitelisted_tags => [])
456
+ result.should == { "table" => { :foo => "bar" } }
457
+
458
+ result = safe_load_round_trip(OpenStruct.new(:foo => "bar"), :deserialize_symbols => false, :whitelisted_tags => [])
459
+ result.should == { "table" => { ":foo" => "bar" } }
460
+ end
461
+ end
462
+ end
463
+ end
464
+
465
+ describe "unsafe_load_file" do
466
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
467
+ it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
468
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
469
+ backdoor.should be_exploited_through_setter
470
+ end
471
+ end
472
+
473
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.2"
474
+ it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
475
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
476
+ backdoor.should be_exploited_through_init_with
477
+ end
478
+ end
479
+
480
+ it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
481
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
482
+ backdoor.should be_exploited_through_ivars
483
+ end
484
+ end
485
+
486
+ describe "safe_load_file" do
487
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
488
+ object = YAML.safe_load_file "spec/exploit.1.9.3.yaml"
489
+ object.should_not be_a(ExploitableBackDoor)
490
+ end
491
+
492
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
493
+ object = YAML.safe_load_file "spec/exploit.1.9.2.yaml"
494
+ object.should_not be_a(ExploitableBackDoor)
495
+ end
496
+ end
497
+
498
+ describe "load" do
499
+ let(:options) { {} }
500
+
501
+ let (:arguments) {
502
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
503
+ ["foo: bar", nil, options]
504
+ else
505
+ ["foo: bar", options]
506
+ end
507
+ }
508
+
509
+ context "as long as a :default_mode has been specified" do
510
+ it "doesn't issue a warning for safe mode, since an explicit mode has been set" do
511
+ SafeYAML::OPTIONS[:default_mode] = :safe
512
+ Kernel.should_not_receive(:warn)
513
+ YAML.load(*arguments)
514
+ end
515
+
516
+ it "doesn't issue a warning for unsafe mode, since an explicit mode has been set" do
517
+ SafeYAML::OPTIONS[:default_mode] = :unsafe
518
+ Kernel.should_not_receive(:warn)
519
+ YAML.load(*arguments)
520
+ end
521
+ end
522
+
523
+ context "when the :safe options is specified" do
524
+ let(:safe_mode) { true }
525
+ let(:options) { { :safe => safe_mode } }
526
+
527
+ it "doesn't issue a warning" do
528
+ Kernel.should_not_receive(:warn)
529
+ YAML.load(*arguments)
530
+ end
531
+
532
+ it "calls #safe_load if the :safe option is set to true" do
533
+ YAML.should_receive(:safe_load)
534
+ YAML.load(*arguments)
535
+ end
536
+
537
+ context "when the :safe option is set to false" do
538
+ let(:safe_mode) { false }
539
+
540
+ it "calls #unsafe_load if the :safe option is set to false" do
541
+ YAML.should_receive(:unsafe_load)
542
+ YAML.load(*arguments)
543
+ end
544
+ end
545
+ end
546
+
547
+ it "issues a warning when the :safe option is omitted" do
548
+ silence_warnings do
549
+ Kernel.should_receive(:warn)
550
+ YAML.load(*arguments)
551
+ end
552
+ end
553
+
554
+ it "only issues a warning once (to avoid spamming an app's output)" do
555
+ silence_warnings do
556
+ Kernel.should_receive(:warn).once
557
+ 2.times { YAML.load(*arguments) }
558
+ end
559
+ end
560
+
561
+ it "defaults to safe mode if the :safe option is omitted" do
562
+ silence_warnings do
563
+ YAML.should_receive(:safe_load)
564
+ YAML.load(*arguments)
565
+ end
566
+ end
567
+
568
+ context "with the default mode set to :unsafe" do
569
+ before :each do
570
+ SafeYAML::OPTIONS[:default_mode] = :unsafe
571
+ end
572
+
573
+ it "defaults to unsafe mode if the :safe option is omitted" do
574
+ silence_warnings do
575
+ YAML.should_receive(:unsafe_load)
576
+ YAML.load(*arguments)
577
+ end
578
+ end
579
+
580
+ it "calls #safe_load if the :safe option is set to true" do
581
+ YAML.should_receive(:safe_load)
582
+ YAML.load(*(arguments + [{ :safe => true }]))
583
+ end
584
+ end
585
+ end
586
+
587
+ describe "load_file" do
588
+ let(:filename) { "spec/exploit.1.9.2.yaml" } # doesn't really matter
589
+
590
+ it "issues a warning if the :safe option is omitted" do
591
+ silence_warnings do
592
+ Kernel.should_receive(:warn)
593
+ YAML.load_file(filename)
594
+ end
595
+ end
596
+
597
+ it "doesn't issue a warning as long as the :safe option is specified" do
598
+ Kernel.should_not_receive(:warn)
599
+ YAML.load_file(filename, :safe => true)
600
+ end
601
+
602
+ it "defaults to safe mode if the :safe option is omitted" do
603
+ silence_warnings do
604
+ YAML.should_receive(:safe_load_file)
605
+ YAML.load_file(filename)
606
+ end
607
+ end
608
+
609
+ it "calls #safe_load_file if the :safe option is set to true" do
610
+ YAML.should_receive(:safe_load_file)
611
+ YAML.load_file(filename, :safe => true)
612
+ end
613
+
614
+ it "calls #unsafe_load_file if the :safe option is set to false" do
615
+ YAML.should_receive(:unsafe_load_file)
616
+ YAML.load_file(filename, :safe => false)
617
+ end
618
+
619
+ context "with arbitrary object deserialization enabled by default" do
620
+ before :each do
621
+ SafeYAML::OPTIONS[:default_mode] = :unsafe
622
+ end
623
+
624
+ it "defaults to unsafe mode if the :safe option is omitted" do
625
+ silence_warnings do
626
+ YAML.should_receive(:unsafe_load_file)
627
+ YAML.load_file(filename)
628
+ end
629
+ end
630
+
631
+ it "calls #safe_load if the :safe option is set to true" do
632
+ YAML.should_receive(:safe_load_file)
633
+ YAML.load_file(filename, :safe => true)
634
+ end
635
+ end
636
+ end
637
+
638
+ describe "whitelist!" do
639
+ context "not a class" do
640
+ it "should raise" do
641
+ expect { SafeYAML::whitelist! :foo }.to raise_error(/not a Class/)
642
+ SafeYAML::OPTIONS[:whitelisted_tags].should be_empty
643
+ end
644
+ end
645
+
646
+ context "anonymous class" do
647
+ it "should raise" do
648
+ expect { SafeYAML::whitelist! Class.new }.to raise_error(/cannot be anonymous/)
649
+ SafeYAML::OPTIONS[:whitelisted_tags].should be_empty
650
+ end
651
+ end
652
+
653
+ context "with a Class as its argument" do
654
+ it "should configure correctly" do
655
+ expect { SafeYAML::whitelist! OpenStruct }.to_not raise_error
656
+ SafeYAML::OPTIONS[:whitelisted_tags].grep(/OpenStruct\Z/).should_not be_empty
657
+ end
658
+
659
+ it "successfully deserializes the specified class" do
660
+ SafeYAML.whitelist!(OpenStruct)
661
+
662
+ # necessary for properly assigning OpenStruct attributes
663
+ SafeYAML::OPTIONS[:deserialize_symbols] = true
664
+
665
+ result = safe_load_round_trip(OpenStruct.new(:foo => "bar"))
666
+ result.should be_a(OpenStruct)
667
+ result.foo.should == "bar"
668
+ end
669
+
670
+ it "works for ranges" do
671
+ SafeYAML.whitelist!(Range)
672
+ safe_load_round_trip(1..10).should == (1..10)
673
+ end
674
+
675
+ it "works for regular expressions" do
676
+ SafeYAML.whitelist!(Regexp)
677
+ safe_load_round_trip(/foo/).should == /foo/
678
+ end
679
+
680
+ it "works for multiple classes" do
681
+ SafeYAML.whitelist!(Range, Regexp)
682
+ safe_load_round_trip([(1..10), /bar/]).should == [(1..10), /bar/]
683
+ end
684
+
685
+ it "works for arbitrary Exception subclasses" do
686
+ class CustomException < Exception
687
+ attr_reader :custom_message
688
+
689
+ def initialize(custom_message)
690
+ @custom_message = custom_message
691
+ end
692
+ end
693
+
694
+ SafeYAML.whitelist!(CustomException)
695
+
696
+ ex = safe_load_round_trip(CustomException.new("blah"))
697
+ ex.should be_a(CustomException)
698
+ ex.custom_message.should == "blah"
699
+ end
700
+ end
701
+ end
702
+ end