paraxial 0.3.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea5f2156685b5204495019d6a72ad0f433326438abfef4e70e26b39ab62bf58b
4
- data.tar.gz: 4dd855ada83c429df5422b42177af07863041ef94feb14912375df5f8d18e807
3
+ metadata.gz: fa19e78278421371d0e25cd1e387383676df44a7d09ec936520e6e2e9a4fa70f
4
+ data.tar.gz: 807b6b4401164502c8b38ea4b6ff861c667b0aee5fcd124d68a5f98c19ed5e38
5
5
  SHA512:
6
- metadata.gz: 5f458988de37e4a6c9c0a8f8e1489b613925faf48d11dc59c1addbcca49acc740292cddae12bd675c25c8c640a2f45133e5ed6d9232c8319f74fe868c2e2396e
7
- data.tar.gz: 81504558ae7231a7f5f5397f6ca640faf63532c15686d61942d54a306c0d28b865f3e11e8c17973729a1b200c74ad3246f149ca46a53635e91dfa229fe57a998
6
+ metadata.gz: f99fc3adc1dab0e302e45b26b2a245055b2b03eac517e5640b7f18828de1a78d64362a37811be9221b4f5c560feff727324b171d1c9493609b513de43579780a
7
+ data.tar.gz: fbe25d2549601ab95b67934deda8dd1e070d7ced5cb0abf570f85b8650acc9700d35c4274f3460a25d434a6f09c67ad80b85927564da3ba78a1b1dfa9b9ed2d0
@@ -0,0 +1,138 @@
1
+ require 'rpatricia'
2
+ module Paraxial
3
+ module Checker
4
+ @allows = { 'v4' => Patricia.new, 'v6' => Patricia.new(:AF_INET6) }
5
+ @bans = { 'v4' => Patricia.new, 'v6' => Patricia.new(:AF_INET6) }
6
+
7
+
8
+ if Paraxial::Helpers.get_api_key
9
+ @thread = Thread.new do
10
+ loop do
11
+ get_abr
12
+ sleep(10)
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.get_abr
18
+ uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/abr')
19
+ headers = { 'Content-Type': 'application/json' }
20
+
21
+ body = { api_key: Paraxial::Helpers.get_api_key }
22
+ begin
23
+ r = Net::HTTP.post(uri, body.to_json, headers)
24
+ if r.code == '200'
25
+ put_abr(JSON.parse(r.body))
26
+ else
27
+ 'ab_failed'
28
+ end
29
+ rescue StandardError => e
30
+ puts '[Paraxial] HTTP connection to backend failed, check configuration'
31
+ 'ab_failed'
32
+ end
33
+ end
34
+
35
+ def self.put_abr(abr)
36
+ # Expected input: a hash
37
+ # {"allows"=>[{"address"=>[96, 56, 162, 210], "netmask"=>32}],
38
+ # "bans"=>
39
+ # [{"address"=>[8193, 3512, 34211, 0, 0, 35374, 880, 29492], "netmask"=>128},
40
+ # {"address"=>[111, 56, 162, 210], "netmask"=>32}],
41
+ # "rules"=>[]}
42
+ ipv4_a = []
43
+ ipv4_b = []
44
+ ipv6_a = []
45
+ ipv6_b = []
46
+ abr.each do |key, value|
47
+ next if key == 'rules' # skip rules for now
48
+
49
+ value.each do |ip|
50
+ address = ip['address']
51
+ if address.length == 4
52
+ if key == 'allows'
53
+ ipv4_a << address.join('.')
54
+ elsif key == 'bans'
55
+ ipv4_b << address.join('.')
56
+ end
57
+ elsif key == 'allows'
58
+ ipv6_a << address.map { |n| n.to_s(16).rjust(4, '0') }.join(':')
59
+ elsif key == 'bans'
60
+ ipv6_b << address.map { |n| n.to_s(16).rjust(4, '0') }.join(':')
61
+ end
62
+ end
63
+ end
64
+
65
+ ab = { 'allows' => { 'v4' => ipv4_a, 'v6' => ipv6_a }, 'bans' => { 'v4' => ipv4_b, 'v6' => ipv6_b } }
66
+ Paraxial::Checker.put(ab)
67
+ end
68
+
69
+ def self.put(allow_ban)
70
+ allows = allow_ban['allows']
71
+ bans = allow_ban['bans']
72
+ @allows = { 'v4' => create_patricia(allows['v4'], 'v4'), 'v6' => create_patricia(allows['v6'], 'v6') }
73
+ @bans = { 'v4' => create_patricia(bans['v4'], 'v4'), 'v6' => create_patricia(bans['v6'], 'v6') }
74
+ :ok
75
+ end
76
+
77
+ def self.create_patricia(list, type)
78
+ if type == 'v4'
79
+ p = Patricia.new
80
+ list.each do |ip|
81
+ p.add(ip)
82
+ end
83
+ p
84
+ elsif type == 'v6'
85
+ p = Patricia.new(:AF_INET6)
86
+ list.each do |ip|
87
+ p.add(ip)
88
+ end
89
+ p
90
+ else
91
+ raise 'Wrong type in Paraxial::Checker.create_patricia'
92
+ end
93
+ end
94
+
95
+ def self.ban_ip(ip)
96
+ if ip.include?('.')
97
+ # IPv4
98
+ current_t = @bans['v4']
99
+ current_t.add(ip)
100
+ @bans['v4'] = current_t
101
+ else
102
+ # IPv6
103
+ current_t = @bans['v6']
104
+ current_t.add(ip)
105
+ @bans['v6'] = current_t
106
+ end
107
+
108
+ uri = URI.parse(Paraxial::Helpers.get_ban_url)
109
+ headers = { 'Content-Type': 'application/json' }
110
+
111
+ body = { api_key: Paraxial::Helpers.get_api_key, ip_address: ip }
112
+ r = Net::HTTP.post(uri, body.to_json, headers)
113
+ if r.code == '200'
114
+ :ok
115
+ else
116
+ :error
117
+ end
118
+ end
119
+
120
+ def self.allow_ip?(ip)
121
+ if ip.include?('.')
122
+ if !@allows['v4'].search_best(ip).nil? # v4 on allow list
123
+ true
124
+ elsif !@bans['v4'].search_best(ip).nil? # v4 on ban list
125
+ false
126
+ else # v4 on no list
127
+ true
128
+ end
129
+ elsif !@allows['v6'].search_best(ip).nil? # v6 on allow list
130
+ true
131
+ elsif !@bans['v6'].search_best(ip).nil? # v6 on ban list
132
+ false
133
+ else # v6 on no list
134
+ true
135
+ end
136
+ end
137
+ end
138
+ end
data/lib/paraxial/cli.rb CHANGED
@@ -18,15 +18,8 @@ module Paraxial
18
18
 
