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:

  1. Antibot Redirect — Identified dynamic property deprecation, static service calls, wrong dependency namespace. Set to Needs work.
  2. Status Block — Identified untranslatable strings, missing null safety, caching concerns. Set to Needs work.
  3. Accessibility Statement — No issues found, excellent code quality. Set to RTBC. (My reponse might've been rate-limited due to my account's newness)

Project link

https://www.drupal.org/project/akismet_antispam

Comments

derekspringer created an issue. See original summary.

vishal.kadam’s picture

Issue summary: View changes
avpaderno’s picture

Thank 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.

  • If you have not done it yet, you should enable GitLab CI for the project and fix the PHP_CodeSniffer errors/warnings it reports.
  • For the time this application is open, only your commits are allowed.
  • The purpose of this application is giving you a new drupal.org role that allows you to opt projects into security advisory coverage, either projects you already created, or projects you will create. The project status will not be changed by this application; once this application is closed, you will be able to change the project status from Not covered to Opt into security advisory coverage. This is possible only 14 days after the project is created.

    Keep in mind that once the project is opted into security advisory coverage, only Security Team members may change coverage.
  • Only the person who created the application will get the permission to opt projects into security advisory coverage. No other person will get the same permission from the same application; that applies also to co-maintainers/maintainers of the project used for the application.
  • We only accept an application per user. If you change your mind about the project to use for this application, or it is necessary to use a different project for the application, please update the issue summary with the link to the correct project and the issue title with the project name and the branch to review.

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.

  • It is preferable to wait for a project moderator before posting the first comment on newly created applications. Project moderators will do some preliminary checks that are necessary before any change on the project files is suggested.
  • Reviewers should show the output of a CLI tool only once per application.
  • It may be best to have the applicant fix things before further review.

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.

vishal.kadam’s picture

Status: Needs review » Needs work

1. trunk is 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.

    $form['image_toolkit'] = [
      '#type' => 'radios',
      '#title' => $this->t('Select an image processing toolkit'),
      '#config_target' => 'system.image:toolkit',
      '#options' => [],
    ];

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.

phpcs --standard=Drupal,DrupalPractice --extensions=php,module,inc,install,test,profile,theme,info,txt,md,yml akismet_antispam/
 
FILE: akismet_antispam/CODE-OF-CONDUCT.md
-------------------------------------------------------------------------
FOUND 0 ERRORS AND 30 WARNINGS AFFECTING 30 LINES
-------------------------------------------------------------------------
  7 | WARNING | Line exceeds 80 characters; contains 580 characters
 11 | WARNING | Line exceeds 80 characters; contains 264 characters
 13 | WARNING | Line exceeds 80 characters; contains 139 characters
 15 | WARNING | Line exceeds 80 characters; contains 90 characters
 21 | WARNING | Line exceeds 80 characters; contains 87 characters
 25 | WARNING | Line exceeds 80 characters; contains 155 characters
 27 | WARNING | Line exceeds 80 characters; contains 139 characters
 28 | WARNING | Line exceeds 80 characters; contains 128 characters
 29 | WARNING | Line exceeds 80 characters; contains 134 characters
 30 | WARNING | Line exceeds 80 characters; contains 143 characters
 31 | WARNING | Line exceeds 80 characters; contains 122 characters
 32 | WARNING | Line exceeds 80 characters; contains 108 characters
 37 | WARNING | Line exceeds 80 characters; contains 133 characters
 38 | WARNING | Line exceeds 80 characters; contains 95 characters
 39 | WARNING | Line exceeds 80 characters; contains 128 characters
 40 | WARNING | Line exceeds 80 characters; contains 141 characters
 44 | WARNING | Line exceeds 80 characters; contains 272 characters
 48 | WARNING | Line exceeds 80 characters; contains 612 characters
 52 | WARNING | Line exceeds 80 characters; contains 354 characters
 57 | WARNING | Line exceeds 80 characters; contains 144 characters
 60 | WARNING | Line exceeds 80 characters; contains 138 characters
 61 | WARNING | Line exceeds 80 characters; contains 329 characters
 62 | WARNING | Line exceeds 80 characters; contains 204 characters
 65 | WARNING | Line exceeds 80 characters; contains 142 characters
 66 | WARNING | Line exceeds 80 characters; contains 222 characters
 67 | WARNING | Line exceeds 80 characters; contains 221 characters
 70 | WARNING | Line exceeds 80 characters; contains 254 characters
 71 | WARNING | Line exceeds 80 characters; contains 287 characters
 74 | WARNING | Line exceeds 80 characters; contains 194 characters
 78 | WARNING | Line exceeds 80 characters; contains 359 characters
