About the applicant
I'm a Staff Engineer at Automattic on the Anti-spam team (the team behind Akismet and WordPress spam protection). This module is the official Drupal integration for Akismet, built by Automattic. I have experience with Drupal module development and have been working with the Drupal API and ecosystem while building this module.
What the module does
The Akismet module provides frictionless spam detection for comments, contact forms, Webform submissions, and user registrations using the Akismet cloud service.
Key features:
- Three-tier verdict system (ham, spam, discard) with per-submission feedback that improves detection accuracy over time
- Moderator tools: dedicated spam queue, bulk actions, per-comment spam/ham feedback
- Bot-detection signals: honeypot fields, HMAC nonces, JavaScript timing data
- Fail-open architecture — API or network errors never block legitimate submissions
- Circuit breaker and queue-based retry for API resilience
- Deferred verdict support via webhook callbacks
- GDPR-compliant data export and erasure (email-based, covers anonymous users)
- Drush commands for verification, recheck, purge, stats, and diagnostics
- Optional integration with the Key module for secure API key storage
Third-party dependencies
The module requires the automattic/akismet-sdk PHP package, which is distributed via Packagist and installed automatically by Composer. The SDK is not bundled in the repository — Composer-based installation is required. This dependency is declared in composer.json.
How it differs from similar projects
I'm aware of the existing anti-spam landscape on Drupal.org:
- The original akismet project was last maintained for Drupal 7 and has no Drupal 10/11 release.
- Honeypot uses hidden form fields and time restrictions — a different, client-side approach.
- Antibot uses JavaScript-based challenges — also client-side only.
This module is a ground-up implementation for modern Drupal using the official Akismet PHP SDK, providing cloud-based content analysis rather than client-side heuristics. It uses current Drupal patterns: PHP 8.1 attribute-based hooks and plugins, dependency injection throughout, Symfony events for extensibility, and config schema with translation support. Test coverage includes 350+ unit, 100+ kernel, 180+ functional tests, with PHPStan at level 9 (zero errors) and clean PHPCS.
Manual reviews of other projects
Completed 3 manual code reviews as part of the review bonus program:
- Antibot Redirect — Identified dynamic property deprecation, static service calls, wrong dependency namespace. Set to Needs work.
- Status Block — Identified untranslatable strings, missing null safety, caching concerns. Set to Needs work.
- Accessibility Statement — No issues found, excellent code quality. Set to RTBC. (My reponse might've been rate-limited due to my account's newness)
Comments
Comment #2
vishal.kadamComment #3
avpadernoThank you for applying!
Please read Review process for security advisory coverage: What to expect for more details and Security advisory coverage application checklist to understand what reviewers look for. Tips for ensuring a smooth review gives some hints for a smoother review.
The important notes are the following.
Keep in mind that once the project is opted into security advisory coverage, only Security Team members may change coverage.
To the reviewers
Please read How to review security advisory coverage applications, Application workflow, What to cover in an application review, and Tools to use for reviews.
The important notes are the following.
For new reviewers, I would also suggest to first read In which way the issue queue for coverage applications is different from other project queues.
Comment #4
vishal.kadam1.
trunkis a wrong name for a branch and should be removed. Release branch names always end with the literal .x as described in Release branches.2. FILE: composer.json
There is no need to add the required Drupal version, since that is already added by the Drupal.org Composer façade.
3. FILE: templates/akismet-widget.html.twig
<strong>Akismet</strong>Strings shown in the user interface must be translatable.
4. FILE: src/Form/AkismetSettingsForm.php
With Drupal 10 and Drupal 11, there is no longer need to use #default_value for each form element, when the parent class is ConfigFormBase: It is sufficient to use #config_target, as in the following code.
Using that code, it is no longer needed to save the configuration values in the form submission handler: The parent class will take care of that.
5. Fix the warnings/errors reported by PHP_CodeSniffer.
NOTE: I would suggest enabling GitLab CI for the project, follow the Drupal Association .gitlab-ci.yml template and fix the PHP_CodeSniffer errors/warnings it reports.
Comment #5
derekspringer commentedThanks again for the review. Here is how each finding has been addressed. All 7 GitLab CI jobs now pass (phpcs, phpstan, eslint, stylelint, cspell, composer-lint, phpunit).
1. Branch naming
Created the
1.0.xrelease branch on drupal.org per the naming conventions. The1.0.1tag targets this branch.2. composer.json — drupal/core
Removed
drupal/corefromrequire. Added aconflictrule fordrupal/core: <10.3so Composer still blocks unsupported core versions without relying on the facade.3. Translatable strings in akismet-widget.html.twig
The "Akismet" brand name is now part of the full translatable string via the
|tfilter with placeholders, so translators can reorder the phrase. (Note:{% trans %}blocks were initially used but Drupal'sTwigNodeTranscompiler silently strips filters likenumber_formatfrom variables inside trans blocks, so we reverted to explicit|tcalls.)4. #config_target in AkismetSettingsForm
Migrated
strictness,privacy_notice, andshow_comment_countto#config_target. The parentConfigFormBase::submitForm()now handles saving these automatically — the explicit$config->save()was also removed in favor of a single save by the parent. The remaining fields have complex conditional save logic (API key precedence across settings.php/Key module/config, checkboxes group mapped to individual booleans, nested resilience config paths with type conversion) that requires manual handling insubmitForm().5. PHP_CodeSniffer and GitLab CI
- Added
.gitlab-ci.ymlusing the Drupal Association template.- Fixed root cause:
phpcs.xml.distwas missing theextensionsarg, silently skipping.moduleand.installfiles. Broadened scan scope to all shipped file types (php,module,install,inc,js,test,profile,theme,info,txt,md,yml).- Fixed all violations in
akismet.module(properImplements hook_xxx().doc blocks,usestatements replacing inline FQCNs),akismet.install(declare ordering, use statements, line wrapping, tag placement), andscripts/manual-test-helper.php.- Wrapped all markdown files to 80-char line length (CODE-OF-CONDUCT.md, SECURITY.md, TESTING.md, README.md, AGENTS.md, CONTRIBUTING.md).
- Removed
projectfromakismet.info.yml.- Added comment documenting why the webhook route uses
_access: 'TRUE'(HMAC signature verification in the controller), placed directly above the_accessline per theDrupalPractice.Yaml.RoutingAccesssniff.Additional CI fixes:
- Modernized JS (
vartolet/const, named functions, JSDoc, method shorthand) for eslint compliance. CSS property ordering andrgba()torgb()for stylelint. Project spell-check dictionary for cspell.- Added
drupal/contact_storagetorequire-dev(21 phpunit failures in CI).- Fixed
drupalGet('/')toUrl::fromRoute('<front>')in onboarding test to avoidbase://URI error in CI's subdirectory web root.- Guarded
Url::fromRoute()in install hook with try/catch for CI environments.- Added
export-ignorerules in.gitattributesfor all dev tooling configs.The
1.0.xbranch and1.0.1tag have been updated with all fixes. Pipeline: https://git.drupalcode.org/project/akismet_antispam/-/pipelinesComment #6
vishal.kadamReleases should not be created after each review done here, since a review could ask for a change that is not backward compatible with the existing releases. Just using a development version avoids those BC issues.
Comment #7
vishal.kadamRest seems fine to me.
Please wait for other reviewers and Project Moderator to take a look and if everything goes fine, you will get the role.
Comment #8
avpadernoJust to make clear the point about not duplicating the Drupal core requirements in the composer.json file: Applicants should know that the Drupal.org Composer façade adds the Drupal core requirements for the project when the composer.json file does not contain them. Not repeating them avoids any issue caused by the composer.json file having different Drupal core requirements than the ones contained in the .info.yml file.
I cannot think of any workflow where Composer is not used, given that Composer is required by Drupal core to gather the dependencies it needs. Therefore, I cannot think of any case where the Drupal.org Composer façade is not involved when site builders are requiring the modules necessary for their sites.
Comment #9
scontzen commentedAutomated Review
GitLab CI enabled, pipeline looks correct.
Manual Review
1.0.x.automattic/akismet-sdkon Packagist) are both GPL-2.0-or-later.AkismetCheckData::spamQueueExclusionExpression()validates its alias against a strict regex before interpolation. The GDPR erasure form hashes the email, uses flood protection, returns a generic response either way, and logs only a truncated hash.Minor:
akismet.routing.ymldescribes the webhook route access as "HMAC signature verification in the controller" (lines 85–87 and 93), butAkismetWebhookController::handleVerdict()verifies a shared API key from the request body viahash_equals()(line 83), not HMAC. The comment could be updated to match the actual implementation.#[LegacyHook]shims, constructor property promotion, dependency injection, optional services via@?service.composer.jsonis present and does not requiredrupal/coredirectly.Still open from #3583592-8: [1.0.x] Akismet Anti-Spam: the
conflictentry fordrupal/core.Rest seems fine to me.
This review uses the Project Application Review Template.
Comment #10
derekspringer commentedThanks @scontzen for the thorough review in #9, and @avpaderno for the earlier guidance in #8. Both items are now addressed on the
1.0.xbranch (no new tag — keeping the application branch stable per the "don't release during SA review" guideline):akismet.routing.yml— the webhook controller never actually did HMAC signature verification; it does a constant-time shared API key comparison viahash_equals()against the resolved Akismet API key. The route comments (and a matchingCHANGELOG.mdline) now describe the real mechanism.conflict: drupal/core: "<10.3"incomposer.json— removed.akismet.info.ymlalready declarescore_version_requirement: ^10.3 || ^11, and the Drupal.org Composer façade injects the core constraint automatically, so the duplicate declaration was unnecessary.New
1.0.xtip: 6dd95e13. The1.0.1tag is unchanged. Happy to iterate further if anything else surfaces. Thank you again for your time! 🙇♂️Comment #11
scontzen commentedThanks for the quick turnaround, @derekspringer. Verified both changes on
6dd95e13:akismet.routing.yml(lines 85–87 and 94) now describe the actual mechanism: constant-time shared API key comparison viahash_equals().CHANGELOG.mdmatches.drupal/core: "<10.3"conflict is gone fromcomposer.json.akismet.info.yml'score_version_requirement: ^10.3 || ^11is enough via the Drupal.org Composer façade.Looks good to me.
Comment #12
avpadernoTo make clearer what I was trying to say about not duplicating the core requirements in the composer.json and in the .info.yml file: Add a composer.json file / Drupal core compatibility contains the following sentences.
I cannot think of any workflow where Composer is not used to load all the dependencies necessary to run a Drupal site. Therefore, I cannot think of a case where duplicating the core requirements is necessary.
This does not mean I think it is mandatory to remove the core requirements from the composer.json file. I think maintainers should know the Drupal.org Composer façade adds the core requirements to the composer.json file basing on the .info.yml file content. They can decide to duplicate that information in both the files, knowing that any mismatch between the information given in those files could cause issues with the project.
(I apologize for posting a new comment about the same topic. I just wanted to make clear what my thought about it is to people who read these applications.)
Comment #13
avpadernoThank you for your contribution and for your patience with the review process!
I am going to update your account so you can opt into security advisory coverage any project you create, including the projects you already created.
These are some recommended readings to help you with maintainership:
You can find more contributors chatting on Slack or IRC in #drupal-contribute. So, come hang out and stay involved!
Anyone is welcome to participate in the review process. Please consider reviewing other projects that are pending review. I encourage you to learn more about that process and join the group of reviewers.
I thank also all the reviewers for helping with these applications.
Comment #14
avpadernoComment #16
derekspringer commentedThank you everyone for your insight and reviews, I genuinely appreciate everyone's time and effort! 🙇♂️