Current section
Files
Jump to
Current section
Files
README.md
# Honeytrap
[](https://codeberg.org/fluck/honeytrap/actions?workflow=ci.yml)
[](https://codeberg.org/fluck/honeytrap/tags)
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`:
```elixir
def deps do
[
{:honeytrap, "~> 0.1.0"}
]
end
```
## Usage
### 1. Arm the trap
Set a timestamp on the session when you render the form. Do this in your
controller action.
```elixir
def new(conn, _params) do
conn = Honeytrap.arm(conn, :website)
render(conn, :new)
end
```
### 2. Render the fields
Add the honeypot field and the signals field to your form template.
```heex
<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 `:class`
> or `:style` to use your own hiding.
### 3. Plug the check into your pipeline
```elixir
pipeline :submissions do
plug Honeytrap.Plug, fields: [:website]
end
```
On matching requests the plug puts a result in `conn.assigns.honeytrap`:
```elixir
%{
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.
```elixir
def create(conn, params) do
if conn.assigns.honeytrap.bot do
render(conn, :thanks)
else
Contact.deliver(params)
render(conn, :thanks)
end
end
```
## Configuration
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` (default `2.0`). Minimum seconds between arm and submit.
- `:disable_delay` (default `false`). Skip the elapsed time check.
- `:minimum_human_rating` (default `nil`). If set, flags submissions below this
score.
- `:signals_input_name` (default `"honeytrap_signals"`). Name of the signals
input.
Example app config:
```elixir
# config/config.exs
config :honeytrap, default_delay: 3.0
```
## How it works
Three independent checks:
1. **Filled honeypot**. Real users cannot see the field. Bots often fill it
anyway.
2. **Elapsed time**. Real users take at least a second or two. Instant submits
are suspicious.
3. **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.