-------------------------------------------------------------------------

FILE: akismet_antispam/SECURITY.md
----------------------------------------------------------------------
FOUND 0 ERRORS AND 6 WARNINGS AFFECTING 6 LINES
----------------------------------------------------------------------
 17 | WARNING | Line exceeds 80 characters; contains 91 characters
 28 | WARNING | Line exceeds 80 characters; contains 91 characters
 34 | WARNING | Line exceeds 80 characters; contains 81 characters
 38 | WARNING | Line exceeds 80 characters; contains 88 characters
 65 | WARNING | Line exceeds 80 characters; contains 88 characters
 69 | WARNING | Line exceeds 80 characters; contains 111 characters
----------------------------------------------------------------------

FILE: akismet_antispam/akismet.routing.yml
--------------------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
--------------------------------------------------------------------------------
 90 | WARNING | Open page callback found, please add a comment before the line why there is no access restriction
--------------------------------------------------------------------------------

FILE: akismet_antispam/TESTING.md
----------------------------------------------------------------------
FOUND 0 ERRORS AND 29 WARNINGS AFFECTING 29 LINES
----------------------------------------------------------------------
   3 | WARNING | Line exceeds 80 characters; contains 97 characters
  44 | WARNING | Line exceeds 80 characters; contains 140 characters
  61 | WARNING | Line exceeds 80 characters; contains 107 characters
  70 | WARNING | Line exceeds 80 characters; contains 161 characters
  72 | WARNING | Line exceeds 80 characters; contains 112 characters
  87 | WARNING | Line exceeds 80 characters; contains 81 characters
  88 | WARNING | Line exceeds 80 characters; contains 84 characters
  90 | WARNING | Line exceeds 80 characters; contains 121 characters
 108 | WARNING | Line exceeds 80 characters; contains 327 characters
 112 | WARNING | Line exceeds 80 characters; contains 149 characters
 118 | WARNING | Line exceeds 80 characters; contains 139 characters
 122 | WARNING | Line exceeds 80 characters; contains 206 characters
 124 | WARNING | Line exceeds 80 characters; contains 291 characters
 126 | WARNING | Line exceeds 80 characters; contains 173 characters
 128 | WARNING | Line exceeds 80 characters; contains 117 characters
 132 | WARNING | Line exceeds 80 characters; contains 243 characters
 144 | WARNING | Line exceeds 80 characters; contains 206 characters
 158 | WARNING | Line exceeds 80 characters; contains 85 characters
 160 | WARNING | Line exceeds 80 characters; contains 116 characters
 163 | WARNING | Line exceeds 80 characters; contains 195 characters
 189 | WARNING | Line exceeds 80 characters; contains 117 characters
 195 | WARNING | Line exceeds 80 characters; contains 137 characters
 201 | WARNING | Line exceeds 80 characters; contains 169 characters
 205 | WARNING | Line exceeds 80 characters; contains 171 characters
 208 | WARNING | Line exceeds 80 characters; contains 89 characters
 214 | WARNING | Line exceeds 80 characters; contains 207 characters
 218 | WARNING | Line exceeds 80 characters; contains 119 characters
 226 | WARNING | Line exceeds 80 characters; contains 172 characters
 230 | WARNING | Line exceeds 80 characters; contains 87 characters
----------------------------------------------------------------------

FILE: akismet_antispam/README.md
----------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
----------------------------------------------------------------------
 78 | WARNING | Line exceeds 80 characters; contains 82 characters
----------------------------------------------------------------------

FILE: akismet_antispam/akismet.info.yml
--------------------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
--------------------------------------------------------------------------------
 1 | WARNING | Remove "project" from the info file, it will be added by drupal.org packaging automatically
--------------------------------------------------------------------------------

