spamtrap 0.3.5 → 0.4.3
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/generators/spamtrap/install/install_generator.rb +15 -0
- data/lib/generators/spamtrap/install/templates/spamtrap.rb +78 -0
- data/lib/spamtrap/controller.rb +237 -43
- data/lib/spamtrap/crypto.rb +86 -19
- data/lib/spamtrap/helper.rb +251 -68
- data/lib/spamtrap/parameter_filter.rb +77 -0
- data/lib/spamtrap/railtie.rb +20 -0
- data/lib/spamtrap/test_helper.rb +103 -0
- data/lib/spamtrap/version.rb +1 -1
- data/lib/spamtrap.rb +174 -1
- metadata +101 -14
- data/test/controller_test.rb +0 -473
- data/test/form_builder_mutation_test.rb +0 -174
- data/test/test_helper.rb +0 -33
data/lib/spamtrap.rb
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
require 'openssl'
|
|
2
|
+
require 'ipaddr'
|
|
2
3
|
|
|
3
4
|
module Spamtrap
|
|
4
5
|
class << self
|
|
5
6
|
attr_accessor :on_trap
|
|
6
|
-
attr_writer
|
|
7
|
+
attr_writer :stable_ids
|
|
8
|
+
|
|
9
|
+
# Whether mutated fields keep an id/for derived from the real field name (e.g.
|
|
10
|
+
# #message_name) instead of the opaque encrypted one. Default true.
|
|
11
|
+
def stable_ids
|
|
12
|
+
@stable_ids.nil? ? true : @stable_ids
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_writer :nonce, :nonce_timeout, :mutate, :mutation_timeout, :nonce_skew, :allowed_params
|
|
7
16
|
|
|
8
17
|
def nonce
|
|
9
18
|
@nonce || false
|
|
@@ -13,12 +22,176 @@ module Spamtrap
|
|
|
13
22
|
@nonce_timeout || 1800
|
|
14
23
|
end
|
|
15
24
|
|
|
25
|
+
# false, true (strict), :strict, or :lenient (remap only; never traps a plaintext key).
|
|
16
26
|
def mutate
|
|
17
27
|
@mutate || false
|
|
18
28
|
end
|
|
29
|
+
|
|
30
|
+
# Extra top-level param names a strict action accepts unencrypted (e.g. app-added form fields).
|
|
31
|
+
def allowed_params
|
|
32
|
+
Array(@allowed_params).map(&:to_s)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mutation_timeout
|
|
36
|
+
@mutation_timeout || nonce_timeout
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# How far a token's timestamp may sit in the future (clock skew), before it's rejected.
|
|
40
|
+
def nonce_skew
|
|
41
|
+
@nonce_skew || 60
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
attr_writer :nonce_store
|
|
45
|
+
|
|
46
|
+
# Where nonce: :single_use records seen nonce ids; defaults to the app's cache.
|
|
47
|
+
def nonce_store
|
|
48
|
+
@nonce_store || Rails.cache
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attr_writer :trap_response
|
|
52
|
+
|
|
53
|
+
# How a trapped request responds: :head, :no_content, :unprocessable, :redirect_back,
|
|
54
|
+
# a callable, or a Hash keyed by trap reason. See Spamtrap::Controller#spamtrap_render_trap.
|
|
55
|
+
def trap_response
|
|
56
|
+
@trap_response || :head
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
attr_writer :enabled
|
|
60
|
+
|
|
61
|
+
# Global kill switch; false skips every spamtrap check (honeypot, nonce, mutation).
|
|
62
|
+
def enabled
|
|
63
|
+
@enabled.nil? ? true : @enabled
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
attr_writer :filter_parameters
|
|
67
|
+
|
|
68
|
+
# Apply the app's filter_parameters to mutated field names in the log. Default true.
|
|
69
|
+
def filter_parameters
|
|
70
|
+
@filter_parameters.nil? ? true : @filter_parameters
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
attr_writer :nonce_bind_ip
|
|
74
|
+
|
|
75
|
+
# true binds the nonce to the full client IP (default), :prefix to its /24 or /48
|
|
76
|
+
# network (tolerates mobile handoff/CGNAT), false does not bind it to the IP at all.
|
|
77
|
+
def nonce_bind_ip
|
|
78
|
+
@nonce_bind_ip.nil? ? true : @nonce_bind_ip
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
attr_writer :min_fill_time
|
|
82
|
+
|
|
83
|
+
# Minimum seconds between render and submission a human needs; false or 0 disables. Default 1.
|
|
84
|
+
def min_fill_time
|
|
85
|
+
@min_fill_time.nil? ? 1 : @min_fill_time
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
attr_writer :secret_key_base
|
|
89
|
+
|
|
90
|
+
# The current HKDF source secret; defaults to the app's own so no setup is needed to opt in.
|
|
91
|
+
def secret_key_base
|
|
92
|
+
@secret_key_base || Rails.application.secret_key_base
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
attr_writer :honeypot_styles, :js_proof, :suspicious_if
|
|
96
|
+
|
|
97
|
+
# Decoys f.spamtrap renders: any of :textarea, :text, :checkbox. Default is the textarea only.
|
|
98
|
+
def honeypot_styles
|
|
99
|
+
Array(@honeypot_styles.nil? ? :textarea : @honeypot_styles).map(&:to_sym)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Require a hidden field that only an executing script fills in; excludes no-JS users.
|
|
103
|
+
def js_proof
|
|
104
|
+
@js_proof || false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# App-defined heuristic run after every other check; truthy result traps as :content.
|
|
108
|
+
def suspicious_if
|
|
109
|
+
@suspicious_if
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
attr_writer :previous_secret_key_base
|
|
113
|
+
|
|
114
|
+
# Set during a rotation window so tokens minted under the old secret still verify/decrypt.
|
|
115
|
+
def previous_secret_key_base
|
|
116
|
+
@previous_secret_key_base
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
attr_writer :token_context
|
|
120
|
+
|
|
121
|
+
# Callable taking the request and returning a String mixed into every token, so a token
|
|
122
|
+
# minted on one hostname doesn't verify on another. Off (nil) by default.
|
|
123
|
+
def token_context
|
|
124
|
+
@token_context
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# nil when token_context is unset, so the token format is unchanged for apps that don't opt in.
|
|
129
|
+
def self.token_context_for(request)
|
|
130
|
+
token_context && token_context.call(request).to_s
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Normalises ip per mode: true keeps it as-is, :prefix masks to its /24 (IPv4) or /48 (IPv6)
|
|
134
|
+
# network (IPv4-mapped addresses unmapped first so they mask as IPv4), false discards it.
|
|
135
|
+
def self.normalize_ip(ip, mode = nonce_bind_ip)
|
|
136
|
+
case mode
|
|
137
|
+
when false
|
|
138
|
+
''
|
|
139
|
+
when :prefix
|
|
140
|
+
begin
|
|
141
|
+
addr = IPAddr.new(ip.to_s)
|
|
142
|
+
addr = addr.native if addr.ipv4_mapped?
|
|
143
|
+
bits = addr.ipv4? ? 24 : 48
|
|
144
|
+
"#{addr.mask(bits)}/#{bits}"
|
|
145
|
+
rescue IPAddr::InvalidAddressError
|
|
146
|
+
ip.to_s
|
|
147
|
+
end
|
|
148
|
+
else
|
|
149
|
+
ip.to_s
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Parameter names for each honeypot style, derived from the declared name so the view and
|
|
154
|
+
# controller agree without extra configuration. With mutation on they are encrypted like any field.
|
|
155
|
+
def self.honeypot_fields(name)
|
|
156
|
+
{ textarea: name.to_s, text: "#{name}_input", checkbox: "#{name}_check" }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Key for Rack::Attack / Rails' rate_limit, scoped by the normalized client IP.
|
|
160
|
+
def self.throttle_key(request)
|
|
161
|
+
"spamtrap:#{normalize_ip(request.remote_ip)}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Wires the gem into Action Controller and Action View. The Railtie calls the two halves
|
|
165
|
+
# from on_load hooks; outside a Rails app call install! after those frameworks are loaded.
|
|
166
|
+
def self.install!
|
|
167
|
+
install_controller!
|
|
168
|
+
install_form_builder!
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def self.install_controller!
|
|
172
|
+
ActionController::Base.include Spamtrap::Controller unless ActionController::Base < Spamtrap::Controller
|
|
173
|
+
# const_defined?(false) avoids autoloading action_controller/api.rb for apps that never use it.
|
|
174
|
+
return unless ActionController.const_defined?(:API, false)
|
|
175
|
+
|
|
176
|
+
ActionController::API.include Spamtrap::Controller unless ActionController::API < Spamtrap::Controller
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def self.install_form_builder!
|
|
180
|
+
builder = ActionView::Helpers::FormBuilder
|
|
181
|
+
builder.prepend Spamtrap::FormBuilderMutation unless builder < Spamtrap::FormBuilderMutation
|
|
182
|
+
builder.include Spamtrap::FormBuilderHelper unless builder < Spamtrap::FormBuilderHelper
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# True once action_view/helpers has loaded, checked without triggering the autoload
|
|
186
|
+
# (defined?(ActionView::Helpers::FormBuilder) would load it).
|
|
187
|
+
def self.form_builder_loaded?
|
|
188
|
+
defined?(ActionView) && ActionView.autoload?(:Helpers).nil? &&
|
|
189
|
+
ActionView.const_defined?(:Helpers, false) && ActionView::Helpers.const_defined?(:FormBuilder, false)
|
|
19
190
|
end
|
|
20
191
|
|
|
21
192
|
require 'spamtrap/crypto'
|
|
22
193
|
require 'spamtrap/controller'
|
|
23
194
|
require 'spamtrap/helper'
|
|
195
|
+
require 'spamtrap/parameter_filter'
|
|
196
|
+
require 'spamtrap/railtie' if defined?(Rails::Railtie)
|
|
24
197
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spamtrap
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3
|
|
4
|
+
version: 0.4.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cedric Howe
|
|
@@ -10,19 +10,107 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: actionpack
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '7.
|
|
18
|
+
version: '7.2'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
26
|
- - ">="
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '7.
|
|
28
|
+
version: '7.2'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: actionview
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '7.2'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '9'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '7.2'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '9'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: railties
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '7.2'
|
|
59
|
+
- - "<"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '9'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '7.2'
|
|
69
|
+
- - "<"
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '9'
|
|
72
|
+
- !ruby/object:Gem::Dependency
|
|
73
|
+
name: rake
|
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '0'
|
|
79
|
+
type: :development
|
|
80
|
+
prerelease: false
|
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
- !ruby/object:Gem::Dependency
|
|
87
|
+
name: minitest
|
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
type: :development
|
|
94
|
+
prerelease: false
|
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '0'
|
|
100
|
+
- !ruby/object:Gem::Dependency
|
|
101
|
+
name: activemodel
|
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - ">="
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '0'
|
|
107
|
+
type: :development
|
|
108
|
+
prerelease: false
|
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
110
|
+
requirements:
|
|
111
|
+
- - ">="
|
|
112
|
+
- !ruby/object:Gem::Version
|
|
113
|
+
version: '0'
|
|
26
114
|
description: Create bogus form fields (honeypots) that will be filled-in by spambots.
|
|
27
115
|
When submitted, the form data will be discarded while still returning a 200 response.
|
|
28
116
|
email: cedric@freezerbox.com
|
|
@@ -30,15 +118,17 @@ executables: []
|
|
|
30
118
|
extensions: []
|
|
31
119
|
extra_rdoc_files: []
|
|
32
120
|
files:
|
|
121
|
+
- lib/generators/spamtrap/install/install_generator.rb
|
|
122
|
+
- lib/generators/spamtrap/install/templates/spamtrap.rb
|
|
33
123
|
- lib/spamtrap.rb
|
|
34
124
|
- lib/spamtrap/controller.rb
|
|
35
125
|
- lib/spamtrap/crypto.rb
|
|
36
126
|
- lib/spamtrap/helper.rb
|
|
127
|
+
- lib/spamtrap/parameter_filter.rb
|
|
128
|
+
- lib/spamtrap/railtie.rb
|
|
129
|
+
- lib/spamtrap/test_helper.rb
|
|
37
130
|
- lib/spamtrap/version.rb
|
|
38
|
-
|
|
39
|
-
- test/form_builder_mutation_test.rb
|
|
40
|
-
- test/test_helper.rb
|
|
41
|
-
homepage: http://github.com/cedric/spamtrap/
|
|
131
|
+
homepage: https://github.com/cedric/spamtrap/
|
|
42
132
|
licenses:
|
|
43
133
|
- MIT
|
|
44
134
|
metadata: {}
|
|
@@ -49,17 +139,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
49
139
|
requirements:
|
|
50
140
|
- - ">="
|
|
51
141
|
- !ruby/object:Gem::Version
|
|
52
|
-
version: 3.
|
|
142
|
+
version: 3.1.0
|
|
53
143
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
144
|
requirements:
|
|
55
145
|
- - ">="
|
|
56
146
|
- !ruby/object:Gem::Version
|
|
57
147
|
version: 1.3.6
|
|
58
148
|
requirements: []
|
|
59
|
-
rubygems_version: 4.0.
|
|
149
|
+
rubygems_version: 4.0.21
|
|
60
150
|
specification_version: 4
|
|
61
151
|
summary: Simple spamtrap for spambots.
|
|
62
|
-
test_files:
|
|
63
|
-
- test/controller_test.rb
|
|
64
|
-
- test/form_builder_mutation_test.rb
|
|
65
|
-
- test/test_helper.rb
|
|
152
|
+
test_files: []
|