19
19
  def scan
20
20
  puts '[Paraxial] Scan starting...'
21
- if check_rubocop_configuration
22
- puts '[Paraxial] .rubocop.yml contains the required paraxial configuration.'
23
- else
24
- puts '[Paraxial] .rubocop.yml does not contain the required paraxial configuration.'
25
- puts '[Paraxial] How to configure: TODO_URL'
26
- exit
27
- end
28
21
 
29
- if ENV['PARAXIAL_API_KEY'].nil?
22
+ if Paraxial::Helpers.get_api_key.nil?
30
23
  puts '[Paraxial] Environment variable PARAXIAL_API_KEY not found'
31
24
  else
32
25
  github_app = options[:github_app]
@@ -36,24 +29,35 @@ module Paraxial
36
29
  pr_number = options[:pr_number]
37
30
 
38
31
  cops = 'Paraxial,Security/Eval,Security/IoMethods,Security/JSONLoad,Security/MarshalLoad,Security/Open,Security/YAMLLoad'
39
- rubocop = `rubocop --only #{cops} --format json`
32
+ rubocop = `rubocop --require paraxial --only #{cops} --disable-pending-cops --format json`
40
33
  lockfile = File.read('./Gemfile.lock')
41
34
  api_key = ENV['PARAXIAL_API_KEY']
