spektr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yaml +32 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +3 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +134 -0
  9. data/Guardfile +45 -0
  10. data/LICENSE.txt +27 -0
  11. data/README.md +70 -0
  12. data/Rakefile +10 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/bin/spektr +7 -0
  16. data/lib/spektr/app.rb +209 -0
  17. data/lib/spektr/checks/base.rb +151 -0
  18. data/lib/spektr/checks/basic_auth.rb +27 -0
  19. data/lib/spektr/checks/basic_auth_timing.rb +24 -0
  20. data/lib/spektr/checks/command_injection.rb +48 -0
  21. data/lib/spektr/checks/content_tag_xss.rb +54 -0
  22. data/lib/spektr/checks/cookie_serialization.rb +21 -0
  23. data/lib/spektr/checks/create_with.rb +27 -0
  24. data/lib/spektr/checks/csrf.rb +25 -0
  25. data/lib/spektr/checks/csrf_setting.rb +39 -0
  26. data/lib/spektr/checks/default_routes.rb +43 -0
  27. data/lib/spektr/checks/deserialize.rb +62 -0
  28. data/lib/spektr/checks/detailed_exceptions.rb +29 -0
  29. data/lib/spektr/checks/digest_dos.rb +28 -0
  30. data/lib/spektr/checks/dynamic_finders.rb +26 -0
  31. data/lib/spektr/checks/evaluation.rb +25 -0
  32. data/lib/spektr/checks/file_access.rb +38 -0
  33. data/lib/spektr/checks/file_disclosure.rb +25 -0
  34. data/lib/spektr/checks/filter_skipping.rb +29 -0
  35. data/lib/spektr/checks/header_dos.rb +20 -0
  36. data/lib/spektr/checks/i18n_xss.rb +20 -0
  37. data/lib/spektr/checks/json_encoding.rb +23 -0
  38. data/lib/spektr/checks/json_entity_escape.rb +30 -0
  39. data/lib/spektr/checks/json_parsing.rb +47 -0
  40. data/lib/spektr/checks/link_to_href.rb +35 -0
  41. data/lib/spektr/checks/mass_assignment.rb +42 -0
  42. data/lib/spektr/checks/send.rb +24 -0
  43. data/lib/spektr/checks/sqli.rb +52 -0
  44. data/lib/spektr/checks/xss.rb +49 -0
  45. data/lib/spektr/checks.rb +9 -0
  46. data/lib/spektr/cli.rb +53 -0
  47. data/lib/spektr/erubi.rb +78 -0
  48. data/lib/spektr/exp/assignment.rb +20 -0
  49. data/lib/spektr/exp/base.rb +32 -0
  50. data/lib/spektr/exp/const.rb +7 -0
  51. data/lib/spektr/exp/definition.rb +32 -0
  52. data/lib/spektr/exp/ivasign.rb +7 -0
  53. data/lib/spektr/exp/lvasign.rb +7 -0
  54. data/lib/spektr/exp/send.rb +135 -0
  55. data/lib/spektr/exp/xstr.rb +12 -0
  56. data/lib/spektr/processors/base.rb +80 -0
  57. data/lib/spektr/processors/class_processor.rb +25 -0
  58. data/lib/spektr/targets/base.rb +119 -0
  59. data/lib/spektr/targets/config.rb +6 -0
  60. data/lib/spektr/targets/controller.rb +74 -0
  61. data/lib/spektr/targets/model.rb +6 -0
  62. data/lib/spektr/targets/routes.rb +38 -0
  63. data/lib/spektr/targets/view.rb +34 -0
  64. data/lib/spektr/version.rb +3 -0
  65. data/lib/spektr/warning.rb +23 -0
  66. data/lib/spektr.rb +120 -0
  67. data/spektr.gemspec +49 -0
  68. metadata +362 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1fc431414443b9c71ebb40106d82714215ba142d1226b3580e324faf786f86ad
