Config Guardian elevates Drupal configuration management to an enterprise standard. It provides a comprehensive safety net for development teams by introducing point-in-time snapshots, advanced impact analysis, and a reliable rollback engine.
The module follows strict coding standards, implements dependency injection, sanitizes user input in Twig templates, and uses secure JSON serialization for data storage. It has been validated with PHPCS (Drupal Standards) and manual security review.
Project link
https://www.drupal.org/project/config_guardian
AI Disclosure
AI tools were used during the development of this module to assist with boilerplate code generation, test scaffolding, and documentation drafting. All code has been reviewed, tested, and validated by the maintainer.
Comments
Comment #2
rushikesh raval commentedThank 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.
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 #3
rushikesh raval commentedComment #4
rushikesh raval commentedComment #5
rushikesh raval commentedComment #6
rushikesh raval commentedRemember to change status, when the project is ready to be reviewed. In this queue, projects are only reviewed when the status is Needs review.
Comment #7
andresmoreno28 commentedComment #8
rushikesh raval commentedFix issue reported from the phpcs job
Comment #9
andresmoreno28 commentedAll CI jobs are now passing and it's ready for review. Thank you so much!
Comment #10
andresmoreno28 commentedI hope you guys are having a great week.
I'm writing just to gently bump this issue. I updated the module code addressing all the previous feedback. Please let me know if there are any outstanding blockers or if there is anything else I can do to facilitate the review process.
Thanks again for your time and your volunteer work reviewing these applications!
Comment #11
rushikesh raval commentedJust go through comment #2. Reviewers will review your code and will update here.
Comment #12
vishal.kadam1. FILE: composer.json
As a side note, it is not necessary to add the Drupal core requirements in the /composer.json/ file: The Drupal.org Composer Façade will add them.
2. FILE: config_guardian.module
For a new module that aims to be compatible with Drupal 10 and Drupal 11, I would rather implement hooks as class methods as described in Support for object oriented hook implementations using autowired services.
Drupal does not have primary and secondary hooks. Instead of that, it is preferable to use the usual description: “Hook implementations for the [module name] module”, where [module name] is the name of the module given in its .info.yml file.
3. FILE: src/Commands/ConfigGuardianCommands.php
FILE: src/Controller/ActivityController.php
FILE: src/Controller/AjaxController.php
FILE: src/Controller/AnalysisController.php
FILE: src/Controller/ConfigSyncController.php
FILE: src/Controller/DashboardController.php
FILE: src/Controller/DependencyGraphController.php
FILE: src/Controller/SnapshotController.php
FILE: src/EventSubscriber/ConfigEventSubscriber.php
FILE: src/Form/ConfigExportForm.php
FILE: src/Form/ConfigImportForm.php
FILE: src/Form/RollbackForm.php
FILE: src/Form/SnapshotCreateForm.php
FILE: src/Form/SnapshotDeleteForm.php
FILE: src/Form/SnapshotImportForm.php
FILE: src/Service/ActivityLoggerService.php
FILE: src/Service/ConfigAnalyzerService.php
FILE: src/Service/ConfigSyncService.php
FILE: src/Service/RollbackEngineService.php
FILE: src/Service/SettingsService.php
FILE: src/Service/SnapshotManagerService.php
New modules, which are compatible with Drupal 10 and higher versions are expected to include type declarations in property definitions, and use constructor property promotion.
4. FILE: src/Controller/ActivityController.php, src/Controller/DashboardController.php
Since that class does not use methods from the parent class, it does not need to use
ControllerBaseas parent class. Controllers do not need to have a parent class; as long as they implement\Drupal\Core\DependencyInjection\ContainerInjectionInterface, they are fine.5. FILE: src/Form/SettingsForm.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.
For this change, it is necessary to require at least Drupal 10.3, but that is not an issue, since Drupal 10.2.x is no longer supported.
Comment #13
vishal.kadamComment #14
andresmoreno28 commentedThank you for the detailed feedback. I have addressed all the points raised:
1. composer.json: Removed `drupal/core` from require section, keeping only `php: >=8.1`.
2. OOP Hooks: Migrated all hooks to a new `ConfigGuardianHooks` class using `#[Hook()]` attributes as described in [Support for object oriented hook implementations](https://www.drupal.org/node/3442349). The class is registered as a service with the `drupal.hook` tag.
3. Constructor Property Promotion: Applied PHP 8.1+ constructor property promotion to all 22 files with dependency injection (services, controllers, forms, commands, and event subscriber).
4. ContainerInjectionInterface: Changed `ActivityController` and `DashboardController` from `extends ControllerBase` to `implements ContainerInjectionInterface` since they don't use any methods from the parent class.
5. #config_target: Updated `SettingsForm` to use `#config_target` for all simple form elements. The `exclude_patterns` field still requires manual handling due to the array-to-textarea conversion.
6. Module file description: Updated to "Hook implementations for the Config Guardian module."
All changes have been tested and the module is working correctly. Ready for re-review.
Comment #15
vishal.kadamFILE: config_guardian.module
Since the module is declared compatible with Drupal 10.2, removing the function implementing the hook is not possible. The function still needs to be defined, but it calls the method defined by the service class, as described in Support for object oriented hook implementations using autowired services (Backwards-compatible Hook implementation for Drupal versions from 10.1 to 11.0).
Comment #16
andresmoreno28 commentedThank you for the feedback again. I have updated the implementation to maintain backwards compatibility with Drupal 10.2:
The `config_guardian.module` file now contains hook functions that delegate to the OOP service class methods.
This ensures the module works on:
- **Drupal 10.2**: Uses the procedural hooks in `.module`
- **Drupal 10.3+/11**: Uses the `#[Hook()]` attributes in the service class
Ready for re-review.
Comment #17
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 #18
andresmoreno28 commentedThank you so much, vishal.kadam! I'll wait for other reviewers and PMs. :)
Comment #19
andresmoreno28 commentedI am writing to follow up on this application. It has been about a month since the last community review, which appeared to be positive, so I wanted to touch base to see if there is anything else required on my end.
I understand the queue can be busy, but I wanted to make sure the application hasn't slipped through the cracks. I am eager to move forward with the security advisory coverage opt-in.
Thank you very much for your time and help!!
Comment #20
rushikesh raval commentedChaning the priority as per Issue Priorities.
Comment #21
nkmaniHi andresmoreno28,
I briefly reviewed your code ... my comments are purely based on what would be expected in terms of testing;
- The README has instructions for running tests and you claim that tests are included for new features. But I don't see any tests folder.
- There are modules like config_ignore, config_split to name a few, that modify the configuration. I would be curious to know how your module works along side others.
Thanks!
Comment #22
avpadernoComment #23
andresmoreno28 commentedHi, thanks for taking the time to review the module and for the feedback.
You're absolutely right about the tests — that was an oversight on my part. I had them locally but they never made it into the repository. I've now pushed the full test suite: 132 tests (73 unit + 59 kernel) covering all services, models, event subscribers, and the installation schema. They all pass on the CI pipeline.
I also took the opportunity to fix a couple of minor bugs the tests uncovered in the filtered count queries, and corrected some inaccuracies in the README.
Regarding compatibility with other configuration modules: I've added a dedicated section to the README explaining how Config Guardian works alongside config_split, config_ignore, and config_readonly. In short, Config Guardian operates at the storage level — it captures raw snapshots of both the active and sync configuration directories without interfering with how other modules filter, split, or lock configuration during import/export. It essentially provides a safety net (snapshots + rollback) that complements whatever configuration workflow you have in place.
Let me know if there's anything else I should address. Thanks again for the review!
Comment #24
batigolixIf the module code includes any AI generated content, then it is a good practices to disclose this (on the project page or in the application). See https://www.drupal.org/docs/develop/issues/issue-procedures-and-etiquett...
Comment #25
andresmoreno28 commentedComment #26
andresmoreno28 commentedHi, batigolix. You're right — AI tools were used during development to assist with scaffolding, boilerplate, and documentation drafts. I've added an AI Disclosure section to this application as recommended. Everything has been reviewed and tested, but I appreciate you pointing to the guidelines. Let me know if there's anything else that needs attention.
Comment #27
batigolixThanks.
I was not clear in my previous comment. With "application" I meant the module, or the code. Not this security coverage application.
So the guideline asks to disclose AI usage. You could do that on the project page, or in the readme. Here is an example of how to include it in the project page: https://www.drupal.org/project/mcp_server
Comment #28
andresmoreno28 commentedThanks for clarifying.
I've added an AI Disclosure section to the README acknowledging that AI tools were used to assist with boilerplate generation, test scaffolding, and documentation drafting. All code has been reviewed, tested, and validated.
Comment #29
batigolixLooks great!
Comment #30
andresmoreno28 commentedThanks for the review! If there's anything else I should address, please let me know. :)
Comment #31
andresmoreno28 commentedSince it's been more than 3 weeks since the last response, I'm changing the priority as stipulated in Issue priorities.
Comment #32
andresmoreno28 commentedSince it's been more than 8 weeks since the last response, I'm changing the priority as stipulated in Issue priorities.
Thank you!
Comment #33
avpadernosrc/Controller/AjaxController.php
Since that class does not use methods from the parent class, or it uses a single method from the parent class, it does not need to use
ControllerBaseas parent class.Controllers do not need to have a parent class; as long as they implement
\Drupal\Core\DependencyInjection\ContainerInjectionInterface, they are fine.src/Form/ConfigExportForm.php
For translatable strings that change basing on a number (as in this case), there is
::formatPlural().src/Form/ConfigImportForm.php
For outputting HTML markup, it is probably better to use template files, or a rendering class.
src/Model/RiskAssessment.php
If those descriptions are visible in the user interface, they should be translatable string.
src/Service/ConfigAnalyzerService.php
If those strings are visible in the user interface, they should be translatable strings, for which the English version is provided.
src/Service/RollbackEngineService.php
Those strings should be translatable strings. As such, they should use placeholders, instead of string concatenation.
Comment #34
andresmoreno28 commentedHi,
Thanks for the review. I've pushed the fixes on 1.0.x (commit a015834), applying each point across the rest of the module too. PHPCS clean and unit tests passing.
Could you take another look when you have a moment? Thanks!
Comment #35
andresmoreno28 commentedGood afternoon! Is there anything else that needs to be fixed, or is it all set? Sorry for the insistence, and thank you very much!
Comment #36
vishal.kadamI am changing priority as per Issue priorities.
Comment #37
avpadernosrc/Controller/AjaxController.php
the @count placeholder needs to be used also for the singular form, since in some languages the singular form is used also for numbers which in English are plural. For example, in Russian it is 101 яблоко and 1 яблоко, where яблоко means apple.
src/Controller/AnalysisController.php
With Drupal 10.3 and higher versions, a controller class does not need to implement
create(); usingAutowireTrait, it just needs to implement the class constructor.The core requirements need to be changed, but that is not an issue, since Drupal 10.3 is no longer supported. Given the recent releases that fixed security issues, it is probably better to require at least Drupal 10.5.x or Drupal 10.6.x.
src/Controller/DependencyGraphController.php
Since that class does not use methods from the parent class, or it uses a single method from the parent class, it does not need to use
ControllerBaseas parent class.Controllers do not need to have a parent class; as long as they implement
\Drupal\Core\DependencyInjection\ContainerInjectionInterface, they are fine.For outputting HTML markup, it is probably better to use template files, or a rendering class, especially when the markup is complex.
src/EventSubscriber/ConfigEventSubscriber.php
In the case of exceptions, it is probably also important to log the backtrace, which allows to better understand what caused the exception. For that, Drupal has
Error::logException().Comment #38
andresmoreno28 commentedThanks for the thorough review, @avpaderno. I've addressed every point, and since each one can apply in other places, I went through the whole module rather than only the cited lines.
formatPlural()singular formEvery
formatPlural()/PluralTranslatableMarkupcall now uses@countin the singular form as well — across controllers, forms, services, batch operations and the Twig templates:Controllers use
AutowireTraitinstead ofcreate()No controller implements
create()anymore; they implementContainerInjectionInterfaceand useAutowireTrait, with custom services wired through#[Autowire].core_version_requirementis now^10.5 || ^11(and thesystemdependency follows).No
ControllerBaseNo controller extends
ControllerBasenow.DependencyGraphControllerinjects theModuleHandlerInterface,LanguageManagerInterfaceandRendererInterfaceit actually uses.HTML markup
The dependency-graph page is no longer built from a heredoc; it is rendered from a dedicated Twig template (
config-guardian-dependency-graph.html.twig). Other inline markup in the forms was moved to templates,html_tagrender elements, or render arrays.Exceptions logged with
Error::logException()Every catch block that logs an exception now records the backtrace, and
ConfigEventSubscriber/ConfigSyncServicewere given an injected logger instead of\Drupal::logger():While going through the code I also removed three JavaScript files that were not loaded by any library (one depended on an undeclared third-party
d3library) and switched the snapshot details output to#plain_text.Everything is on the
1.0.xbranch (latest commitfd9395c). PHP_CodeSniffer (Drupal,DrupalPractice) is clean and the unit tests pass. Back to "Needs review" — thanks again for your time.Comment #39
andresmoreno28 commentedComment #40
avpadernoconfig_guardian.info.yml
Requiring the System module is not necessary, since that module is always installed in any Drupal site. That line was used to require a specific Drupal core version, but that is no longer necessary now that Drupal core uses
core_version_requirement.config_guardian.services.yml
Services can be autowired starting with Drupal 9.3.x. This means that it is no longer necessary to give a list of service arguments in the .services.yml file; they will be retrieved from the constructor definition.
src/Hook/ConfigGuardianHooks.php
It is preferable to initialize the string translation service by calling
::setStringTranslation().StringTranslationTraitcalls\Drupal::service('string_translation')for backward-compatibility reasons, but the service object should be explicitly initialized.It is preferable to store the logger factory in a class property. Storing a logger instance is considered an anti-pattern by some developers.
src/Controller/DashboardController.php
Using the class namespaced-name as service ID, it is not necessary to use
#[Autowire()]for those parameters. The new service ID can be added as alias.I would also suggest creating another alias using the identifier of the interface implemented by the service class.
src/Form/ConfigExportForm.php
It is better to use @count new configuration for the singular, if
::formatPlural()is used. Otherwise, it is not necessary to use::formatPlural().src/Form/RollbackForm.php
This is instead a case where
::formatPlural()should be used. I would also just use<strong>@total</strong> change/<strong>@total</strong> changes.Comment #41
andresmoreno28 commentedThanks again, avpaderno. All six points are addressed, applied across the whole module:
config_guardian.info.yml— Removed thesystemdependency (anduser; both are always installed). Addeddrupal:file, which providesfile_save_upload()used by the snapshot import form.config_guardian.services.yml— Switched to autowiring: each service is registered by its class name withautowire: trueand a short-name alias; the argument lists are gone.#[Autowire(service: '…')]remains only on the genuinely ambiguous parameters (the two config storages,lock,event_dispatcher). I also removed several unused injected dependencies found along the way.ConfigGuardianHooks— String translation is now initialised explicitly viasetStringTranslation()(through the service definition), and the logger factory is stored in a property instead of a logger instance. Same treatment for the other classes using the trait.DashboardController— Since services are registered under their class name (with aliases), the#[Autowire]attributes are no longer needed; removed from all controllers.ConfigExportForm/formatPlural()— The singular now uses@count, and singular/plural are never identical: where a noun is counted I use real forms (@count new configuration/@count new configurations), and where the label does not change with the count I use plaint()with an@countplaceholder. Applied to everyformatPlural()call in the module.RollbackForm— The total-changes line now uses::formatPlural()(@count change/@count changes).While going through the code I also removed routing parameter converters for a non-existent entity type, generated all URLs from routes instead of hard-coding paths, mapped activity action names to translatable labels in PHP instead of passing dynamic values to the Twig
|tfilter, and replacedfetchAll(\PDO::FETCH_ASSOC)(deprecated in 11.2) with a version-neutral conversion.PHP_CodeSniffer (Drupal, DrupalPractice) is clean and the unit and kernel tests pass on the
1.0.xbranch (latest commitb85ffc5). Back to "Needs review" — thanks for your patience.Comment #42
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 #43
avpadernoComment #45
andresmoreno28 commentedThank you all very much. I really appreciate the time and the thorough, patient
feedback throughout the review.
Special thanks to @avpaderno for the detailed, line-by-line reviews across several
rounds, and to @rushikesh raval, @vishal.kadam, @nkmani and @batigolix for taking
the time to look at the project and raise useful points (tests, module
interactions, coding standards and API usage). Every comment made Config Guardian
noticeably better.
All the feedback has been applied across the whole module, and the improvements
are now released in 1.0.1. Thanks again to the reviewers and to everyone keeping
the application queue going.
Andrés M.