42
35
  uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/ruby_scan')
43
36
  headers = { 'Content-Type': 'application/json' }
44
37
 
45
- body = { rubocop:, lockfile:, api_key:, timestamp: Paraxial.get_timestamp }
38
+ body = { rubocop: rubocop, lockfile: lockfile, api_key: api_key, timestamp: Paraxial.get_timestamp }
46
39
  response = Net::HTTP.post(uri, body.to_json, headers)
47
- puts "[Paraxial] scan result: #{response.body}"
40
+ m = JSON.parse(response.body)
41
+ findings = m['ok']['findings']
42
+ puts
43
+ puts "[Paraxial] Scan count #{findings.length}"
44
+ puts
45
+ findings.each do |finding|
46
+ puts finding
47
+ puts
48
+ end
49
+ puts "[Paraxial] Scan UUID #{m['ok']['scan_uuid']}"
50
+ puts "[Paraxial] Scan URL #{m['ok']['scan_url']}"
48
51
  github_valid = (!!github_app and !!install_id and !!repo_owner and !!repo_name and !!pr_number)
49
52
 
50
53
  if github_app and github_valid == false
51
54
  puts '[Paraxial] --github_app missing arguments'
52
55
  puts '[Paraxial] Required: --github_app, --install_id, --repo_owner, --repo_name, --pr_number'
53
56
  elsif github_app and github_valid
54
- uuid_regex = /UUID\s+(\S+)/
55
- match = response.body.match(uuid_regex)
56
- uuid = match[1] if match
57
+ # uuid_regex = /UUID\s+(\S+)/
58
+ # match = response.body.match(uuid_regex)
59
+ # uuid = match[1] if match
60
+ uuid = m['ok']['scan_uuid']
57
61
  if uuid
58
62
  final_uuid = uuid.chomp('.')
