mini_defender 0.6.7 → 0.6.9

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: '0766463845ff12b982d3230cbd2d62a4506d5b470160256a2ec0684146f4e8bc'
4
- data.tar.gz: d24329e0981cb6dabe1d42a85e2c0bec719610a829dc2eb6d2dab480faace668
3
+ metadata.gz: 1a9dd7cd41807156b4aaa18aa7cf645fd00b67ea0dfbd9bc457e12c01bf9c6a3
4
+ data.tar.gz: 9b666b35cbe313a93783609ada72dadffe70feacd7eb22dfba05d958906ac283
5
5
  SHA512:
6
- metadata.gz: 3a232eba0c84024e5248cabcc3f324af4c86956eb494c5f8fb4c1531f76965a32de4b1b674584b1008d4f566b2752712498b600646bf1a1ed94770d5ddeacfa9
7
- data.tar.gz: 5afafaa73bd774d094540de2db462f1b2d0c840bf8fda34a5ff9c9959b0d1d6a9dfd0999343ac14d1ce31f1c94de2ff4a255f6650611fbe4d02cee63eb89f8e8
6
+ metadata.gz: f604e44530496d5f20eddecc82806cd3b52ea66b2158dcb6cb106f7571f26c57153d3bba13cdc8f2a78c63f7dd9deb16132ad1dde220ab1eaded04fa6167afb5
7
+ data.tar.gz: ce4951ee441eaac7eeba302336a9d7403a6cb18aa607784b28702ec02d654dc9b521bc8dde021e75bebc06ab9aad83b477da0aa90ba461514d03f7907fe2d1a3
@@ -0,0 +1,64 @@
1
+ # RFC 2606 - Reserved Domain Names
2
+ # https://datatracker.ietf.org/doc/html/rfc2606
3
+ *.test
4
+ *.example
5
+ *.invalid
6
+ localhost
7
+ *.localhost
8
+ example.com
9
+ example.net
10
+ example.org
11
+
12
+ # RFC 6761 - Special-Use Domain Names
13
+ # https://datatracker.ietf.org/doc/html/rfc6761
14
+ *.local
15
+ *.onion
16
+ *.home.arpa
17
+
18
+ # RFC 1918 - Private Address Space
19
+ # https://datatracker.ietf.org/doc/html/rfc1918
20
+ 10.*
21
+ 172.(1[6-9]|2[0-9]|3[0-1]).*
22
+ 192.168.*
23
+
24
+ # RFC 3330 - Special-Use IPv4 Addresses
25
+ # https://datatracker.ietf.org/doc/html/rfc3330
26
+ 127.*
27
+ 169.254.*
28
+ 0.0.0.0
29
+
30
+ # RFC 4291 - IPv6 Addressing Architecture
31
+ # https://datatracker.ietf.org/doc/html/rfc4291
32
+ ::1
33
+ fe80:*
34
+ ::
35
+ ::ffff:*
36
+
37
+ # RFC 4193 - Unique Local IPv6 Unicast Addresses
38
+ # https://datatracker.ietf.org/doc/html/rfc4193
39
+ fc00:*
40
+ fd00:*
41
+
42
+ # Common Internal Network Patterns (based on RFC 2606 and 6761)
43
+ *.intranet
44
+ *.internal
45
+ *.corp
46
+ *.lan
47
+ intranet.*
48
+ internal.*
49
+ corp.*
50
+ lan.*
51
+
52
+ # Development Environments (based on RFC 2606)
53
+ *.dev.test
54
+ *.staging.test
55
+ *.qa.test
56
+ dev.example.*
57
+ staging.example.*
58
+ qa.example.*
59
+
60
+ # RFC 6052 - IPv6 Translation
61
+ 64:ff9b::*
62
+
63
+ # RFC 3986 - URI Encoded Forms
64
+ %6C%6F%63%61%6C%68%6F%73%74
@@ -6,11 +6,27 @@ class MiniDefender::Rules::Numeric < MiniDefender::Rule
6
6
  end
7
7
 
8
8
  def coerce(value)