FILE: akismet_antispam/akismet.module
--------------------------------------------------------------------------------
FOUND 44 ERRORS AND 10 WARNINGS AFFECTING 34 LINES
--------------------------------------------------------------------------------
  24 | WARNING | [x] There must be no blank line following an inline comment
  24 | WARNING | [ ] There must be no blank line following an inline comment
  27 | ERROR   | [x] You must use "/**" style comments for a function comment
  27 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  32 | ERROR   | [x] Missing function doc comment
  37 | ERROR   | [x] Missing function doc comment
  42 | ERROR   | [x] Missing function doc comment
  47 | ERROR   | [x] Missing function doc comment
  52 | ERROR   | [x] Missing function doc comment
  57 | ERROR   | [x] Missing function doc comment
  62 | ERROR   | [x] Missing function doc comment
  62 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  66 | WARNING | [x] There must be no blank line following an inline comment
  66 | WARNING | [ ] There must be no blank line following an inline comment
  69 | ERROR   | [x] You must use "/**" style comments for a function comment
  74 | ERROR   | [x] Missing function doc comment
  74 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  74 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  79 | ERROR   | [x] Missing function doc comment
  79 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  84 | ERROR   | [x] Missing function doc comment
  84 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  89 | ERROR   | [x] Missing function doc comment
  89 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  94 | ERROR   | [x] Missing function doc comment
  94 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  99 | ERROR   | [x] Missing function doc comment
  99 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 104 | ERROR   | [x] Missing function doc comment
 104 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 109 | ERROR   | [x] Missing function doc comment
 109 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 113 | WARNING | [x] There must be no blank line following an inline comment
 113 | WARNING | [ ] There must be no blank line following an inline comment
 116 | ERROR   | [x] You must use "/**" style comments for a function comment
 116 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 121 | ERROR   | [x] Missing function doc comment
 121 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 125 | WARNING | [x] There must be no blank line following an inline comment
 125 | WARNING | [ ] There must be no blank line following an inline comment
 128 | ERROR   | [x] You must use "/**" style comments for a function comment
 133 | ERROR   | [x] Missing function doc comment
 138 | ERROR   | [x] Missing function doc comment
 138 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 138 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 143 | ERROR   | [x] Missing function doc comment
 143 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 147 | WARNING | [ ] There must be no blank line following an inline comment
 154 | ERROR   | [ ] Description for the @return value must be on the next line
 164 | WARNING | [ ] There must be no blank line following an inline comment
 174 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 185 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 196 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 200 | ERROR   | [x] Expected 1 newline at end of file; 2 found
--------------------------------------------------------------------------------
PHPCBF CAN FIX THE 47 MARKED SNIFF VIOLATIONS AUTOMATICALLY
--------------------------------------------------------------------------------

FILE: akismet_antispam/AGENTS.md
----------------------------------------------------------------------
FOUND 0 ERRORS AND 36 WARNINGS AFFECTING 36 LINES
----------------------------------------------------------------------
   5 | WARNING | Line exceeds 80 characters; contains 177 characters
  13 | WARNING | Line exceeds 80 characters; contains 85 characters
  14 | WARNING | Line exceeds 80 characters; contains 194 characters
  15 | WARNING | Line exceeds 80 characters; contains 147 characters
  16 | WARNING | Line exceeds 80 characters; contains 93 characters
  17 | WARNING | Line exceeds 80 characters; contains 85 characters
  18 | WARNING | Line exceeds 80 characters; contains 89 characters
  19 | WARNING | Line exceeds 80 characters; contains 113 characters
  50 | WARNING | Line exceeds 80 characters; contains 87 characters
  51 | WARNING | Line exceeds 80 characters; contains 179 characters
  52 | WARNING | Line exceeds 80 characters; contains 94 characters
  53 | WARNING | Line exceeds 80 characters; contains 129 characters
  54 | WARNING | Line exceeds 80 characters; contains 114 characters
  60 | WARNING | Line exceeds 80 characters; contains 192 characters
  61 | WARNING | Line exceeds 80 characters; contains 129 characters
  62 | WARNING | Line exceeds 80 characters; contains 168 characters
  63 | WARNING | Line exceeds 80 characters; contains 93 characters
  64 | WARNING | Line exceeds 80 characters; contains 185 characters
  65 | WARNING | Line exceeds 80 characters; contains 190 characters
  66 | WARNING | Line exceeds 80 characters; contains 152 characters
  67 | WARNING | Line exceeds 80 characters; contains 124 characters
  68 | WARNING | Line exceeds 80 characters; contains 126 characters
  69 | WARNING | Line exceeds 80 characters; contains 196 characters
  70 | WARNING | Line exceeds 80 characters; contains 162 characters
  71 | WARNING | Line exceeds 80 characters; contains 109 characters
  77 | WARNING | Line exceeds 80 characters; contains 394 characters
  83 | WARNING | Line exceeds 80 characters; contains 87 characters
  92 | WARNING | Line exceeds 80 characters; contains 88 characters
  95 | WARNING | Line exceeds 80 characters; contains 91 characters
  96 | WARNING | Line exceeds 80 characters; contains 86 characters
  97 | WARNING | Line exceeds 80 characters; contains 86 characters
  98 | WARNING | Line exceeds 80 characters; contains 88 characters
  99 | WARNING | Line exceeds 80 characters; contains 92 characters
 100 | WARNING | Line exceeds 80 characters; contains 84 characters
 105 | WARNING | Line exceeds 80 characters; contains 161 characters
 106 | WARNING | Line exceeds 80 characters; contains 134 characters