4
+ data.tar.gz: d768f7a18250b9ad59f6331cf65a03fadd38a8501b2f24489a139c62c33d0730
5
+ SHA512:
6
+ metadata.gz: 463d421a7a50013946fee4768b474fe2f0222522cd472351c2cb7a1a8d61a618c653944507fc7e101c606350a51731a30ec6d42a363da5e6b96d597035700ed9
7
+ data.tar.gz: 185c2584b1dba4e5a81fbf6c31b9ae029c2292eedd6c8366d1bb89eb146f02329810465306c2e0bc99482782d3fbe81782f36511c57c52a21341e8d26bd647ca
@@ -0,0 +1,32 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+
6
+ name: CI
7
+
8
+ on:
9
+ push:
10
+ branches: [ master ]
11
+ pull_request:
12
+ branches: [ master ]
13
+
14
+ jobs:
15
+ test:
16
+
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ ruby: [2.7, 3.0]
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ - name: Set up Ruby
25
+ uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
26
+ with:
27
+ bundler-cache: true
28
+ ruby-version: ${{ matrix.ruby }}
29
+ - name: Install dependencies
30
+ run: bundle install
31
+ - name: Run tests
32
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .byebug_history
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.2
6
+ before_install: gem install bundler -v 2.1.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # Change Log
2
+
3
+ ## Unreleased
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at molnargerg@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in spektr.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,134 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ spektr (0.1.0)
5
+ activesupport (~> 6.1.0)
6
+ erubi
7
+ haml (~> 5.1)
8
+ parser (~> 3.0.0)
9
+ pastel
10
+ ruby_parser (~> 3.13)
11
+ tty-color
12
+ tty-option
13
+ tty-spinner
14
+ tty-table
15
+ unparser (~> 0.6.0)
16
+ zeitwerk
17
+
18
+ GEM
19
+ remote: https://rubygems.org/
20
+ specs:
21
+ activesupport (6.1.6)
22
+ concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ i18n (>= 1.6, < 2)
24
+ minitest (>= 5.1)
25
+ tzinfo (~> 2.0)
26
+ zeitwerk (~> 2.3)
27
+ ast (2.4.2)
28
+ byebug (11.1.3)
29
+ coderay (1.1.3)
30
+ concurrent-ruby (1.1.10)
31
+ diff-lcs (1.5.0)
32
+ erubi (1.10.0)
33
+ ffi (1.15.5)
34
+ formatador (0.3.0)
35
+ guard (2.18.0)
36
+ formatador (>= 0.2.4)
37
+ listen (>= 2.7, < 4.0)
38
+ lumberjack (>= 1.0.12, < 2.0)
39
+ nenv (~> 0.1)
40
+ notiffany (~> 0.0)
41
+ pry (>= 0.13.0)
42
+ shellany (~> 0.0)
43
+ thor (>= 0.18.1)
44
+ guard-compat (1.2.1)
45
+ guard-minitest (2.4.6)
46
+ guard-compat (~> 1.2)
47
+ minitest (>= 3.0)
48
+ haml (5.2.2)
49
+ temple (>= 0.8.0)
50
+ tilt
51
+ i18n (1.10.0)
52
+ concurrent-ruby (~> 1.0)
53
+ listen (3.7.1)
54
+ rb-fsevent (~> 0.10, >= 0.10.3)
55
+ rb-inotify (~> 0.9, >= 0.9.10)
56
+ lumberjack (1.2.8)
57
+ method_source (1.0.0)
58
+ minitest (5.15.0)
59
+ nenv (0.3.0)
60
+ notiffany (0.1.3)
61
+ nenv (~> 0.1)
62
+ shellany (~> 0.0)
63
+ parallel (1.21.0)
64
+ parser (3.0.3.2)
65
+ ast (~> 2.4.1)
66
+ pastel (0.8.0)
67
+ tty-color (~> 0.5)
68
+ pry (0.14.1)
69
+ coderay (~> 1.1)
70
+ method_source (~> 1.0)
71
+ rainbow (3.0.0)
72
+ rake (12.3.3)
73
+ rb-fsevent (0.11.0)
74
+ rb-inotify (0.10.1)
75
+ ffi (~> 1.0)
76
+ regexp_parser (2.2.0)
77
+ rexml (3.2.5)
78
+ rubocop (1.24.0)
79
+ parallel (~> 1.10)
80
+ parser (>= 3.0.0.0)
81
+ rainbow (>= 2.2.2, < 4.0)
82
+ regexp_parser (>= 1.8, < 3.0)
83
+ rexml
84
+ rubocop-ast (>= 1.15.0, < 2.0)
85
+ ruby-progressbar (~> 1.7)
86
+ unicode-display_width (>= 1.4.0, < 3.0)
87
+ rubocop-ast (1.15.0)
88
+ parser (>= 3.0.1.1)
89
+ ruby-progressbar (1.11.0)
90
+ ruby_parser (3.19.1)
91
+ sexp_processor (~> 4.16)
92
+ sexp_processor (4.16.1)
93
+ shellany (0.0.1)
94
+ strings (0.2.1)
95
+ strings-ansi (~> 0.2)
96
+ unicode-display_width (>= 1.5, < 3.0)
97
+ unicode_utils (~> 1.4)
98
+ strings-ansi (0.2.0)
99
+ temple (0.8.2)
100
+ thor (1.2.1)
101
+ tilt (2.0.10)
102
+ tty-color (0.6.0)
103
+ tty-cursor (0.7.1)
104
+ tty-option (0.2.0)
105
+ tty-screen (0.8.1)
106
+ tty-spinner (0.9.3)
107
+ tty-cursor (~> 0.7)
108
+ tty-table (0.12.0)
109
+ pastel (~> 0.8)
110
+ strings (~> 0.2.0)
111
+ tty-screen (~> 0.8)
112
+ tzinfo (2.0.4)
113
+ concurrent-ruby (~> 1.0)
114
+ unicode-display_width (2.1.0)
115
+ unicode_utils (1.4.0)
116
+ unparser (0.6.2)
117
+ diff-lcs (~> 1.3)
118
+ parser (>= 3.0.0)
119
+ zeitwerk (2.6.0)
120
+
121
+ PLATFORMS
122
+ ruby
123
+
124
+ DEPENDENCIES
125
+ byebug
126
+ guard
127
+ guard-minitest
128
+ minitest (~> 5.0)
129
+ rake (~> 12.0)
130
+ rubocop
131
+ spektr!
132
+
133
+ BUNDLED WITH
134
+ 2.1.4
data/Guardfile ADDED
@@ -0,0 +1,45 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard :minitest do
19
+ # with Minitest::Unit
20
+ watch(%r{^test/(.*)\/?(.*)_test\.rb$})
21
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m|
22
+ puts "test/#{m[1]}#{m[2]}_test.rb"
23
+ "test/#{m[1]}#{m[2]}_test.rb"
24
+ }
25
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
26
+
27
+ # with Minitest::Spec
28
+ # watch(%r{^spec/(.*)_spec\.rb$})
29
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
30
+ # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
31
+
32
+ # Rails 4
33
+ # watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
34
+ # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
35
+ # watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" }
36
+ # watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
37
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" }
38
+ # watch(%r{^test/.+_test\.rb$})
39
+ # watch(%r{^test/test_helper\.rb$}) { 'test' }
40
+
41
+ # Rails < 4
42
+ # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
43
+ # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" }
44
+ # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
45
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,27 @@
1
+ Spektr Public Use Licence
2
+
3
+ Copyright (c) 2022 Spektr Security LLC
4
+
5
+ Commercial Use of the Software for commercial purposes requires a commercial licence but any non-commercial use is
6
+ allowed free of charge.
7
+
8
+ Commercial use includes offering the software as part of a Software as a Service, or part of a distributed software
9
+ where it is used as part of that software.
10
+
11
+ Non-commercial use includes anything else, for instance when lincecee scans his own codebase for vulnerabilities,
12
+ or as part of a security audit engagement the licencee scans the target's source code for vulnerabilities.
13
+
14
+ Licencee is also permitted to embed the scanner into his own closed source application as long as they don't break
15
+ the non-commercial use requirements of the software.
16
+
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
26
+ Software: Spektr Scanner
27
+ Licensor: Spektr Security LLC
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Spektr
2
+
3
+ [![Ruby CI](https://github.com/gregmolnar/spektr/actions/workflows/ci.yaml/badge.svg?branch=master)](https://github.com/gregmolnar/spektr/actions/workflows/ci.yaml)
4
+
5
+ Spektr is a static-code analyser for Ruby On Rails applications to find security issues.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'spektr'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install spektr
22
+
23
+ ## Usage
24
+
25
+ If you are using in your app:
26
+
27
+ ```
28
+ spektr
29
+ ```
30
+
31
+ If you want to scan an app in another folder:
32
+
33
+ ```
34
+ spektr path/to/app
35
+ ```
36
+
37
+
38
+ ## Development
39
+
40
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
41
+
42
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
43
+
44
+ ## Contributing
45
+
46
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/spektr. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/gregmolnar/spektr/blob/master/CODE_OF_CONDUCT.md).
47
+
48
+
49
+ ## License
50
+
51
+ The gem is available as open source under the terms described in the [licence](https://github.com/gregmolnar/spektr/blob/master/licence.txt). Non-commercial use is free of charge, to obtain a commercial licence, contact us at info[at]spektrhq.com.
52
+
53
+
54
+ ## Code of Conduct
55
+
56
+ Everyone interacting in the Spektr project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/gregmolnar/spektr/blob/master/CODE_OF_CONDUCT.md).
57
+
58
+ ## FAQ
59
+
60
+ ### I use Spektr in my closed-source paid product making millions of dollars, is that non-commercial use?
61
+
62
+ Yes, this is perfectly fine without obtaining a licence. You can however donate to the development here on Github.
63
+
64
+ ### I want to use Spektr in my automated code analyser SaaS, do I need a commercial licence?
65
+
66
+ Yes, plese get in touch at info[at]spektrhq.com and we will work something out.
67
+
68
+ ### I am a penetration tester and I'd like to use Spektr to audit on a paid engagement. Do I need a commercial licence?
69
+
70
+ No. You are free to use it for that purpose, happy bug hunting!
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "spektr"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/spektr ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
3
+ $VERBOSE = nil
4
+ require 'spektr'
5
+ require 'spektr/cli'
6
+
7
+ Spektr::Cli.new.parse.scan
data/lib/spektr/app.rb ADDED
@@ -0,0 +1,209 @@
1
+ module Spektr
2
+ class App
3
+ attr_accessor :root, :checks, :initializers, :controllers, :models, :views, :lib_files, :routes, :warnings, :rails_version,
4
+ :production_config, :gem_specs, :ruby_version
5
+
6
+ def self.parser
7
+ @@parser ||= Parser::CurrentRuby
8
+ end
9
+
10
+ def initialize(checks:, root: './')
11
+ @root = root
12
+ @checks = checks
13
+ @controllers = []
14
+ @models = []
15
+ @warnings = []
16
+ @json_output = {
17
+ app: {},
18
+ advisories: []
19
+ }
20
+ @ruby_version = '2.7.1'
21
+ version_file = File.join(root, '.ruby-version')
22
+ @ruby_version = File.read(version_file).lines.first if File.exist?(version_file)
23
+ case @ruby_version
24
+ when /^2\.0\./
25
+ require 'parser/ruby20'
26
+ @@parser = Parser::Ruby20
27
+ when /^2\.1\./
28
+ require 'parser/ruby21'
29
+ @@parser = Parser::Ruby21
30
+ when /^2\.2\./
31
+ require 'parser/ruby22'
32
+ @@parser = Parser::Ruby22
33
+ when /^2\.3/
34
+ require 'parser/ruby23'
35
+ @@parser = Parser::Ruby23
36
+ when /^2\.4\./
37
+ require 'parser/ruby24'
38
+ @@parser = Parser::Ruby24
39
+ when /^2\.5\./
40
+ require 'parser/ruby25'
41
+ @@parser = Parser::Ruby25
42
+ when /^2\.6\./
43
+ require 'parser/ruby26'
44
+ @@parser = Parser::Ruby26
45
+ when /^2\.7\./
46
+ require 'parser/ruby27'
47
+ @@parser = Parser::Ruby27
48
+ when /^3\.0\./
49
+ require 'parser/ruby30'
50
+ @@parser = Parser::Ruby30
51
+ when /^3\.1\./
52
+ require 'parser/ruby31'
53
+ @@parser = Parser::Ruby31
54
+ when /^3\.2\./
55
+ require 'parser/ruby32'
56
+ @@parser = Parser::Ruby32
57
+ else
58
+ @@parser = Parser::CurrentRuby
59
+ end
60
+ end
61
+
62
+ def load
63
+ loaded_files = []
64
+
65
+ config_path = File.join(@root, 'config', 'environments', 'production.rb')
66
+ if File.exist?(config_path)
67
+ @production_config = Targets::Config.new(config_path,
68
+ File.read(config_path, encoding: 'utf-8'))
69
+ end
70
+
71
+ @initializers = initializer_paths.map do |path|
72
+ loaded_files << path
73
+ Targets::Base.new(path, File.read(path))
74
+ end
75
+ @controllers = controller_paths.map do |path|
76
+ loaded_files << path
77
+ Targets::Controller.new(path, File.read(path, encoding: 'utf-8'))
78
+ end
79
+ @models = model_paths.map do |path|
80
+ loaded_files << path
81
+ Targets::Model.new(path, File.read(path, encoding: 'utf-8'))
82
+ end
83
+ @views = view_paths.map do |path|
84
+ loaded_files << path
85
+ Targets::View.new(path, File.read(path, encoding: 'utf-8'))
86
+ end
87
+ @routes = [File.join(@root, 'config', 'routes.rb')].map do |path|
88
+ next unless File.exist? path
89
+
90
+ loaded_files << path
91
+ Targets::Routes.new(path, File.read(path, encoding: 'utf-8'))
92
+ end.reject(&:nil?)
93
+ # TODO: load non-app lib too
94
+ @lib_files = find_files('lib').map do |path|
95
+ next if loaded_files.include?(path)
96
+
97
+ Targets::Base.new(path, File.read(path, encoding: 'utf-8'))
98
+ end.reject(&:nil?)
99
+ self
100
+ end
101
+
102
+ def scan!
103
+ @checks.each do |check|
104
+ if @controllers
105
+ @controllers.each do |controller|
106
+ check.new(self, controller).run
107
+ end
108
+ end
109
+ if @views
110
+ @views.each do |view|
111
+ check.new(self, view).run
112
+ end
113
+ end
114
+ if @models
115
+ @models.each do |view|
116
+ check.new(self, view).run
117
+ end
118
+ end
119
+ if @routes
120
+ @routes.each do |view|
121
+ check.new(self, view).run
122
+ end
123
+ end
124
+ if @initializers
125
+ @initializers.each do |i|
126
+ check.new(self, i).run
127
+ end
128
+ end
129
+ if @lib_files
130
+ @lib_files.each do |i|
131
+ check.new(self, i).run
132
+ end
133
+ end
134
+
135
+ check.new(self, @production_config).run if @production_config
136
+ end
137
+ self
138
+ end
139
+
140
+ def report(_format = 'terminal')
141
+ @json_output[:app][:rails_version] = @rails_version
142
+ @json_output[:app][:initializers] = @initializers.size
143
+ @json_output[:app][:controllers] = @controllers.size
144
+ @json_output[:app][:models] = @models.size
145
+ @json_output[:app][:views] = @views.size
146
+ @json_output[:app][:routes] = @routes.size
147
+ @json_output[:app][:lib_files] = @lib_files.size
148
+
149
+ @warnings.each do |warning|
150
+ @json_output[:advisories] << {
151
+ name: warning.check.name,
152
+ description: warning.message,
153
+ path: warning.path,
154
+ location: warning.location&.line,
155
+ line: warning.line,
156
+ check: warning.check.class.name
157
+ }
158
+ end
159
+
160
+ @json_output[:summary] = []
161
+ @json_output[:checks] = @checks.collect(&:name)
162
+
163
+ @json_output[:advisories].group_by { |a| a[:name] }.each do |n, i|
164
+ @json_output[:summary] << {
165
+ n => i.size
166
+ }
167
+ end
168
+ @json_output
169
+ end
170
+
171
+ def initializer_paths
172
+ @initializer_paths ||= find_files('config/initializers')
173
+ end
174
+
175
+ def controller_paths
176
+ @controller_paths ||= find_files('app/**/controllers')
177
+ end
178
+
179
+ def model_paths
180
+ @model_paths ||= find_files('app/**/models')
181
+ end
182
+
183
+ def view_paths
184
+ @view_paths ||= find_files('app', "{#{%w[html.erb html.haml rhtml js.erb html.slim].join(',')}}")
185
+ end
186
+
187
+ def find_files(path, extensions = 'rb')
188
+ Dir.glob(File.join(@root, path, '**', "*.#{extensions}"))
189
+ end
190
+
191
+ def gem_specs
192
+ return unless File.exist? "#{@root}/Gemfile.lock"
193
+
194
+ @gem_specs ||= Bundler::LockfileParser.new(Bundler.read_file("#{@root}/Gemfile.lock")).specs
195
+ end
196
+
197
+ def has_gem?(name)
198
+ return false unless gem_specs
199
+
200
+ gem_specs.any? { |spec| spec.name == name }
201
+ end
202
+
203
+ def rails_version
204
+ return unless gem_specs
205
+
206
+ @rails_version ||= Gem::Version.new(gem_specs.find { |spec| spec.name == 'rails' }&.version)
207
+ end
208
+ end
209
+ end