9
- value.is_a?(Numeric) ? value : Float(value.to_s)
9
+ if value.is_a?(Numeric)
10
+ return value
11
+ end
12
+
13
+ if value.is_a?(String)
14
+ value = value.gsub(',', '')
15
+ end
16
+
17
+ Float(value.to_s)
10
18
  end
11
19
 
12
20
  def passes?(attribute, value, validator)
13
- value.is_a?(Numeric) || Float(value.to_s) rescue false
21
+ if value.is_a?(Numeric)
22
+ return true
23
+ end
24
+
25
+ if value.is_a?(String)
26
+ value = value.gsub(',', '')
27
+ end
28
+
29
+ Float(value.to_s) rescue false
14
30
  end
15
31
 
16
32
  def message(attribute, value, validator)
@@ -1,17 +1,140 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'uri'
4
+ require 'ipaddr'
5
+ require 'public_suffix'
4
6
 
5
7
  class MiniDefender::Rules::Url < MiniDefender::Rule
8
+ ALLOWED_MODIFIERS = %w[https public not_ip not_private]
9
+
10
+ def initialize(modifiers = [])
11
+ @modifiers = Array(modifiers).map(&:to_s)
12
+
13
+ unless @modifiers.empty?
14
+ validate_modifiers!
15
+ end
16
+
17
+ @validation_error = nil
18
+ end
19
+
6
20
  def self.signature
7
21
  'url'
8
22
  end
9
23
 
24
+ def self.make(modifiers) # no need to raise an error when no modifier is entered; as 'url' rule checks URL structure on its own
25
+ new(modifiers)
26
+ end
27
+
10
28
  def passes?(attribute, value, validator)
11
- value.is_a?(String) && URI.regexp(%w[http https]).match?(value)
29
+ unless value.is_a?(String)
30
+ return false
31
+ end
32
+
33
+ begin
34
+ uri = URI.parse(value)
35
+
36
+ if uri.host.blank? || uri.scheme.blank?
37
+ return false
38
+ end
39
+
40
+ unless %w[http https].include?(uri.scheme)
41
+ @validation_error = 'URL protocol must be HTTP or HTTPS.'
42
+ return false
43
+ end
44
+
45
+ if @modifiers.empty?
46
+ return true
47
+ end
48
+
49
+ if @modifiers.include?('https') && uri.scheme != 'https'
50
+ @validation_error = 'The URL must use HTTPS.'
51
+ return false
52
+ end
53
+
54
+ if @modifiers.include?('public') && (!PublicSuffix.valid?(uri.host) || private_network?(uri.host))
55
+ @validation_error = 'The URL must use a valid public domain.'
56
+ return false
57
+ end
58
+
59
+ if @modifiers.include?('not_ip') && ip_address?(uri.host)
60
+ @validation_error = 'IP addresses are not allowed in URLs.'
61
+ return false
62
+ end
63
+
64
+ if @modifiers.include?('not_private') && private_network?(uri.host)
65
+ @validation_error = 'Private or reserved resources are not allowed.'
66
+ return false
67
+ end
68
+
69
+ true
70
+ rescue URI::InvalidURIError
71
+ @validation_error = 'The field must contain a valid URL.'
72
+ false
73
+ rescue PublicSuffix::Error
74
+ false
75
+ end
12
76
  end
13
77
 
14
78
  def message(attribute, value, validator)