----------------------------------------------------------------------

FILE: akismet_antispam/CONTRIBUTING.md
----------------------------------------------------------------------
FOUND 0 ERRORS AND 2 WARNINGS AFFECTING 2 LINES
----------------------------------------------------------------------
   3 | WARNING | Line exceeds 80 characters; contains 157 characters
 151 | WARNING | Line exceeds 80 characters; contains 130 characters
----------------------------------------------------------------------

FILE: akismet_antispam/scripts/manual-test-helper.php
--------------------------------------------------------------------------------
FOUND 1 ERROR AND 2 WARNINGS AFFECTING 3 LINES
--------------------------------------------------------------------------------
  7 | WARNING | [ ] Line exceeds 80 characters; contains 94 characters
 11 | WARNING | [ ] Line exceeds 80 characters; contains 83 characters
 28 | ERROR   | [x] Comments may not appear after statements
--------------------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
--------------------------------------------------------------------------------

FILE: akismet_antispam/akismet.install
--------------------------------------------------------------------------------
FOUND 7 ERRORS AND 7 WARNINGS AFFECTING 14 LINES
--------------------------------------------------------------------------------
   1 | ERROR   | [x] Missing file doc comment
  19 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
  23 | WARNING | [ ] Line exceeds 80 characters; contains 83 characters
  35 | WARNING | [ ] Line exceeds 80 characters; contains 88 characters
  55 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 394 | WARNING | [ ] Hook implementations should not duplicate @return documentation
 399 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 434 | WARNING | [ ] Line exceeds 80 characters; contains 84 characters
 477 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 480 | ERROR   | [x] Namespaced classes/interfaces/traits should be referenced with use statements
 503 | ERROR   | [ ] Parameter tags must be defined first in a doc comment
 509 | WARNING | [ ] Line exceeds 80 characters; contains 85 characters
 538 | WARNING | [ ] Only string literals should be passed to t() where possible
 722 | WARNING | [ ] Hook implementations should not duplicate @return documentation
--------------------------------------------------------------------------------
PHPCBF CAN FIX THE 6 MARKED SNIFF VIOLATIONS AUTOMATICALLY
--------------------------------------------------------------------------------
derekspringer’s picture

Status: Needs work » Needs review

Thanks 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.x release branch on drupal.org per the naming conventions. The 1.0.1 tag targets this branch.

