drupal / mutamad_project
Project template for the Mutamad distribution.
Package info
git.drupalcode.org/project/mutamad_project.git
Type:project
pkg:composer/drupal/mutamad_project
Requires
- composer/installers: ^2.3
- cweagans/composer-patches: ~2.0
- drupal/canvas_translate: ^1.0@alpha
- drupal/core-composer-scaffold: ~11.4
- drupal/core-project-message: ~11.4
- drupal/core-recipe-unpack: ~11.4
- drupal/core-recommended: ~11.4
- drupal/core-vendor-hardening: ~11.4
- drupal/entityqueue_form_order: ^1.0@alpha
- drupal/mutamad: ~1.0.0
- drupal/tfa: ^2.0@alpha
- drupal/ultimate_cron: ^2.0@beta
- drush/drush: ~13
- enyo/dropzone: 5.9.3
- yaireo/tagify: 4.35.4
Requires (Dev)
- drupal/coder: ^8.3
- drupal/core-dev: ~11.4
- drupal/storybook: ^1.0
- mglaman/phpstan-drupal: ^2
- phpstan/phpstan-deprecation-rules: ^2
Suggests
None
Provides
None
Conflicts
- drupal/drupal: *
- drupal/gin_login: <2.1.5
Replaces
None
This package is auto-updated.
Last update: 2026-11-15 09:05:33 UTC
README
Mutamad project template
What an adopter create-projects. It pins Drupal core and the Mutamad profile,
scaffolds a settings fragment, and ships the DDEV environment.
Built and maintained by iSpectra — Imagine. Implement. Inspire. See iSpectra on drupal.org.
composer create-project drupal/mutamad_project my-site
cd my-site
ddev start
ddev install-mutamad
ddev install-mutamad installs in Arabic; ddev install-mutamad --lang=en
installs in English. Either way the site has both languages, each behind its
URL prefix (/ar, /en), and the language it was installed in is the
default before the first field exists — which matters: Drupal fixes a field's
language when the field is created.
What install-mutamad does
It runs the installer — drush site:install mutamad with the site template —
and nothing else. The
profile and its recipes hold everything the site needs, including what recipes
cannot do: making settings.php load settings.mutamad.php, creating the private
files directory outside the web root, and generating the MFA encryption key.
Then it runs mutamad-verify on the result, and exits with its status.
Checking the controls
ddev mutamad-verify
Checks every setting a Mutamad control rests on, on the running site, and fails if any has drifted. Each check is tagged with the control it evidences (NCA ECC/DCC, DGA). It reads behaviour, not just settings: the cookie a real login receives, the headers a real response carries, whether a private file can be downloaded anonymously.
CI runs the same checks on a fresh install (mutamad-verify job), plus a
WCAG 2 AA scan of the Arabic and English pages (mutamad-accessibility, not
yet blocking).
Hardened settings
assets/settings.mutamad.php and assets/services.mutamad.yml are scaffolded
into web/sites/default/:
| Setting | Control |
|---|---|
Private files in private-files/, beside web/ — outside the web root, so no web server rule is needed to protect them | ECC 3-2-3 |
| Session cookie ends when the browser closes; a session unused for 12 hours is discarded; strict session IDs | ECC 2-2-4 |
update.php cannot be run anonymously | ECC 3-2-2 |
No X-Powered-By header | ECC 3-2-1 |
| Refuses to run on PHP older than 8.3 | ECC 10-2 |
Every log entry to the database log and to syslog as one JSON line — facility LOCAL0, ident mutamad — through Monolog (mutamad_logging). The host must run a syslog daemon that forwards LOCAL0 to the SIEM | ECC 12-2-1 |
Multi-factor authentication
Administrators, site administrators and content approvers must
set up an authenticator app (mutamad_crypto). Each
gets 3 logins to do it, then is refused until it is done — including
webmaster. composer.json allows drupal/tfa's alpha on purpose: TFA 2 has
no stable Drupal 11 release, and only the root composer.json can allow one.
It allows drupal/canvas_translate's alpha for the same reason: Canvas Pages
are translated with it (mutamad_canvas). And drupal/entityqueue_form_order's:
editors add a question, a person or a life journey to its queue from the
edit form with it (mutamad_faq, mutamad_people, mutamad_life_journeys).
Libraries served by the site
The Content Security Policy loads scripts and styles from the site only, so a
library a module would fetch from a CDN must be under web/libraries instead —
which also suits a closed network. composer.json installs two there, each
from a package repository in the form its module's README gives, since
neither is on Packagist:
| Library | Package | Used by |
|---|---|---|
| Tagify | yaireo/tagify | The tags widget (News's Tags) |
| Dropzone | enyo/dropzone, the official release | Media bulk upload (mutamad_media) |
Only the root composer.json can declare a repository, which is why the
recipes that need these libraries do not require them. ddev mutamad-verify
fails if either library is missing.
Storybook
The Mutamad theme's components, each rendered by this site, with its settings as controls, in English and Arabic, and an accessibility check per story. For development sites only.
ddev storybook init # the Storybook module, its permission, CORS; the stories' JSON
ddev storybook start # https://<site>.ddev.site:6006
ddev storybook gen # after changing a component's stories
ddev storybook build # a static Storybook in storybook-static/
ddev storybook disable # turn the site's side off again
init copies assets/services.storybook.yml into web/sites/default/:
CORS for the Storybook app and the module's development mode.
settings.mutamad.php loads it under DDEV only (IS_DDEV_PROJECT), so
neither reaches a site that faces the public. The app's settings are in
.storybook/, its packages in package.json, and its port in
.ddev/config.storybook.yaml. For a theme generated from the Mutamad
theme, set STORYBOOK_THEME to its folder.
Before this site faces the public
- Set
$settings['trusted_host_patterns']inweb/sites/default/settings.mutamad.php. Without it Drupal answers to anyHostheader, which makes password-reset poisoning trivial. - Read the hash salt from a file outside the repository, and turn on
read-only configuration in production — both are shown, commented, in
settings.mutamad.php. On nginx, let browsers cache the static files, fonts included. Apache needs nothing: Drupal's
.htaccesscaches every file for a year. nginx follows its own configuration, and one with no rule for fonts sends the theme's IBM Plex Sans Arabic with none, so a browser checks it again on every page (Lighthouse: "Serve static assets with an efficient cache policy"). In the site'sserverblock:location ~* \.(css|js|woff2?|ttf|otf|eot|jpe?g|gif|png|webp|svg|ico)$ { try_files $uri @rewrite; expires max; log_not_found off; }Drupal's aggregated CSS and JavaScript change address when they change, so they can be cached for good too. The development site (DDEV) caches fonts the same way (
.ddev/nginx/fonts.conf), and leaves CSS and JavaScript uncached while the theme is being worked on.- Send email through the entity's mail relay: set
MUTAMAD_MAILER_DSNin the web server's environment, for examplesmtp://user:password@relay.example.gov.sa:587. Without it, Drupal sends by the server's ownsendmail, and on a server with none every email — the workflow's notifications, password resets — is lost without an error. The site's email address must be on the entity's domain, covered by its SPF, DKIM and DMARC records. Seesettings.mutamad.php. Run cron every minute, from the server: Mutamad turns core's Automated Cron off (
mutamad_logging), so nothing runs it otherwise — and without cron the site stops checking for security updates, expiring passwords and trimming its log, with no error. In the crontab of the user that owns the site:* * * * * cd /var/www/example.gov.sa && vendor/bin/drush cron --quietDDEV does it already (
.ddev/config.cron.yaml).
Licence
GPL-2.0-or-later.
Maintainers
Built and maintained by iSpectra (on drupal.org).
