site-inspector 1.0.2 → 2.0.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +3 -0
  6. data/Guardfile +8 -0
  7. data/README.md +175 -0
  8. data/Rakefile +8 -0
  9. data/bin/site-inspector +48 -21
  10. data/lib/site-inspector.rb +38 -613
  11. data/lib/site-inspector/cache.rb +9 -52
  12. data/lib/site-inspector/checks/check.rb +41 -0
  13. data/lib/site-inspector/checks/content.rb +67 -0
  14. data/lib/site-inspector/checks/dns.rb +129 -0
  15. data/lib/site-inspector/checks/headers.rb +83 -0
  16. data/lib/site-inspector/checks/hsts.rb +78 -0
  17. data/lib/site-inspector/checks/https.rb +40 -0
  18. data/lib/site-inspector/checks/sniffer.rb +42 -0
  19. data/lib/site-inspector/disk_cache.rb +38 -0
  20. data/lib/site-inspector/domain.rb +248 -0
  21. data/lib/site-inspector/endpoint.rb +200 -0
  22. data/lib/site-inspector/rails_cache.rb +11 -0
  23. data/lib/site-inspector/version.rb +3 -0
  24. data/script/bootstrap +1 -0
  25. data/script/cibuild +7 -0
  26. data/script/console +1 -0
  27. data/script/release +38 -0
  28. data/site-inspector.gemspec +33 -0
  29. data/spec/checks/site_inspector_endpoint_check_spec.rb +34 -0
  30. data/spec/checks/site_inspector_endpoint_content_spec.rb +89 -0
  31. data/spec/checks/site_inspector_endpoint_dns_spec.rb +167 -0
  32. data/spec/checks/site_inspector_endpoint_headers_spec.rb +74 -0
  33. data/spec/checks/site_inspector_endpoint_hsts_spec.rb +91 -0
  34. data/spec/checks/site_inspector_endpoint_https_spec.rb +48 -0
  35. data/spec/checks/site_inspector_endpoint_sniffer_spec.rb +52 -0
  36. data/spec/site_inspector_cache_spec.rb +13 -0
  37. data/spec/site_inspector_disc_cache_spec.rb +31 -0
  38. data/spec/site_inspector_domain_spec.rb +252 -0
  39. data/spec/site_inspector_endpoint_spec.rb +224 -0
  40. data/spec/site_inspector_spec.rb +46 -0
  41. data/spec/spec_helper.rb +17 -0
  42. metadata +75 -57
  43. data/lib/site-inspector/compliance.rb +0 -19
  44. data/lib/site-inspector/dns.rb +0 -92
  45. data/lib/site-inspector/headers.rb +0 -59
  46. data/lib/site-inspector/sniffer.rb +0 -26
