Invisible honeypot spam protection for any Plug-based Elixir app.
Bots fill every form field they can find. Honeytrap adds a hidden field that real users never touch. It also measures how fast the form was submitted and collects a few passive interaction signals. If any of those look wrong, the request is flagged. You decide what to do with the flag.
Install
Add honeytrap to your deps in mix.exs:
def deps do
[
{:honeytrap, "~> 0.1.0"}
]
endUsage
1. Arm the trap
Set a timestamp on the session when you render the form. Do this in your controller action.
def new(conn, _params) do
conn = Honeytrap.arm(conn, :website)
render(conn, :new)
end2. Render the fields
Add the honeypot field and the signals field to your form template.
<form action={~p"/contact"} method="post">
<%= Honeytrap.field(:website) %>
<%= Honeytrap.signals_field() %>
<label>Message <textarea name="message"></textarea></label>
<button type="submit">Send</button>
</form>[!NOTE] The honeypot field is hidden with inline styles by default. Pass a
:classor:styleto use your own hiding.
3. Plug the check into your pipeline
pipeline :submissions do
plug Honeytrap.Plug, fields: [:website]
endOn matching requests the plug puts a result in conn.assigns.honeytrap:
%{
bot: true,
reasons: [{:filled, :website}, {:too_fast, :website}],
human_rating: 0.2
}4. Act on the result
Honeytrap does not halt the pipeline. Your action decides what to do. That way you keep an escape hatch for false positives.
def create(conn, params) do
if conn.assigns.honeytrap.bot do
render(conn, :thanks)
else
Contact.deliver(params)
render(conn, :thanks)
end
endConfiguration
Pass options to the plug directly:
:fields(required). List of honeypot field names to check. Set per plug call, since different forms use different fields.
These options can also be set globally via Application env under the
:honeytrap key. Per-plug values override the global default.
:default_delay(default2.0). Minimum seconds between arm and submit.:disable_delay(defaultfalse). Skip the elapsed time check.:minimum_human_rating(defaultnil). If set, flags submissions below this score.:signals_input_name(default"honeytrap_signals"). Name of the signals input.
Example app config:
# config/config.exs
config :honeytrap, default_delay: 3.0How it works
Three independent checks:
- Filled honeypot. Real users cannot see the field. Bots often fill it anyway.
- Elapsed time. Real users take at least a second or two. Instant submits are suspicious.
- Human signals. A tiny script watches for mouse, touch, scroll, keyboard, and focus events on the form. The result is packed into a hidden JSON blob. You can act on the rating or ignore it.
Session storage is used for the arming timestamp. No cookies beyond what you already have.
License
MIT.