15
- 'The field must contain a valid URL.'
79
+ @validation_error || 'The field must contain a valid URL.'
80
+ end
81
+
82
+ def private_network?(host)
83
+ unless host
84
+ return false
85
+ end
86
+
87
+ host = host.downcase
88
+
89
+ private_patterns.any? { |pattern| pattern.match?(host) }
90
+ end
91
+
92
+ private
93
+
94
+ def validate_modifiers!
95
+ invalid_modifiers = @modifiers - ALLOWED_MODIFIERS
96
+ if invalid_modifiers.empty?
97
+ return
98
+ end
99
+
100
+ raise ArgumentError, "Invalid URL modifiers: #{invalid_modifiers.join(', ')}"
101
+ end
102
+
103
+ def ip_address?(host)
104
+ unless host
105
+ return false
106
+ end
107
+
108
+ begin
109
+ IPAddr.new(host)
110
+ true
111
+ rescue IPAddr::InvalidAddressError
112
+ false
113
+ end
114
+ end
115
+
116
+ def private_patterns
117
+ # Cache the result in class variable to avoid loading again
118
+ # across multiple instances
119
+ @@private_patterns ||= begin
120
+ pattern_file = File.expand_path('../data/private_network_patterns.txt', __dir__)
121
+
122
+ File.readlines(pattern_file).filter_map do |line|
123
+ line = line.strip
124
+
125
+ if line.empty? || line.start_with?('#')
126
+ next
127
+ end
128
+
129
+ # Pattern => regex (once)
130
+ pattern = line
131
+ .gsub('.', '\.') # escape dots
132
+ .gsub('*', '.*') # wildcards => regex
133
+ .gsub('[0-9]+', '\d+') # convert number ranges
134
+ .gsub(/\[(.+?)\]/, '(\1)') # convert chars classes
135
+
136
+ Regexp.new("^#{pattern}$", Regexp::IGNORECASE)
137
+ end
138
+ end
16
139
  end
17
140
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniDefender
4
- VERSION = "0.6.7"
4
+ VERSION = "0.6.9"
5
5
  end
@@ -34,4 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_runtime_dependency 'countries'
35
35
  spec.add_runtime_dependency 'money'
36
36
  spec.add_runtime_dependency 'marcel'
37
+ spec.add_runtime_dependency 'public_suffix'
37
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_defender
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.7
4
+ version: 0.6.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ali Alhoshaiyan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-10 00:00:00.000000000 Z
11
+ date: 2025-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: public_suffix
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  description: A small and efficient validation library for Rails and anything that
98
112
  uses Ruby.
99
113
  email:
@@ -102,7 +116,6 @@ executables: []
102
116
  extensions: []
103
117
  extra_rdoc_files: []
104
118
  files:
105
- - ".DS_Store"
106
119
  - CHANGELOG.md
107
120
  - Gemfile
108
121
  - LICENSE.md
@@ -110,6 +123,7 @@ files:
110
123
  - RULES.md
111
124
  - Rakefile
112
125
  - lib/mini_defender.rb
126
+ - lib/mini_defender/data/private_network_patterns.txt
113
127
  - lib/mini_defender/extensions/enumerable.rb
114
128
  - lib/mini_defender/handles_validation_errors.rb
115
129
  - lib/mini_defender/rule.rb
@@ -181,7 +195,6 @@ files:
181
195
  - lib/mini_defender/rules/national_id.rb
182
196
  - lib/mini_defender/rules/not_ending_with.rb
183
197
  - lib/mini_defender/rules/not_in.rb
184
- - lib/mini_defender/rules/not_local_url.rb
185
198
  - lib/mini_defender/rules/not_regex.rb
186
199
  - lib/mini_defender/rules/not_starting_with.rb
187
200
  - lib/mini_defender/rules/numeric.rb
data/.DS_Store DELETED
Binary file
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class MiniDefender::Rules::NotLocalURL < MiniDefender::Rule
4
- LOCALHOST_PATTERNS = [
5
- /^localhost$/i, # localhost, LOCALHOST
6
- /^127\./, # 127.x.x.x
7
- /^::1$/, # IPv6 localhost
8
- /^0\.0\.0\.0$/, # All interfaces IPv4
9
- /^::$/, # IPv6 unspecified
10
- /\.local$/i, # domain.local
11
- /^local\./i, # local.domain
12
- /^localhost\./i, # localhost.anything
13
- ]
14
-
15
- def self.signature
16
- 'not_local_url'
17
- end
18
-
19
- def passes?(attribute, value, validator)
20
- uri = URI.parse(value.to_s)
21
- host = uri.host.to_s.downcase
22
-
23
- !LOCALHOST_PATTERNS.any? { |pattern| host.match?(pattern) }
24
- rescue URI::InvalidURIError
25
- false
26
- end
27
-
28
- def message(attribute, value, validator)
29
- 'URL cannot point to localhost or local domain.'
30
- end
31
- end