
I released Commentail 1.3.1, a security update for Wagtail comments. It changes service-data storage, form issuance and discussion access. The project page covers the library itself; this article focuses on the update.
Changes to stored fingerprints
Commentail stores fingerprints of identities and request content to count quotas and recognise repeated submissions. These values previously used ordinary SHA-256 hashes. If an original value is predictable, such as an IP address, someone with a database copy can test guesses against those hashes.
In 1.3.1 I changed these fingerprints to HMAC keyed from Django’s SECRET_KEY. Separate contexts distinguish their purposes: quotas include the Site and event kind, while submission fingerprints include the discussion. A database copy alone is insufficient to test guessed values against these fingerprints without the key.
This does not encrypt comment bodies or private email fields. Random secrets for protected links, forms and browser proof keep their existing storage method. The separate HMAC check for repeated guest text is unchanged.
The migration transforms old fingerprints without recovering their original values. Current quotas are preserved, and retries of accepted submissions remain recognisable. These records support SECRET_KEY_FALLBACKS for later key rotation; the duplicate guest-text history starts over when the key changes.
Limits on new forms
Submission limits alone do not bound the creation of service records: opening and recovering forms also create data. New form and recovery tickets are now limited for guests and signed-in readers. Navigation counts when it issues a new ticket.
The defaults allow 120 new tickets per minute and 1200 per hour for one IP address within one Site. The settings are FORM_IP_LIMIT_PER_MINUTE and FORM_IP_LIMIT_PER_HOUR. These limits are separate from quotas for accepted comments and guest submission attempts.
When the limit is reached, reading remains available. An existing valid form still works, and a refusal creates neither a new ticket nor a CAPTCHA. Entered values remain in the error response. Once the window expires, the reader can retry the check without reloading the page.
A refusal cannot save another recovery ticket, though. Copy the displayed draft before leaving the page. Shared networks, such as offices and schools, need limits suited to their actual traffic.
Keeping a Page within its Site
A discussion belongs to a particular Site. Version 1.3.1 checks that its target Page remains within that Site’s root tree. Direct requests, protected guest links and queued email follow this check. Moving a Page outside the allowed tree must revoke access through its old links.
I made this check too narrow for multilingual sites: Wagtail can place translated homepages in sibling trees, which 1.3.1 treated as foreign. Valid translated discussions therefore became unavailable. Version 1.3.2 fixes this while retaining access checks for restricted and unpublished pages.
Cleanup and adapter diagnostics
Expired CAPTCHA records, guest grants, form tickets and rate events are deleted in batches of at most 500. Each batch commits in a separate transaction. If a later batch fails, earlier cleanup remains committed. Active records are retained; the site still needs its existing background-processing schedule.
Configuration errors for ordinary Django-model adapters are now more specific. Diagnostics distinguish model resolution, primary-key support, duplicate registration, class imports and required methods. Arbitrary exception text from an adapter remains private.
Preparing the upgrade
Migration 0018_private_fingerprints changes data in one transaction and has no reverse transformation. Version 1.3.1 introduces no new dependencies or schema changes, but upgrading still requires more than replacing the package.
First rehearse on a restored database. Back up the database, SECRET_KEY, settings, template overrides and exact previous package and dependency versions together. Stop web and background writers during the upgrade. Keep the same key and do not run old and new writers concurrently. Large databases need maintenance time and transaction space.
To upgrade directly to the corrected release, after the backup and with writers stopped:
python -m pip install commentail==1.3.2
python manage.py migrate
python manage.py check
python manage.py collectstatic --noinputRestart the application and background processes. Check a form opened before the upgrade, an accepted submission retry, guest and account submissions, CAPTCHA errors, draft recovery, replies, protected links and email. If your form templates are overridden, compare their issuance-error handling and disabled submit state with the new version.
Behind nginx, check TRUSTED_PROXIES and client-IP forwarding separately. Commentail uses the direct peer by default; without the correct configuration, visitors may share the proxy’s IP quota.
Downgrading requires restoring the matching database, key and old package with writers stopped. Reinstalling the old version or reversing migrations cannot restore compatible data. Older backups and exports still contain the earlier hashes and need separate protection.
See the migration and recovery guide and security and privacy documentation for details.
Choose a sign-in method or comment as a guest below.