main
rb 80 lines 3.01 KB
Raw
1 # frozen_string_literal: true
2
3 require "rails_helper"
4
5 # The SSRF guard for import-by-URL. These are the checks that stop a user from
6 # pointing the importer at cloud metadata or an internal service.
7 RSpec.describe ImportUrlValidator do
8 describe ".validate!" do
9 it "accepts a public https git URL" do
10 expect(described_class.validate!("https://192.0.2.10/owner/repo.git"))
11 .to eq("https://192.0.2.10/owner/repo.git")
12 end
13
14 it "rejects non-http(s) schemes" do
15 %w[
16 ssh://git@github.com/x.git
17 git://github.com/x.git
18 file:///etc/passwd
19 ftp://example.com/x.git
20 ].each do |url|
21 expect { described_class.validate!(url) }
22 .to raise_error(ImportUrlValidator::InvalidUrl), "expected #{url} to be rejected"
23 end
24 end
25
26 it "rejects the cloud metadata address" do
27 expect { described_class.validate!("http://169.254.169.254/latest/meta-data/") }
28 .to raise_error(ImportUrlValidator::InvalidUrl, /private or internal/)
29 end
30
31 it "rejects loopback and RFC1918 literal IPs" do
32 %w[
33 http://127.0.0.1/x.git
34 http://10.0.0.5/x.git
35 http://192.168.1.1/x.git
36 http://172.16.9.9/x.git
37 ].each do |url|
38 expect { described_class.validate!(url) }
39 .to raise_error(ImportUrlValidator::InvalidUrl), "expected #{url} to be rejected"
40 end
41 end
42
43 it "rejects IPv6 loopback and IPv4-mapped private addresses" do
44 expect { described_class.validate!("http://[::1]/x.git") }
45 .to raise_error(ImportUrlValidator::InvalidUrl)
46 expect { described_class.validate!("http://[::ffff:10.0.0.1]/x.git") }
47 .to raise_error(ImportUrlValidator::InvalidUrl)
48 end
49
50 it "rejects credentials embedded in the URL" do
51 expect { described_class.validate!("https://user:pass@github.com/x.git") }
52 .to raise_error(ImportUrlValidator::InvalidUrl, /credentials/)
53 end
54
55 it "rejects a host that resolves to a blocked address" do
56 allow(described_class).to receive(:resolve).and_return([ "127.0.0.1" ])
57 expect { described_class.validate!("https://internal.example.com/x.git") }
58 .to raise_error(ImportUrlValidator::InvalidUrl, /private or internal/)
59 end
60
61 it "accepts a host that resolves to a public address" do
62 allow(described_class).to receive(:resolve).and_return([ "140.82.112.3" ])
63 expect(described_class.validate!("https://github.com/rails/rails.git"))
64 .to eq("https://github.com/rails/rails.git")
65 end
66
67 it "rejects blank input" do
68 expect { described_class.validate!("") }.to raise_error(ImportUrlValidator::InvalidUrl)
69 expect { described_class.validate!(nil) }.to raise_error(ImportUrlValidator::InvalidUrl)
70 end
71 end
72
73 describe ".safe?" do
74 it "is true/false without raising" do
75 allow(described_class).to receive(:resolve).and_return([ "140.82.112.3" ])
76 expect(described_class.safe?("https://github.com/x.git")).to be(true)
77 expect(described_class.safe?("http://127.0.0.1/x.git")).to be(false)
78 end
79 end
80 end