2. composer.json — drupal/core
Removed drupal/core from require. Added a conflict rule for drupal/core: &lt;10.3 so 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 |t filter with placeholders, so translators can reorder the phrase. (Note: {% trans %} blocks were initially used but Drupal's TwigNodeTrans compiler silently strips filters like number_format from variables inside trans blocks, so we reverted to explicit |t calls.)

4. #config_target in AkismetSettingsForm
Migrated strictness, privacy_notice, and show_comment_count to #config_target. The parent ConfigFormBase::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 in submitForm().

5. PHP_CodeSniffer and GitLab CI
- Added .gitlab-ci.yml using the Drupal Association template.
- Fixed root cause: phpcs.xml.dist was missing the extensions arg, silently skipping .module and .install files. 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 (proper Implements hook_xxx(). doc blocks, use statements replacing inline FQCNs), akismet.install (declare ordering, use statements, line wrapping, tag placement), and scripts/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 project from akismet.info.yml.
- Added comment documenting why the webhook route uses _access: 'TRUE' (HMAC signature verification in the controller), placed directly above the _access line per the DrupalPractice.Yaml.RoutingAccess sniff.

Additional CI fixes:
- Modernized JS (var to let/const, named functions, JSDoc, method shorthand) for eslint compliance. CSS property ordering and rgba() to rgb() for stylelint. Project spell-check dictionary for cspell.
- Added drupal/contact_storage to require-dev (21 phpunit failures in CI).
- Fixed drupalGet('/') to Url::fromRoute('&lt;front&gt;') in onboarding test to avoid base:// URI error in CI's subdirectory web root.
- Guarded Url::fromRoute() in install hook with try/catch for CI environments.
- Added export-ignore rules in .gitattributes for all dev tooling configs.

The 1.0.x branch and 1.0.1 tag have been updated with all fixes. Pipeline: https://git.drupalcode.org/project/akismet_antispam/-/pipelines

vishal.kadam’s picture

Releases 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.

vishal.kadam’s picture

Rest 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.

avpaderno’s picture

Just 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.

scontzen’s picture

Automated Review

GitLab CI enabled, pipeline looks correct.

Manual Review

Individual user account
Yes, follows the guidelines for individual user accounts.
No duplication
Yes. The D7 akismet project has no D10/11 release. Honeypot and Antibot use client-side heuristics, this one goes through the Akismet cloud API.
Master Branch
Yes, uses 1.0.x.
Licensing
Yes. Module and SDK dependency (automattic/akismet-sdk on Packagist) are both GPL-2.0-or-later.
3rd party assets/code
Yes. The SDK is pulled via Composer, not bundled.
README.txt/README.md
Yes, README.md with all required sections.
Code long/complex enough for review
Yes, plenty going on. Custom DB schema, service layer, OO hooks, event subscribers, action plugins, queue workers, webhook controller, views integration, Drush commands. Tests across Unit, Kernel and Functional.
Secure code
Yes, meets the security requirements. Queries are parameterized, the one raw SQL fragment in 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.yml describes the webhook route access as "HMAC signature verification in the controller" (lines 85–87 and 93), but AkismetWebhookController::handleVerdict() verifies a shared API key from the request body via hash_equals() (line 83), not HMAC. The comment could be updated to match the actual implementation.

Coding style & Drupal API usage
Agreeing with @vishal.kadam that the code looks solid. OO hooks with #[LegacyHook] shims, constructor property promotion, dependency injection, optional services via @?service. composer.json is present and does not require drupal/core directly.

Still open from #3583592-8: [1.0.x] Akismet Anti-Spam: the conflict entry for drupal/core.

Rest seems fine to me.

This review uses the Project Application Review Template.

derekspringer’s picture

Thanks @scontzen for the thorough review in #9, and @avpaderno for the earlier guidance in #8. Both items are now addressed on the 1.0.x branch (no new tag — keeping the application branch stable per the "don't release during SA review" guideline):

  1. Stale "HMAC" wording in akismet.routing.yml — the webhook controller never actually did HMAC signature verification; it does a constant-time shared API key comparison via hash_equals() against the resolved Akismet API key. The route comments (and a matching CHANGELOG.md line) now describe the real mechanism.
  2. Redundant conflict: drupal/core: "<10.3" in composer.json — removed. akismet.info.yml already declares core_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.x tip: 6dd95e13. The 1.0.1 tag is unchanged. Happy to iterate further if anything else surfaces. Thank you again for your time! 🙇‍♂️

scontzen’s picture

Thanks for the quick turnaround, @derekspringer. Verified both changes on 6dd95e13:

  • The webhook route comments in akismet.routing.yml (lines 85–87 and 94) now describe the actual mechanism: constant-time shared API key comparison via hash_equals(). CHANGELOG.md matches.
  • The redundant drupal/core: "&lt;10.3" conflict is gone from composer.json. akismet.info.yml's core_version_requirement: ^10.3 || ^11 is enough via the Drupal.org Composer façade.

Looks good to me.

avpaderno’s picture

To 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.

If your project does have a composer.json file without a  drupal/core version requirement the Drupal's composer facade will generate the appropriate metadata based on the info.yml file.server will add a drupal/core version requirement if it is not already present.

The composer facade can not generate this metadata for GIT based checkouts that do not use the facade. As a maintainer to ensure compatibility with composer an entry for drupal/core may be required in your composer.json file.

If you place a drupal/core version requirement in your composer.json file's require section, then it should be compatible with the same version set in the info file's core_version_requirement. A mismatch in compatibility may create a situation that Composer will download a module that can not be installed by Drupal, or that a user may install a module obtained manually when composer would not permit download.

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.)

avpaderno’s picture

Assigned: Unassigned » avpaderno
Status: Needs review » Reviewed & tested by the community

Thank 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.

avpaderno’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -PAreview: review bonus

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

derekspringer’s picture

Thank you everyone for your insight and reviews, I genuinely appreciate everyone's time and effort! 🙇‍♂️

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.