mini_defender 0.6.5 → 0.6.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mini_defender/data/private_network_patterns.txt +64 -0
- data/lib/mini_defender/rules/image.rb +5 -1
- data/lib/mini_defender/rules/integer.rb +2 -1
- data/lib/mini_defender/rules/mime_types.rb +5 -1
- data/lib/mini_defender/rules/url.rb +125 -2
- data/lib/mini_defender/version.rb +1 -1
- data/mini_defender.gemspec +2 -0
- metadata +32 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bae6fdb0faacfd54fe463d30f80710e40d383bd868d64e65da7243bae0c5b1c
|
4
|
+
data.tar.gz: f9cf13d8acbc654b9c647c33b322937722b97f0a99b8d389d4311002a3a5a65e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c50350da6f341f0487357cf63cad4dbf070c5c8f89614344619182dffd9b7f345b3015466e185fe4114cadcd2a3f7159bbf87e7413bd0d887327cdb13cebbce
|
7
|
+
data.tar.gz: 8bb5e5e2702c985a7c3933147d0db5f59abde4e566f8747aa6158d21fb5bd440dcf6f1d8706ae0846e0532a14d918d2b36d04673f519d98c72412b480c25fef4
|
@@ -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
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'action_dispatch'
|
4
|
+
require 'marcel'
|
4
5
|
|
5
6
|
class MiniDefender::Rules::Image < MiniDefender::Rule
|
6
7
|
MIMES = %w[image/jpeg image/png image/gif image/bmp image/png image/svg+xml image/webp]
|
@@ -10,7 +11,10 @@ class MiniDefender::Rules::Image < MiniDefender::Rule
|
|
10
11
|
end
|
11
12
|
|
12
13
|
def passes?(attribute, value, validator)
|
13
|
-
|
14
|
+
content_type = Marcel::MimeType.for(value.read)
|
15
|
+
value.rewind
|
16
|
+
|
17
|
+
value.is_a?(ActionDispatch::Http::UploadedFile) && MIMES.include?(content_type)
|
14
18
|
end
|
15
19
|
|
16
20
|
def message(attribute, value, validator)
|
@@ -54,9 +54,10 @@ class MiniDefender::Rules::Integer < MiniDefender::Rule
|
|
54
54
|
end
|
55
55
|
|
56
56
|
# Remove leading zero so Integer will not treat it as octal
|
57
|
+
# Handle leading zeros while preserving both + and - signs
|
57
58
|
value = value
|
58
59
|
.to_s
|
59
|
-
.gsub(/^0
|
60
|
+
.gsub(/^([+-])?0+(?=\d)/, '\1')
|
60
61
|
|
61
62
|
if @mode == 'relaxed'
|
62
63
|
value = normalize_digits(value)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'action_dispatch'
|
4
|
+
require 'marcel'
|
4
5
|
|
5
6
|
class MiniDefender::Rules::MimeTypes < MiniDefender::Rule
|
6
7
|
def initialize(types)
|
@@ -26,7 +27,10 @@ class MiniDefender::Rules::MimeTypes < MiniDefender::Rule
|
|
26
27
|
|
27
28
|
def passes?(attribute, value, validator)
|
28
29
|
@file = value.is_a?(ActionDispatch::Http::UploadedFile)
|
29
|
-
|
30
|
+
content_type = Marcel::MimeType.for(value.read)
|
31
|
+
value.rewind
|
32
|
+
|
33
|
+
@file && @types.include?(content_type)
|
30
34
|
end
|
31
35
|
|
32
36
|
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.8
|
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
|
+
date: 2024-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: marcel
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
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'
|
83
111
|
description: A small and efficient validation library for Rails and anything that
|
84
112
|
uses Ruby.
|
85
113
|
email:
|
@@ -96,6 +124,7 @@ files:
|
|
96
124
|
- RULES.md
|
97
125
|
- Rakefile
|
98
126
|
- lib/mini_defender.rb
|
127
|
+
- lib/mini_defender/data/private_network_patterns.txt
|
99
128
|
- lib/mini_defender/extensions/enumerable.rb
|
100
129
|
- lib/mini_defender/handles_validation_errors.rb
|
101
130
|
- lib/mini_defender/rule.rb
|
@@ -219,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
248
|
- !ruby/object:Gem::Version
|
220
249
|
version: '0'
|
221
250
|
requirements: []
|
222
|
-
rubygems_version: 3.
|
251
|
+
rubygems_version: 3.5.3
|
223
252
|
signing_key:
|
224
253
|
specification_version: 4
|
225
254
|
summary: A small and efficient validation library for Rails and anything that uses
|