Categories

A Simple & Effective Django Honeypot Form Field

Understand the basic concepts behind honeypot form fields and learn how to implement a simple Django honeypot form field 🍯

After publishing a form on the Internet, it is only a question of time until gangs of evil bots will show up to take it down. I was recently confronted with a bot spamming a registration form belonging to a Django app I’m maintaining.

According to Wikipedia,

registration bots which sign up a specific email address to numerous services in order to have the confirmation messages flood the email inbox and distract from important messages indicating a security breach.

Evil bastards.

This is how I imagine those mischievous bots must look like. Photo by Somchai Kongkamsri

Anyway… Turns out there is a simple technique for tricking bots into revealing their false identity. You see, most bots are not very clever. They will simply fill in all fields of the form they are attacking. We can use this behavior to our advantage by adding an invisible checkbox to the form.

Although the checkbox is invisible, all but the smartest bots will still activate it. Therefore, whenever we receive a form with an invisible-yet-activated checkbox, we can be confident that something is fishy. The following code snippet implements this idea as a custom Django form field:

from django import forms
from django.core.exceptions import ValidationError

class HoneypotField(forms.BooleanField):
    default_widget = forms.CheckboxInput({'style': 'display:none !important;', 'tabindex': '-1', 'autocomplete': 'off'})

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('widget', HoneypotField.default_widget)
        kwargs['required'] = False
        super().__init__(*args, **kwargs)

    def clean(self, value):
        if cleaned_value := super().clean(value):
            raise ValidationError('')
        else:
            return cleaned_value

We can then add the above Django honeypot field to any form that we think should be protected by the honeypot mechanism:

class MyRegistrationForm(forms.Form):
    asdf = HoneypotField()
    ...

Be sure to give the honeypot field an inconspicuous name as some bots check for obvious names such as honeypot.