59
63
  censored_backend_map = {
@@ -85,21 +89,5 @@ module Paraxial
85
89
  end
86
90
  end
87
91
 
88
- private
89
-
90
- def check_rubocop_configuration
91
- rubocop_file = File.join(Dir.pwd, '.rubocop.yml')
92
-
93
- return false unless File.exist?(rubocop_file)
94
-
95
- config = YAML.load_file(rubocop_file)
96
- required_key = 'require'
97
-
98
- if config.is_a?(Hash) && config[required_key].is_a?(Array)
99
- config[required_key].include?('paraxial')
100
- else
101
- false
102
- end
103
- end
104
92
  end
105
93
  end
@@ -3,5 +3,17 @@ module Paraxial
3
3
  def self.get_paraxial_url
4
4
  @paraxial_url ||= ENV['PARAXIAL_URL'] || 'https://app.paraxial.io/'
5
5
  end
6
+
7
+ def self.get_ban_url
8
+ get_paraxial_url + '/api/ban_ip'
9
+ end
10
+
11
+ def self.get_exploit_url
12
+ get_paraxial_url + '/api/exploit'
13
+ end
14
+
15
+ def self.get_api_key
16
+ @paraxial_api_key ||= ENV['PARAXIAL_API_KEY']
17
+ end
6
18
  end
7
19
  end
@@ -0,0 +1,37 @@
1
+ unless Rails.env.test? || File.basename($0) == 'rake' || defined?(Rails::Generators)
2
+ module Marshal
3
+ class << self
4
+ alias_method :original_load, :load
5
+
6
+ def load(source, proc = nil)
7
+ exg = Paraxial.configuration.exploit_guard
8
+ if [:monitor, :block].include?(exg)
9
+ if source.is_a?(String) && source.match?(/ActionView|Net::BufferedIO|ERB|ActiveSupport/)
10
+ puts "[Paraxial] Exploit Guard triggered, malicious input to Marshal.load"
11
+ puts source
12
+
13
+ m = {
14
+ "api_key" => Paraxial::Helpers.get_api_key,
15
+ "mode" => exg,
16
+ "message" => "Marshal.load exploit behavior detected: #{Base64.encode64(source)}"
17
+ }
18
+ headers = { 'Content-Type': 'application/json' }
19
+ uri = URI.parse(Paraxial::Helpers.get_exploit_url)
20
+ Thread.new do
21
+ Net::HTTP.post(uri, m.to_json, headers)
22
+ end
23
+ if exg == :monitor
24
+ original_load(source, proc)
25
+ else
26
+ :block
27
+ end
28
+ else
29
+ original_load(source, proc)
30
+ end
31
+ else
32
+ original_load(source, proc)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -2,65 +2,75 @@ require 'bundler'
2
2
  require 'paraxial'
3
3
  require 'rpatricia'
4
4
  require_relative '../helpers'
5
+ require_relative '../checker'
5
6
 
6
7
  Bundler.setup
7
8
 
8
- Rails.application.config.to_prepare do
9
- puts '[Paraxial] Init start'
10
- api_key = ENV['PARAXIAL_API_KEY']
9
+ unless Rails.env.test? || File.basename($0) == 'rake' || defined?(Rails::Generators)
10
+ Rails.application.config.to_prepare do
11
+ puts '[Paraxial] Agent starting...'
12
+ api_key = Paraxial::Helpers.get_api_key
11
13
 
12
- if api_key.nil?
13
- puts '[Paraxial] Init PARAXIAL_API_KEY key not set, agent not started'
14
- elsif Rails.env.test?
15
- puts '[Paraxial] Init Test environment detected, agent not started'
16
- else
17
- begin
18
- puts '[Paraxial] Init config valid, agent starting'
19
- deps_and_licenses = []
20
- Bundler.load.specs.each do |spec|
21
- # Print the gem name and license
22
- h = { name: spec.name, version: spec.version.to_s, description: Paraxial.trim_dep(spec.description),
23
- license: spec.license || 'None' }
24
- deps_and_licenses << h
25
- end
26
- deps_and_licenses << { name: 'ruby', version: RUBY_VERSION, description: 'The Ruby Programming Language',
27
- license: 'Ruby' }
28
- uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/ruby_app_lic')
29
- headers = { 'Content-Type': 'application/json' }
14
+ if api_key.nil?
15
+ puts '[Paraxial] PARAXIAL_API_KEY key not set, agent not started'
16
+ elsif Rails.env.test?
17
+ puts '[Paraxial] Test environment detected, agent not started'
18
+ else
19
+ begin
20
+ puts '[Paraxial] API key detected, agent starting'
30
21
 
31
- body = { app_lic: deps_and_licenses, api_key:, timestamp: Paraxial.get_timestamp }
32
- cloud_uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/cloud_ip_list')
33
- response = Net::HTTP.get(cloud_uri)
22
+ Paraxial.check_exploit_guard
34
23
 
35
- Thread.new do
36
- Net::HTTP.post(uri, body.to_json, headers)
37
- end
24
+ deps_and_licenses = []
25
+ Bundler.load.specs.each do |spec|
26
+ # Print the gem name and license
27
+ h = { name: spec.name, version: spec.version.to_s, description: Paraxial.trim_dep(spec.description),
28
+ license: spec.license || 'None' }
29
+ deps_and_licenses << h
30
+ end
31
+ deps_and_licenses << { name: 'ruby', version: RUBY_VERSION, description: 'The Ruby Programming Language',
32
+ license: 'Ruby' }
33
+ uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/ruby_app_lic')
34
+ headers = { 'Content-Type': 'application/json' }
35
+
36
+ body = { app_lic: deps_and_licenses, api_key: api_key, timestamp: Paraxial.get_timestamp }
37
+ cloud_uri = URI.parse(Paraxial::Helpers.get_paraxial_url + '/api/cloud_ip_list')
38
+ response = Net::HTTP.get(cloud_uri)
39
+
40
+ Thread.new do
41
+ Net::HTTP.post(uri, body.to_json, headers)
42
+ end
38
43
 
39
- # https://github.com/jkitching/rpatricia
40
- pt_v4 = Patricia.new
41
- pt_v6 = Patricia.new(:AF_INET6)
42
- cloud_list = JSON.parse(response)
43
- cloud_list.each do |k, v|
44
- if k.include?('::')
45
- pt_v6.add(k, v)
46
- else
47
- pt_v4.add(k, v)
44
+ # https://github.com/jkitching/rpatricia
45
+ pt_v4 = Patricia.new
46
+ pt_v6 = Patricia.new(:AF_INET6)
47
+ cloud_list = JSON.parse(response)
48
+ cloud_list.each do |k, v|
49
+ if k.include?('::')
50
+ pt_v6.add(k, v)
51
+ else
52
+ pt_v4.add(k, v)
53
+ end
48
54
  end
55
+ puts '[Paraxial] Cloud IPs set'
56
+ # puts '[Paraxial] pt_v4.num_nodes'
57
+ # puts pt_v4.num_nodes
58
+ # puts 'pt_v6.num_nodes'
59
+ # puts pt_v6.num_nodes
60
+ PARAXIAL_IPV4 = pt_v4
61
+ PARAXIAL_IPV6 = pt_v6
62
+ # ab = Paraxial.get_abr
63
+ # puts "[Paraxial] Allows/Bans set: #{ab}"
64
+ rescue Errno::ECONNREFUSED => _e
65
+ puts '[Paraxial] Init HTTP request failed, check configuration'
66
+ PARAXIAL_IPV4 = Patricia.new unless defined?(PARAXIAL_IPV4)
67
+ PARAXIAL_IPV6 = Patricia.new(:AF_INET6) unless defined?(PARAXIAL_IPV4)
68
+ rescue StandardError => e
69
+ puts e
70
+ puts '[Paraxial] Init error, check configuration'
71
+ PARAXIAL_IPV4 = Patricia.new unless defined?(PARAXIAL_IPV4)
72
+ PARAXIAL_IPV6 = Patricia.new(:AF_INET6) unless defined?(PARAXIAL_IPV4)
49
73
  end
50
- # puts '[Paraxial] pt_v4.num_nodes'
51
- # puts pt_v4.num_nodes
52
- # puts 'pt_v6.num_nodes'
53
- # puts pt_v6.num_nodes
54
- PARAXIAL_IPV4 = pt_v4
55
- PARAXIAL_IPV6 = pt_v6
56
- rescue Errno::ECONNREFUSED => _e
57
- puts '[Paraxial] Init HTTP request failed, check configuration'
58
- PARAXIAL_IPV4 = Patricia.new
59
- PARAXIAL_IPV6 = Patricia.new(:AF_INET6)
60
- rescue StandardError => _e
61
- puts '[Paraxial] Init error, check configuration'
62
- PARAXIAL_IPV4 = Patricia.new
63
- PARAXIAL_IPV6 = Patricia.new(:AF_INET6)
64
74
  end
65
75
  end
66
76
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paraxial
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.0'
5
5
  end
data/lib/paraxial.rb CHANGED
@@ -9,11 +9,14 @@ require_relative 'rubocop/cop/paraxial/send'
9
9
  require_relative 'rubocop/cop/paraxial/constantize'
10
10
  require_relative 'rubocop/cop/paraxial/html_safe'
11
11
  require_relative 'rubocop/cop/paraxial/sql'
12
- require_relative "paraxial/version"
12
+ require_relative 'paraxial/version'
13
13
  require_relative 'paraxial/cli'
14
14
 
15
-
16
15
  module Paraxial
16
+ class << self
17
+ attr_accessor :configuration
18
+ end
19
+
17
20
  class Error < StandardError; end
18
21
  # Your code goes here...
19
22
 
@@ -27,7 +30,7 @@ module Paraxial
27
30
 
28
31
  if request_path.end_with?('.php')
29
32
  # Return a 404 response if the request path ends with '.php'
30
- [404, { 'Content-Type' => 'text/plain' }, ["Not Found from Paraxial.io"]]
33
+ [404, { 'Content-Type' => 'text/plain' }, ['Not Found from Paraxial.io']]
31
34
  else
32
35
  # Pass the request to the next middleware or the application
33
36
  @app.call(env)
@@ -37,15 +40,27 @@ module Paraxial
37
40
 
38
41
  def self.get_timestamp
39
42
  utc_time = Time.now.utc
40
- utc_time.strftime("%Y-%m-%d %H:%M:%S.%6N") + "Z"
43
+ utc_time.strftime('%Y-%m-%d %H:%M:%S.%6N') + 'Z'
41
44
  end
42
45
 
43
46
  def self.cloud_ip?(ip)
44
- !!(PARAXIAL_IPV4.search_best(ip) or PARAXIAL_IPV6.search_best(ip))
47
+ if ip.include?('.')
48
+ !!PARAXIAL_IPV4.search_best(ip)
49
+ else
50
+ !!PARAXIAL_IPV6.search_best(ip)
51
+ end
52
+ end
53
+
54
+ def self.ban_ip(ip)
55
+ Paraxial::Checker.ban_ip(ip)
56
+ end
57
+
58
+ def self.allow_ip?(ip)
59
+ Paraxial::Checker.allow_ip?(ip)
45
60
  end
46
61
 
47
62
  def self.trim_dep(input)
48
- if input == nil
63
+ if input.nil?
49
64
  nil
50
65
  else
51
66
  cleaned_string = input.gsub(/\n/, '')
@@ -54,11 +69,40 @@ module Paraxial
54
69
  period_index = cleaned_string.index('.')
55
70
 
56
71
  # If there's a period, truncate the string up to that point
57
- if period_index
58
- cleaned_string = cleaned_string[0..period_index]
59
- end
72
+ cleaned_string = cleaned_string[0..period_index] if period_index
60
73
 
61
74
  cleaned_string
62
75
  end
63
76
  end
77
+
78
+ def self.configure
79
+ self.configuration ||= Configuration.new
80
+ yield(configuration) if block_given?
81
+ end
82
+
83
+ def self.check_exploit_guard
84
+ if configuration.nil?
85
+ puts "[Paraxial] Exploit Guard, no config exists, will not run"
86
+ return
87
+ end
88
+
89
+ case configuration.exploit_guard
90
+ when :monitor
91
+ puts "[Paraxial] Exploit Guard, running in monitor mode"
92
+ when :block
93
+ puts "[Paraxial] Exploit Guard, running in block mode"
94
+ when nil
95
+ puts "[Paraxial] Exploit Guard, not configured, will not run"
96
+ else
97
+ puts "[Paraxial] Exploit Guard, bad value"
98
+ end
99
+ end
100
+
101
+ class Configuration
102
+ attr_accessor :exploit_guard
103
+
104
+ def initialize
105
+ @exploit_guard = nil
106
+ end
107
+ end
64
108
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paraxial
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Lubas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-13 00:00:00.000000000 Z
11
+ date: 2024-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -80,9 +80,11 @@ files:
80
80
  - Rakefile
81
81
  - exe/paraxial
82
82
  - lib/paraxial.rb
83
+ - lib/paraxial/checker.rb
83
84
  - lib/paraxial/cli.rb
84
85
  - lib/paraxial/engine.rb
85
86
  - lib/paraxial/helpers.rb
87
+ - lib/paraxial/initializers/marshal_patch.rb
86
88
  - lib/paraxial/initializers/startup.rb
87
89
  - lib/paraxial/version.rb
88
90
  - lib/rubocop/cop/paraxial/constantize.rb