@@ -0,0 +1,11 @@
1
+ class SiteInspector
2
+ class RailsCache
3
+ def get(request)
4
+ Rails.cache.read(request)
5
+ end
6
+
7
+ def set(request, response)
8
+ Rails.cache.write(request, response)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ class SiteInspector
2
+ VERSION = "2.0.0"
3
+ end
data/script/bootstrap ADDED
@@ -0,0 +1 @@
1
+ bundle install
data/script/cibuild ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ bundle exec rake spec
6
+
7
+ gem build site-inspector.gemspec
data/script/console ADDED
@@ -0,0 +1 @@
1
+ bundle exec pry -r ./lib/site-inspector.rb
data/script/release ADDED
@@ -0,0 +1,38 @@
1
+ #!/bin/sh
2
+ # Tag and push a release.
3
+
4
+ set -e
5
+
6
+ # Make sure we're in the project root.
7
+
8
+ cd $(dirname "$0")/..
9
+
10
+ # Build a new gem archive.
11
+
12
+ rm -rf site-inspector-*.gem
13
+ gem build -q site-inspector.gemspec
14
+
15
+ # Make sure we're on the master branch.
16
+
17
+ (git branch | grep -q '* master') || {
18
+ echo "Only release from the master branch."
19
+ exit 1
20
+ }
21
+
22
+ # Figure out what version we're releasing.
23
+
24
+ tag=v`ls site-inspector-*.gem | sed 's/^site-inspector-\(.*\)\.gem$/\1/'`
25
+
26
+ # Make sure we haven't released this version before.
27
+
28
+ git fetch -t origin
29
+
30
+ (git tag -l | grep -q "$tag") && {
31
+ echo "Whoops, there's already a '${tag}' tag."
32
+ exit 1
33
+ }
34
+
35
+ # Tag it and bag it.
36
+
37
+ gem push site-inspector-*.gem && git tag "$tag" &&
38
+ git push origin master && git push origin "$tag"
@@ -0,0 +1,33 @@
1
+ require File.expand_path "./lib/site-inspector/version", File.dirname(__FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+
5
+ s.name = "site-inspector"
6
+ s.version = SiteInspector::VERSION
7
+ s.summary = "A Ruby port and v2 of Site Inspector (http://github.com/benbalter/site-inspector)"
8
+ s.description = "Returns information about a domain's technology and capabilities"
9
+ s.authors = "Ben Balter"
10
+ s.email = "ben@balter.com"
11
+ s.homepage = "https://github.com/benbalter/site-inspector-ruby"
12
+ s.license = "MIT"
13
+
14
+ s.files = `git ls-files -z`.split("\x0")
15
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_dependency("nokogiri", "~> 1.6")
20
+ s.add_dependency("public_suffix", "~> 1.4")
21
+ s.add_dependency("gman", "~> 4.1")
22
+ s.add_dependency("dnsruby", "~> 1.56")
23
+ s.add_dependency("sniffles", "~> 0.2")
24
+ s.add_dependency("typhoeus", "~> 0.7")
25
+ s.add_dependency("oj", "~> 2.11")
26
+ s.add_dependency("mercenary", "~> 0.3")
27
+ s.add_dependency("colorator", "~> 0.1")
28
+ s.add_development_dependency("pry", "~> 0.10")
29
+ s.add_development_dependency( "rake", "~> 10.4" )
30
+ s.add_development_dependency( "rspec", "~> 3.2")
31
+ s.add_development_dependency( "bundler", "~> 1.6" )
32
+ s.add_development_dependency( "webmock", "~> 1.2" )
33
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::Endpoint::Check do
4
+
5
+ subject do
6
+ stub_request(:get, "http://example.com/").to_return(:status => 200)
7
+ endpoint = SiteInspector::Endpoint.new("http://example.com")
8
+ SiteInspector::Endpoint::Check.new(endpoint)
9
+ end
10
+
11
+ it "returns the endpoint" do
12
+ expect(subject.endpoint.class).to eql(SiteInspector::Endpoint)
13
+ end
14
+
15
+ it "returns the response" do
16
+ expect(subject.response.class).to eql(Typhoeus::Response)
17
+ end
18
+
19
+ it "returns the request" do
20
+ expect(subject.request.class).to eql(Typhoeus::Request)
21
+ end
22
+
23
+ it "returns the host" do
24
+ expect(subject.host).to eql("example.com")
25
+ end
26
+
27
+ it "returns its name" do
28
+ expect(subject.name).to eql(:check)
29
+ end
30
+
31
+ it "returns the instance name" do
32
+ expect(SiteInspector::Endpoint::Check.name).to eql(:check)
33
+ end
34
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::Endpoint::Content do
4
+
5
+ subject do
6
+ body = <<-eos
7
+ <!DOCTYPE html>
8
+ <html>
9
+ <body>
10
+ <h1>Some page</h1>
11
+ </body>
12
+ </html>
13
+ eos
14
+
15
+ stub_request(:get, "http://example.com/").
16
+ to_return(:status => 200, :body => body )
17
+ endpoint = SiteInspector::Endpoint.new("http://example.com")
18
+ SiteInspector::Endpoint::Content.new(endpoint)
19
+ end
20
+
21
+ it "returns the doc" do
22
+ expect(subject.document.class).to eql(Nokogiri::HTML::Document)
23
+ expect(subject.document.css("h1").text).to eql("Some page")
24
+ end
25
+
26
+ it "returns the body" do
27
+ expect(subject.body).to match("<h1>Some page</h1>")
28
+ end
29
+
30
+ it "returns the doctype" do
31
+ expect(subject.doctype).to eql("html")
32
+ end
33
+
34
+ it "knows when robots.txt exists" do
35
+ stub_request(:get, "http://example.com/robots.txt").
36
+ to_return(:status => 200)
37
+ expect(subject.robots_txt?).to eql(true)
38
+ end
39
+
40
+ it "knows when robots.txt doesn't exist" do
41
+ stub_request(:get, "http://example.com/robots.txt").
42
+ to_return(:status => 404)
43
+ expect(subject.robots_txt?).to eql(false)
44
+ end
45
+
46
+ it "knows when sitemap.xml exists" do
47
+ stub_request(:get, "http://example.com/sitemap.xml").
48
+ to_return(:status => 200)
49
+ expect(subject.sitemap_xml?).to eql(true)
50
+ end
51
+
52
+ it "knows when sitemap.xml exists" do
53
+ stub_request(:get, "http://example.com/sitemap.xml").
54
+ to_return(:status => 404)
55
+ expect(subject.sitemap_xml?).to eql(false)
56
+ end
57
+
58
+ it "knows when humans.txt exists" do
59
+ stub_request(:get, "http://example.com/humans.txt").
60
+ to_return(:status => 200)
61
+ expect(subject.humans_txt?).to eql(true)
62
+ end
63
+
64
+ it "knows when humans.txt doesn't exist" do
65
+ stub_request(:get, "http://example.com/humans.txt").
66
+ to_return(:status => 200)
67
+ expect(subject.humans_txt?).to eql(true)
68
+ end
69
+
70
+ context "404s" do
71
+ it "knows when an endpoint returns a proper 404" do
72
+ stub_request(:get, /http\:\/\/example.com\/.*/).
73
+ to_return(:status => 404)
74
+ expect(subject.proper_404s?).to eql(true)
75
+ end
76
+
77
+ it "knows when an endpoint doesn't return a proper 404" do
78
+ stub_request(:get, /http\:\/\/example.com\/[a-z0-9]{32}/i).
79
+ to_return(:status => 200)
80
+ expect(subject.proper_404s?).to eql(false)
81
+ end
82
+
83
+ it "generates a random path" do
84
+ path = subject.send(:random_path)
85
+ expect(path).to match /[a-z0-9]{32}/i
86
+ expect(subject.send(:random_path)).to eql(path)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+ require 'dnsruby'
3
+
4
+ describe SiteInspector::Endpoint::Dns do
5
+
6
+ subject do
7
+ stub_request(:get, "http://github.com/").to_return(:status => 200)
8
+ endpoint = SiteInspector::Endpoint.new("http://github.com")
9
+ SiteInspector::Endpoint::Dns.new(endpoint)
10
+ end
11
+
12
+ it "inits the resolver" do
13
+ expect(SiteInspector::Endpoint::Dns.resolver.class).to eql(Dnsruby::Resolver)
14
+ end
15
+
16
+ # Note: these tests makes external calls
17
+ context "live tests" do
18
+ it "it runs the query" do
19
+ expect(subject.query).not_to be_empty
20
+ end
21
+
22
+ context "resolv" do
23
+ it "returns the IP" do
24
+ expect(subject.ip).to include("192.30.252.")
25
+ end
26
+
27
+ it "returns the hostname" do
28
+ expect(subject.hostname.sld).to eql("github")
29
+
30
+ end
31
+ end
32
+ end
33
+
34
+ context "stubbed tests" do
35
+
36
+ before do
37
+ record = Dnsruby::RR.create :type => "A", :address => "1.2.3.4", :name => "test"
38
+ allow(subject).to receive(:records) { [record] }
39
+ allow(subject).to receive(:query) { [] }
40
+ end
41
+
42
+ it "returns the records" do
43
+ expect(subject.records.count).to eql(1)
44
+ expect(subject.records.first.class).to eql(Dnsruby::RR::IN::A)
45
+ end
46
+
47
+ it "knows if a record exists" do
48
+ expect(subject.has_record?("A")).to eql(true)
49
+ expect(subject.has_record?("CNAME")).to eql(false)
50
+ end
51
+
52
+ it "knows if a domain supports dnssec" do
53
+ expect(subject.dnssec?).to eql(false)
54
+
55
+ # via https://github.com/alexdalitz/dnsruby/blob/master/test/tc_dnskey.rb
56
+ input = "example.com. 86400 IN DNSKEY 256 3 5 ( AQPSKmynfzW4kyBv015MUG2DeIQ3" +
57
+ "Cbl+BBZH4b/0PY1kxkmvHjcZc8no" +
58
+ "kfzj31GajIQKY+5CptLr3buXA10h" +
59
+ "WqTkF7H6RfoRqXQeogmMHfpftf6z" +
60
+ "Mv1LyBUgia7za6ZEzOJBOztyvhjL" +
61
+ "742iU/TpPSEDhm2SNKLijfUppn1U" +
62
+ "aNvv4w== )"
63
+
64
+ record = Dnsruby::RR.create input
65
+ allow(subject).to receive(:records) { [record] }
66
+
67
+ expect(subject.dnssec?).to eql(true)
68
+ end
69
+
70
+ it "knows if a domain supports ipv6" do
71
+ expect(subject.ipv6?).to eql(false)
72
+
73
+ input = {
74
+ :type => "AAAA",
75
+ :name => "test",
76
+ :address => '102:304:506:708:90a:b0c:d0e:ff10'
77
+ }
78
+ record = Dnsruby::RR.create input
79
+ allow(subject).to receive(:records) { [record] }
80
+
81
+ expect(subject.ipv6?).to eql(true)
82
+ end
83
+
84
+ context "hostname detection" do
85
+ it "lists cnames" do
86
+ records = []
87
+
88
+ records.push Dnsruby::RR.create({
89
+ :type => "CNAME",
90
+ :domainname => "example.com",
91
+ :name => "example"
92
+ })
93
+
94
+ records.push Dnsruby::RR.create({
95
+ :type => "CNAME",
96
+ :domainname => "github.com",
97
+ :name => "github"
98
+ })
99
+
100
+ allow(subject).to receive(:records) { records }
101
+
102
+ expect(subject.cnames.count).to eql(2)
103
+ expect(subject.cnames.first.sld).to eql("example")
104
+ end
105
+
106
+ it "knows when a domain doesn't have a cdn" do
107
+ expect(subject.cdn?).to eql(false)
108
+ end
109
+
110
+ it "detects CDNs" do
111
+ records = [Dnsruby::RR.create({
112
+ :type => "CNAME",
113
+ :domainname => "foo.cloudfront.net",
114
+ :name => "example"
115
+ })]
116
+ allow(subject).to receive(:records) { records }
117
+
118
+ expect(subject.send(:detect_by_hostname, "cdn")).to eql(:cloudfront)
119
+ expect(subject.cdn).to eql(:cloudfront)
120
+ expect(subject.cdn?).to eql(true)
121
+ end
122
+
123
+ it "builds that path to a data file" do
124
+ path = subject.send(:data_path, "foo")
125
+ expected = File.expand_path "../../lib/data/foo.yml", File.dirname(__FILE__)
126
+ expect(path).to eql(expected)
127
+ end
128
+
129
+ it "loads data files" do
130
+ data = subject.send(:load_data, "cdn")
131
+ expect(data.keys).to include("cloudfront")
132
+ end
133
+
134
+ it "knows when a domain isn't cloud" do
135
+ expect(subject.cloud?).to eql(false)
136
+ end
137
+
138
+ it "detects cloud providers" do
139
+ records = [Dnsruby::RR.create({
140
+ :type => "CNAME",
141
+ :domainname => "foo.herokuapp.com",
142
+ :name => "example"
143
+ })]
144
+ allow(subject).to receive(:records) { records }
145
+
146
+ expect(subject.send(:detect_by_hostname, "cloud")).to eql(:heroku)
147
+ expect(subject.cloud_provider).to eql(:heroku)
148
+ expect(subject.cloud?).to eql(true)
149
+ end
150
+
151
+ it "knows when a domain doesn't have google apps" do
152
+ expect(subject.google_apps?).to eql(false)
153
+ end
154
+
155
+ it "knows when a domain is using google apps" do
156
+ records = [Dnsruby::RR.create({
157
+ :type => "MX",
158
+ :exchange => "mx1.google.com",
159
+ :name => "example",
160
+ :preference => 10
161
+ })]
162
+ allow(subject).to receive(:records) { records }
163
+ expect(subject.google_apps?).to eql(true)
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::Endpoint::Headers do
4
+
5
+ subject do
6
+ stub_request(:get, "http://example.com/").
7
+ to_return(:status => 200, :headers => { :foo => "bar" } )
8
+ endpoint = SiteInspector::Endpoint.new("http://example.com")
9
+ SiteInspector::Endpoint::Headers.new(endpoint)
10
+ end
11
+
12
+ def stub_header(header, value)
13
+ allow(subject).to receive(:headers) { { header => value } }
14
+ end
15
+
16
+ it "parses the headers" do
17
+ expect(subject.headers.count).to eql(1)
18
+ expect(subject.headers.keys).to include("foo")
19
+ end
20
+
21
+ it "returns a header" do
22
+ expect(subject["foo"]).to eql("bar")
23
+ expect(subject.headers["foo"]).to eql("bar")
24
+ end
25
+
26
+ it "knows the server" do
27
+ stub_header "server", "foo"
28
+ expect(subject.server).to eql("foo")
29
+ end
30
+
31
+ it "knows if a server has an xss protection header" do
32
+ stub_header "x-xss-protection", "foo"
33
+ expect(subject.xss_protection).to eql("foo")
34
+ end
35
+
36
+ it "validates xss-protection" do
37
+ stub_header "x-xss-protection", "foo"
38
+ expect(subject.xss_protection?).to eql(false)
39
+
40
+ stub_header "x-xss-protection", "1; mode=block"
41
+ expect(subject.xss_protection?).to eql(true)
42
+ end
43
+
44
+ it "checks for clickjack proetection" do
45
+ expect(subject.click_jacking_protection?).to eql(false)
46
+ stub_header "x-frame-options", "foo"
47
+ expect(subject.click_jacking_protection).to eql("foo")
48
+ expect(subject.click_jacking_protection?).to eql(true)
49
+ end
50
+
51
+ it "checks for CSP" do
52
+ expect(subject.content_security_policy?).to eql(false)
53
+ stub_header "content-security-policy", "foo"
54
+ expect(subject.content_security_policy).to eql("foo")
55
+ expect(subject.content_security_policy?).to eql(true)
56
+ end
57
+
58
+ it "checks for strict-transport-security" do
59
+ expect(subject.strict_transport_security?).to eql(false)
60
+ stub_header "strict-transport-security", "foo"
61
+ expect(subject.strict_transport_security).to eql("foo")
62
+ expect(subject.strict_transport_security?).to eql(true)
63
+ end
64
+
65
+ it "knows if there are cookies" do
66
+ expect(subject.cookies?).to eql(false)
67
+ stub_header "set-cookie", "foo"
68
+ expect(subject.cookies?).to eql(true)
69
+ end
70
+
71
+ it "knows if the cookies are secure" do
72
+
73
+ end
74
+ end