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 +4 -4
- data/lib/mini_defender/data/private_network_patterns.txt +64 -0
- data/lib/mini_defender/rules/numeric.rb +18 -2
- data/lib/mini_defender/rules/url.rb +125 -2
- data/lib/mini_defender/version.rb +1 -1
- data/mini_defender.gemspec +1 -0
- metadata +17 -4
- data/.DS_Store +0 -0
- data/lib/mini_defender/rules/not_local_url.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a9dd7cd41807156b4aaa18aa7cf645fd00b67ea0dfbd9bc457e12c01bf9c6a3
|
4
|
+
data.tar.gz: 9b666b35cbe313a93783609ada72dadffe70feacd7eb22dfba05d958906ac283
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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)
|
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)
|
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)
|
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
|
data/mini_defender.gemspec
CHANGED
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.
|
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:
|
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
|