rack-honeypot 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -0
- data/LICENSE.md +10 -0
- data/README.md +70 -0
- data/Rakefile +6 -0
- data/VERSION +1 -0
- data/lib/rack/honeypot.rb +68 -0
- data/test/test_honeypot.rb +87 -0
- metadata +131 -0
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Copyright (c) 2009, Sunlight Foundation
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
7
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
8
|
+
* Neither the name of Sunlight Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
9
|
+
|
10
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Honeypot, a Rack Middleware for trapping spambots
|
2
|
+
|
3
|
+
Written by Luigi Montanez of the Sunlight Labs, with contributions from Luc Castera. Copyright 2009.
|
4
|
+
|
5
|
+
This middleware acts as a spam trap. It inserts, into every outputted `<form>`, a text field that a spambot will really want to fill in, but is actually not used by the app. The field is hidden to humans via CSS, and includes a warning label for screenreading software.
|
6
|
+
|
7
|
+
In the `<body>`:
|
8
|
+
|
9
|
+
<form>
|
10
|
+
<div class='phonetoy'>
|
11
|
+
<label for='email'>Don't fill in this field</label>
|
12
|
+
<input type='text' name='email' value=''/>
|
13
|
+
</div>
|
14
|
+
[...]
|
15
|
+
|
16
|
+
In the `<head>`:
|
17
|
+
|
18
|
+
<style type='text/css' media='all'>
|
19
|
+
div.phonetoy {
|
20
|
+
display:none;
|
21
|
+
}
|
22
|
+
</style>
|
23
|
+
|
24
|
+
Then, for incoming requests, the middleware will check if the text field has been set to an unexpected value. If it has, that means a spambot has altered the field, and the spambot is booted to a dead end blank page.
|
25
|
+
|
26
|
+
## Dependencies
|
27
|
+
|
28
|
+
You will need to install these RubyGems:
|
29
|
+
|
30
|
+
* unindentable: http://github.com/sunlightlabs/ruby-unindentable/tree/master
|
31
|
+
* rack-test: http://github.com/brynary/rack-test/tree/master
|
32
|
+
|
33
|
+
## Configuration
|
34
|
+
|
35
|
+
To use in your Rails app, place `honeypot.rb` in `lib/rack`.
|
36
|
+
|
37
|
+
Then in `environment.rb`:
|
38
|
+
|
39
|
+
config.middleware.use "Rack::Honeypot"
|
40
|
+
|
41
|
+
That's all there is to it. Fire up your app, View Source on a page with a form, and see the magic.
|
42
|
+
|
43
|
+
There are a few options you can pass in:
|
44
|
+
|
45
|
+
* `:class_name` is the class assigned to the parent div of the honeypot. Defaults to "phonetoy", an anagram of honeypot.
|
46
|
+
* `:label` is the warning label displayed to those with CSS disabled. Defaults to "Don't fill in this field".
|
47
|
+
* `:input_name` is the name of the form field. Ensure that this is tempting to a spambot if you modify it. Defaults to "email".
|
48
|
+
* `:input_value` is the value of the form field that would only be modified by a spambot. Defaults to blank.
|
49
|
+
|
50
|
+
If you want to modify the options used, simply do:
|
51
|
+
|
52
|
+
config.middleware.use "Rack::Honeypot", :input_name => "firstname"
|
53
|
+
|
54
|
+
|
55
|
+
## Tests
|
56
|
+
|
57
|
+
To run the tests:
|
58
|
+
|
59
|
+
sudo gem install rack-test
|
60
|
+
cd test
|
61
|
+
ruby test_honeypot.rb
|
62
|
+
|
63
|
+
|
64
|
+
## Props
|
65
|
+
|
66
|
+
Based on [django-honeypot](http://github.com/sunlightlabs/django-honeypot) by James Turk.
|
67
|
+
|
68
|
+
Credit to Geoff Buesing for a first stab at this [idea in Rack](http://mad.ly/2009/05/01/honeypot-filter-as-a-rack-middleware/).
|
69
|
+
|
70
|
+
See LICENSE.md for proper reuse guidelines.
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'unindentable'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class Honeypot
|
5
|
+
include Unindentable
|
6
|
+
|
7
|
+
def initialize(app, options={})
|
8
|
+
@app = app
|
9
|
+
@class_name = options[:class_name] || "phonetoy"
|
10
|
+
@label = options[:label] || "Don't fill in this field"
|
11
|
+
@input_name = options[:input_name] || "email"
|
12
|
+
@input_value = options[:input_value] || ""
|
13
|
+
@logger = options[:logger]
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
if spambot_submission?(Rack::Request.new(env).params)
|
18
|
+
@logger.warn("[Rack::Honeypot] Spam bot detected; responded with null") unless @logger.nil?
|
19
|
+
send_to_dead_end
|
20
|
+
else
|
21
|
+
status, headers, response = @app.call(env)
|
22
|
+
new_body = insert_honeypot(build_response_body(response))
|
23
|
+
new_headers = recalculate_body_length(headers, new_body)
|
24
|
+
[status, new_headers, new_body]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def spambot_submission?(form_hash)
|
29
|
+
form_hash && form_hash[@input_name] && form_hash[@input_name] != @input_value
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_to_dead_end
|
33
|
+
[200, {'Content-Type' => 'text/html', "Content-Length" => "0"}, []]
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_response_body(response)
|
37
|
+
response_body = ""
|
38
|
+
response.each { |part| response_body += part }
|
39
|
+
response_body
|
40
|
+
end
|
41
|
+
|
42
|
+
def recalculate_body_length(headers, body)
|
43
|
+
new_headers = headers
|
44
|
+
new_headers["Content-Length"] = body.length.to_s
|
45
|
+
new_headers
|
46
|
+
end
|
47
|
+
|
48
|
+
def insert_honeypot(body)
|
49
|
+
css = unindent <<-BLOCK
|
50
|
+
<style type='text/css' media='all'>
|
51
|
+
div.#{@class_name} {
|
52
|
+
display:none;
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
BLOCK
|
56
|
+
div = unindent <<-BLOCK
|
57
|
+
<div class='#{@class_name}'>
|
58
|
+
<label for='#{@input_name}'>#{@label}</label>
|
59
|
+
<input type='text' name='#{@input_name}' value='#{@input_value}'/>
|
60
|
+
</div>
|
61
|
+
BLOCK
|
62
|
+
body.gsub!(/<\/head>/, css + "\n</head>")
|
63
|
+
body.gsub!(/<form(.*)>/, '<form\1>' + "\n" + div)
|
64
|
+
body
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rack/test'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'mocha'
|
4
|
+
require 'unindentable'
|
5
|
+
|
6
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/rack/honeypot')
|
7
|
+
|
8
|
+
# To run this test, you need to have rack-test gem installed: sudo gem install rack-test
|
9
|
+
|
10
|
+
class HoneypotTest < Test::Unit::TestCase
|
11
|
+
include Rack::Test::Methods
|
12
|
+
include Unindentable
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@logger = stub("logger", :warn => nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def app
|
19
|
+
content = unindent <<-BLOCK
|
20
|
+
<html>
|
21
|
+
<head>
|
22
|
+
</head>
|
23
|
+
<body>
|
24
|
+
<form></form>
|
25
|
+
Hello World!
|
26
|
+
</body>
|
27
|
+
</html>
|
28
|
+
BLOCK
|
29
|
+
|
30
|
+
hello_world_app = lambda do |env|
|
31
|
+
[
|
32
|
+
200,
|
33
|
+
{
|
34
|
+
'Content-Type' => 'text/plain',
|
35
|
+
'Content-Length' => content.length.to_s
|
36
|
+
},
|
37
|
+
[content]
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
Rack::Honeypot.new(hello_world_app, :input_name => 'honeypot_email', :logger => @logger)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_normal_request_should_go_through
|
45
|
+
get '/'
|
46
|
+
assert_equal 200, last_response.status
|
47
|
+
assert_not_equal '', last_response.body
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_request_with_form_should_add_honeypot_css
|
51
|
+
get '/'
|
52
|
+
assert_equal 200, last_response.status
|
53
|
+
css = unindent <<-BLOCK
|
54
|
+
<style type='text/css' media='all'>
|
55
|
+
div.phonetoy {
|
56
|
+
display:none;
|
57
|
+
}
|
58
|
+
</style>
|
59
|
+
BLOCK
|
60
|
+
assert last_response.body.index(css)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_request_with_form_should_add_honeypot_div
|
64
|
+
get '/'
|
65
|
+
assert_equal 200, last_response.status
|
66
|
+
|
67
|
+
div = unindent <<-BLOCK
|
68
|
+
<div class='phonetoy'>
|
69
|
+
<label for='honeypot_email'>Don't fill in this field</label>
|
70
|
+
<input type='text' name='honeypot_email' value=''/>
|
71
|
+
</div>
|
72
|
+
BLOCK
|
73
|
+
assert last_response.body.index(div)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_spam_request_should_be_sent_to_dead_end
|
77
|
+
post '/', :honeypot_email => 'joe@example.com'
|
78
|
+
assert_equal 200, last_response.status
|
79
|
+
assert_equal '', last_response.body
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_spam_request_should_be_logged
|
83
|
+
@logger.expects(:warn).with("[Rack::Honeypot] Spam bot detected; responded with null")
|
84
|
+
post '/', :honeypot_email => 'joe@example.com'
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-honeypot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Luigi Montanez
|
14
|
+
- Luc Castera
|
15
|
+
- Daniel Schierbeck
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2011-10-05 00:00:00 Z
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: rake
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: unindentable
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - "="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 23
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 0
|
48
|
+
- 4
|
49
|
+
version: 0.0.4
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: rack
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
type: :runtime
|
65
|
+
version_requirements: *id003
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: rack-test
|
68
|
+
prerelease: false
|
69
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 3
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
type: :runtime
|
79
|
+
version_requirements: *id004
|
80
|
+
description: This middleware acts as a spam trap. It inserts, into every outputted <form>, a text field that a spambot will really want to fill in, but is actually not used by the app. The field is hidden to humans via CSS, and includes a warning label for screenreading software.
|
81
|
+
email: luigi.montanez@gmail.com
|
82
|
+
executables: []
|
83
|
+
|
84
|
+
extensions: []
|
85
|
+
|
86
|
+
extra_rdoc_files:
|
87
|
+
- LICENSE.md
|
88
|
+
- README.md
|
89
|
+
files:
|
90
|
+
- Gemfile
|
91
|
+
- LICENSE.md
|
92
|
+
- README.md
|
93
|
+
- Rakefile
|
94
|
+
- VERSION
|
95
|
+
- lib/rack/honeypot.rb
|
96
|
+
- test/test_honeypot.rb
|
97
|
+
homepage: http://github.com/sunlightlabs/rack-honeypot
|
98
|
+
licenses: []
|
99
|
+
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
hash: 3
|
111
|
+
segments:
|
112
|
+
- 0
|
113
|
+
version: "0"
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
hash: 3
|
120
|
+
segments:
|
121
|
+
- 0
|
122
|
+
version: "0"
|
123
|
+
requirements: []
|
124
|
+
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 1.8.6
|
127
|
+
signing_key:
|
128
|
+
specification_version: 3
|
129
|
+
summary: Middleware that functions as a spambot trap.
|
130
|
+
test_files: []
|
131
|
+
|