safe_yaml-instructure 0.8.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.
@@ -0,0 +1,3 @@
1
+ module SafeYAML
2
+ VERSION = "0.8.0"
3
+ end
@@ -0,0 +1,74 @@
1
+ module SafeYAML
2
+ class Whitelist
3
+ attr_reader :allowed
4
+
5
+ def initialize
6
+ reset!
7
+ end
8
+
9
+ def check(tag, value)
10
+ @allowed.each do |ok, checker|
11
+ if ok === tag
12
+ check = check_value(ok, checker, value)
13
+ return check if check
14
+ end
15
+ end
16
+ nil
17
+ end
18
+
19
+ def check_value(tag, checker, value)
20
+ if checker == true
21
+ return :cacheable
22
+ end
23
+
24
+ if @cached[tag][value]
25
+ return :cacheable
26
+ end
27
+
28
+ result = checker.call(value)
29
+ if result == :cacheable
30
+ @cached[tag][value] = true
31
+ return :cacheable
32
+ elsif result
33
+ return :allowed
34
+ else
35
+ return nil
36
+ end
37
+ end
38
+
39
+ def reset!
40
+ @allowed = {}
41
+ @cached = {}
42
+ if SafeYAML::YAML_ENGINE == "psych"
43
+ # psych doesn't tag the default types, except for binary
44
+ add("!binary",
45
+ "tag:yaml.org,2002:binary")
46
+ else
47
+ add("tag:yaml.org,2002:str",
48
+ "tag:yaml.org,2002:int",
49
+ "tag:yaml.org,2002:float",
50
+ "tag:yaml.org,2002:binary",
51
+ "tag:yaml.org,2002:merge",
52
+ "tag:yaml.org,2002:null",
53
+ %r{^tag:yaml.org,2002:bool#},
54
+ %r{^tag:yaml.org,2002:float#},
55
+ %r{^tag:yaml.org,2002:timestamp#},
56
+ "tag:ruby.yaml.org,2002:object:YAML::Syck::BadAlias")
57
+ end
58
+ end
59
+
60
+ def add(*tags, &block)
61
+ tags.each do |tag|
62
+ @cached[tag] = {} if block
63
+ @allowed[tag] = block || true
64
+ end
65
+ end
66
+
67
+ def remove(*tags)
68
+ tags.each do |tag|
69
+ @cached.delete(tag)
70
+ @allowed.delete(tag)
71
+ end
72
+ end
73
+ end
74
+ 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,24 @@
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-instructure"
6
+ gem.version = SafeYAML::VERSION
7
+ gem.authors = ["Dan Tao", "Brian Palmer"]
8
+ gem.email = "brianp@instructure.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://github.com/instructure/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
+
19
+ gem.add_development_dependency "hashie"
20
+ gem.add_development_dependency "heredoc_unindent"
21
+ gem.add_development_dependency "rake"
22
+ gem.add_development_dependency "rspec"
23
+ gem.add_development_dependency "travis-lint"
24
+ 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,3 @@
1
+ ---
2
+ a: 1
3
+ b: 2
@@ -0,0 +1,541 @@
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
+ before :each do
15
+ YAML.disable_symbol_parsing!
16
+ end
17
+
18
+ describe "unsafe_load" do
19
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
20
+ it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
21
+ backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
22
+ backdoor.should be_exploited_through_setter
23
+ end
24
+
25
+ it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
26
+ backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
27
+ backdoor.should be_exploited_through_init_with
28
+ end
29
+ end
30
+
31
+ it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
32
+ backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
33
+ backdoor.should be_exploited_through_ivars
34
+ end
35
+
36
+ it "allows exploits through sequenced objects" do
37
+ yaml = <<-YAML.unindent
38
+ ---
39
+ - 1
40
+ - 2
41
+ - !ruby/object:ExploitableBackDoor
42
+ foo: bar
43
+ YAML
44
+ array = YAML.unsafe_load(yaml)
45
+ array[2].should be_exploited_through_ivars
46
+ end
47
+
48
+ it "allows exploits through nested objects" do
49
+ yaml = <<-YAML.unindent
50
+ ---
51
+ a: 1
52
+ b:
53
+ a: 1
54
+ b: !ruby/object:ExploitableBackDoor
55
+ foo: bar
56
+ YAML
57
+ hash = YAML.unsafe_load(yaml)
58
+ hash['b']['b'].should be_exploited_through_ivars
59
+ end
60
+ end
61
+
62
+ describe "safe_load" do
63
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
64
+ expect { YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n") }.to raise_error(SafeYAML::UnsafeTagError)
65
+ end
66
+
67
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
68
+ expect { YAML.safe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n") }.to raise_error(SafeYAML::UnsafeTagError)
69
+ end
70
+
71
+ it "does NOT allow exploits in sequenced objects" do
72
+ yaml = <<-YAML.unindent
73
+ ---
74
+ - 1
75
+ - 2
76
+ - !ruby/object:ExploitableBackDoor
77
+ foo: bar
78
+ YAML
79
+ expect { YAML.safe_load(yaml) }.to raise_error(SafeYAML::UnsafeTagError)
80
+ end
81
+
82
+ it "does NOT allow exploits in nested objects" do
83
+ yaml = <<-YAML.unindent
84
+ ---
85
+ a: 1
86
+ b:
87
+ a: 1
88
+ b: !ruby/object:ExploitableBackDoor
89
+ foo: bar
90
+ YAML
91
+ expect { YAML.safe_load(yaml) }.to raise_error(SafeYAML::UnsafeTagError)
92
+ end
93
+
94
+ context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
95
+ if SafeYAML::YAML_ENGINE == "psych"
96
+ let(:arguments) {
97
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
98
+ ["foo: bar", nil]
99
+ else
100
+ ["foo: bar"]
101
+ end
102
+ }
103
+ it "uses Psych internally to parse YAML" do
104
+ Psych::Parser.any_instance.should_receive(:parse).with(*arguments)
105
+ YAML.should_receive(:unsafe_load)
106
+ # This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
107
+ YAML.safe_load(*arguments)
108
+ end
109
+ end
110
+
111
+ if SafeYAML::YAML_ENGINE == "syck"
112
+ it "uses Syck internally to parse YAML" do
113
+ YAML.should_receive(:parse).with("foo: bar")
114
+ # This won't work now; we just want to ensure YAML::parse was in fact called.
115
+ YAML.safe_load("foo: bar") rescue nil
116
+ end
117
+ end
118
+ end
119
+
120
+ it "loads a plain ol' YAML document just fine" do
121
+ YAML.enable_symbol_parsing!
122
+ result = YAML.safe_load <<-YAML.unindent
123
+ foo:
124
+ number: 1
125
+ float: 1.5
126
+ string: Hello, there!
127
+ symbol: :blah
128
+ quoted_symbol_1: ":blah"
129
+ quoted_symbol_2: ':blah'
130
+ y: true
131
+ n: false
132
+ nada:
133
+ date: 2013-02-14
134
+ time: 2013-02-14 09:49:19.419780000 -07:00
135
+ sequence:
136
+ - hi
137
+ - bye
138
+ YAML
139
+
140
+ result.should == {
141
+ "foo" => {
142
+ "number" => 1,
143
+ "float" => 1.5,
144
+ "string" => "Hello, there!",
145
+ "symbol" => :blah,
146
+ "quoted_symbol_1" => ":blah",
147
+ "quoted_symbol_2" => ":blah",
148
+ "y" => true,
149
+ "n" => false,
150
+ "nada" => nil,
151
+ "date" => YAML.unsafe_load("2013-02-14"),
152
+ "time" => YAML.unsafe_load("2013-02-14 09:49:19.419780000 -07:00"),
153
+ "sequence" => ["hi", "bye"]
154
+ }
155
+ }
156
+ end
157
+
158
+ it "works for YAML documents with anchors and aliases" do
159
+ result = YAML.safe_load <<-YAML
160
+ - &id001 {}
161
+ - *id001
162
+ - *id001
163
+ YAML
164
+
165
+ result.should == [{}, {}, {}]
166
+ end
167
+
168
+ if SafeYAML::YAML_ENGINE == "psych"
169
+ # syck doesn't support !binary
170
+ it "works for YAML documents with binary tagged keys" do
171
+ result = YAML.safe_load <<-YAML
172
+ ? !!binary >
173
+ Zm9v
174
+ : "bar"
175
+ ? !!binary >
176
+ YmFy
177
+ : "baz"
178
+ YAML
179
+
180
+ result.should == {"foo" => "bar", "bar" => "baz"}
181
+ end
182
+
183
+ it "works for YAML documents with binary tagged values" do
184
+ result = YAML.safe_load <<-YAML
185
+ "foo": !!binary >
186
+ YmFy
187
+ "bar": !!binary >
188
+ YmF6
189
+ YAML
190
+
191
+ result.should == {"foo" => "bar", "bar" => "baz"}
192
+ end
193
+
194
+ it "works for YAML documents with binary tagged array values" do
195
+ result = YAML.safe_load <<-YAML
196
+ - !binary |-
197
+ Zm9v
198
+ - !binary |-
199
+ YmFy
200
+ YAML
201
+
202
+ result.should == ["foo", "bar"]
203
+ end
204
+ end
205
+
206
+ it "works for YAML documents with sections" do
207
+ result = YAML.safe_load <<-YAML
208
+ mysql: &mysql
209
+ adapter: mysql
210
+ pool: 30
211
+ login: &login
212
+ username: user
213
+ password: password123
214
+ development: &development
215
+ <<: *mysql
216
+ <<: *login
217
+ host: localhost
218
+ YAML
219
+
220
+ result.should == {
221
+ "mysql" => {
222
+ "adapter" => "mysql",
223
+ "pool" => 30
224
+ },
225
+ "login" => {
226
+ "username" => "user",
227
+ "password" => "password123"
228
+ },
229
+ "development" => {
230
+ "adapter" => "mysql",
231
+ "pool" => 30,
232
+ "username" => "user",
233
+ "password" => "password123",
234
+ "host" => "localhost"
235
+ }
236
+ }
237
+ end
238
+
239
+ it "correctly prefers explicitly defined values over default values from included sections" do
240
+ # Repeating this test 100 times to increase the likelihood of running into an issue caused by
241
+ # non-deterministic hash key enumeration.
242
+ 100.times do
243
+ result = YAML.safe_load <<-YAML
244
+ defaults: &defaults
245
+ foo: foo
246
+ bar: bar
247
+ baz: baz
248
+ custom:
249
+ <<: *defaults
250
+ bar: custom_bar
251
+ baz: custom_baz
252
+ YAML
253
+
254
+ result["custom"].should == {
255
+ "foo" => "foo",
256
+ "bar" => "custom_bar",
257
+ "baz" => "custom_baz"
258
+ }
259
+ end
260
+ end
261
+
262
+ it "works with multi-level inheritance" do
263
+ result = YAML.safe_load <<-YAML
264
+ defaults: &defaults
265
+ foo: foo
266
+ bar: bar
267
+ baz: baz
268
+ custom: &custom
269
+ <<: *defaults
270
+ bar: custom_bar
271
+ baz: custom_baz
272
+ grandcustom: &grandcustom
273
+ <<: *custom
274
+ YAML
275
+
276
+ result.should == {
277
+ "defaults" => { "foo" => "foo", "bar" => "bar", "baz" => "baz" },
278
+ "custom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" },
279
+ "grandcustom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" }
280
+ }
281
+ end
282
+
283
+ context "with custom whitelist defined" do
284
+ class MyClass
285
+ attr_reader :a, :b
286
+ def initialize(a, b)
287
+ @a = a
288
+ @b = b
289
+ end
290
+
291
+ def self.new_from_hash(hash)
292
+ self.new(*hash.values_at('a', 'b'))
293
+ end
294
+ end
295
+
296
+ before :each do
297
+ if SafeYAML::YAML_ENGINE == "psych"
298
+ YAML.whitelist.add('!ruby/object:Set',
299
+ '!map:Hashie::Mash',
300
+ '!ruby/object:MyClass')
301
+ YAML.whitelist.add('!ruby/class') { |val| val == 'A' }
302
+ else
303
+ YAML.whitelist.add('tag:ruby.yaml.org,2002:object:Set',
304
+ 'tag:yaml.org,2002:map:Hashie::Mash',
305
+ 'tag:ruby.yaml.org,2002:object:MyClass')
306
+ YAML.whitelist.add('tag:ruby.yaml.org,2002:class') { |val| val == 'A' }
307
+ end
308
+ end
309
+
310
+ after :each do
311
+ YAML.whitelist.reset!
312
+ end
313
+
314
+ it "will allow array-structure classes via the whitelist" do
315
+ result = YAML.safe_load <<-YAML.unindent
316
+ --- !ruby/object:Set
317
+ hash:
318
+ 1: true
319
+ 2: true
320
+ 3: true
321
+ YAML
322
+
323
+ result.should be_a(Set)
324
+ result.to_a.should =~ [1, 2, 3]
325
+ end
326
+
327
+ it "will allow hash-structure classes via the whitelist" do
328
+ pending("1.9.2 psych doesn't load the map class") if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION == "1.9.2"
329
+ result = YAML.safe_load <<-YAML.unindent
330
+ --- !map:Hashie::Mash
331
+ foo: bar
332
+ YAML
333
+
334
+ result.should be_a(Hashie::Mash)
335
+ result.to_hash.should == { "foo" => "bar" }
336
+
337
+ result = YAML.safe_load <<-YAML.unindent
338
+ --- !ruby/object:MyClass
339
+ a: 1
340
+ b: !ruby/object:MyClass
341
+ a: &70346695583400 !ruby/object:MyClass
342
+ a: 2
343
+ b: *70346695583400
344
+ b: *70346695583400
345
+ YAML
346
+
347
+ result.should be_a(MyClass)
348
+ result.a.should == 1
349
+ result.b.should be_a(MyClass)
350
+ result.b.a.should be_a(MyClass)
351
+ result.b.a.a.should == 2
352
+ result.b.a.b.object_id.should == result.b.a.object_id
353
+ result.b.a.object_id.should == result.b.b.object_id
354
+ end
355
+
356
+ class A; end
357
+ class B; end
358
+
359
+ it "allows a block in the whitelist to determine safety" do
360
+ res = YAML.safe_load <<-YAML.unindent
361
+ ---
362
+ a: !ruby/class A
363
+ YAML
364
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
365
+ res['a'].should == A
366
+ end
367
+
368
+ yaml = <<-YAML.unindent
369
+ ---
370
+ a: !ruby/class A
371
+ b: !ruby/class B
372
+ YAML
373
+
374
+ expect { YAML.safe_load yaml }.to raise_error(SafeYAML::UnsafeTagError)
375
+ end
376
+ end
377
+ end
378
+
379
+ describe "unsafe_load_file" do
380
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
381
+ it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
382
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
383
+ backdoor.should be_exploited_through_setter
384
+ end
385
+ end
386
+
387
+ if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.2"
388
+ it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
389
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
390
+ backdoor.should be_exploited_through_init_with
391
+ end
392
+ end
393
+
394
+ it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
395
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
396
+ backdoor.should be_exploited_through_ivars
397
+ end
398
+ end
399
+
400
+ describe "safe_load_file" do
401
+ it "loads successfully" do
402
+ result = YAML.safe_load_file "spec/ok.yaml"
403
+ result.should == { 'a' => 1, 'b' => 2 }
404
+ end
405
+
406
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
407
+ expect { YAML.safe_load_file "spec/exploit.1.9.3.yaml" }.to raise_error(SafeYAML::UnsafeTagError)
408
+ end
409
+
410
+ it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
411
+ expect { YAML.safe_load_file "spec/exploit.1.9.2.yaml" }.to raise_error(SafeYAML::UnsafeTagError)
412
+ end
413
+ end
414
+
415
+ describe "load" do
416
+ let(:arguments) {
417
+ if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
418
+ ["foo: bar", nil]
419
+ else
420
+ ["foo: bar"]
421
+ end
422
+ }
423
+
424
+ context "with :suppress_warnings set to true" do
425
+ before :each do SafeYAML::OPTIONS[:suppress_warnings] = true; end
426
+ after :each do SafeYAML::OPTIONS[:suppress_warnings] = false; end
427
+
428
+ it "doesn't issue a warning if :suppress_warnings option is set to true" do
429
+ SafeYAML::OPTIONS[:suppress_warnings] = true
430
+ Kernel.should_not_receive(:warn)
431
+ YAML.load(*arguments)
432
+ end
433
+ end
434
+
435
+ it "issues a warning if the :safe option is omitted" do
436
+ silence_warnings do
437
+ Kernel.should_receive(:warn)
438
+ YAML.load(*arguments)
439
+ end
440
+ end
441
+
442
+ it "doesn't issue a warning as long as the :safe option is specified" do
443
+ Kernel.should_not_receive(:warn)
444
+ YAML.load(*(arguments + [{:safe => true}]))
445
+ end
446
+
447
+ it "defaults to safe mode if the :safe option is omitted" do
448
+ silence_warnings do
449
+ YAML.should_receive(:safe_load).with(*arguments)
450
+ YAML.load(*arguments)
451
+ end
452
+ end
453
+
454
+ it "calls #safe_load if the :safe option is set to true" do
455
+ YAML.should_receive(:safe_load).with(*arguments)
456
+ YAML.load(*(arguments + [{:safe => true}]))
457
+ end
458
+
459
+ it "calls #unsafe_load if the :safe option is set to false" do
460
+ YAML.should_receive(:unsafe_load).with(*arguments)
461
+ YAML.load(*(arguments + [{:safe => false}]))
462
+ end
463
+
464
+ context "with arbitrary object deserialization enabled by default" do
465
+ before :each do
466
+ YAML.enable_arbitrary_object_deserialization!
467
+ end
468
+
469
+ after :each do
470
+ YAML.disable_arbitrary_object_deserialization!
471
+ end
472
+
473
+ it "defaults to unsafe mode if the :safe option is omitted" do
474
+ silence_warnings do
475
+ YAML.should_receive(:unsafe_load).with(*arguments)
476
+ YAML.load(*arguments)
477
+ end
478
+ end
479
+
480
+ it "calls #safe_load if the :safe option is set to true" do
481
+ YAML.should_receive(:safe_load).with(*arguments)
482
+ YAML.load(*(arguments + [{:safe => true}]))
483
+ end
484
+ end
485
+ end
486
+
487
+ describe "load_file" do
488
+ let(:filename) { "spec/ok.yaml" } # doesn't really matter
489
+
490
+ it "issues a warning if the :safe option is omitted" do
491
+ silence_warnings do
492
+ Kernel.should_receive(:warn)
493
+ YAML.load_file(filename)
494
+ end
495
+ end
496
+
497
+ it "doesn't issue a warning as long as the :safe option is specified" do
498
+ Kernel.should_not_receive(:warn)
499
+ YAML.load_file(filename, :safe => true)
500
+ end
501
+
502
+ it "defaults to safe mode if the :safe option is omitted" do
503
+ silence_warnings do
504
+ YAML.should_receive(:safe_load_file).with(filename)
505
+ YAML.load_file(filename)
506
+ end
507
+ end
508
+
509
+ it "calls #safe_load_file if the :safe option is set to true" do
510
+ YAML.should_receive(:safe_load_file).with(filename)
511
+ YAML.load_file(filename, :safe => true)
512
+ end
513
+
514
+ it "calls #unsafe_load_file if the :safe option is set to false" do
515
+ YAML.should_receive(:unsafe_load_file).with(filename)
516
+ YAML.load_file(filename, :safe => false)
517
+ end
518
+
519
+ context "with arbitrary object deserialization enabled by default" do
520
+ before :each do
521
+ YAML.enable_arbitrary_object_deserialization!
522
+ end
523
+
524
+ after :each do
525
+ YAML.disable_arbitrary_object_deserialization!
526
+ end
527
+
528
+ it "defaults to unsafe mode if the :safe option is omitted" do
529
+ silence_warnings do
530
+ YAML.should_receive(:unsafe_load_file).with(filename)
531
+ YAML.load_file(filename)
532
+ end
533
+ end
534
+
535
+ it "calls #safe_load if the :safe option is set to true" do
536
+ YAML.should_receive(:safe_load_file).with(filename)
537
+ YAML.load_file(filename, :safe => true)
538
+ end
539
+ end
540
+ end
541
+ end