Comment Zana vérifie la sécurité de chaque projet

Des réglages sûrs par défaut, un refus de démarrer en cas de déploiement dangereux, une commande qui vérifie toute l’application, un contrôle du serveur, et des audits dont les résultats sont corrigés et publiés. Avec les résultats réels d’un projet créé aujourd’hui.

Un bouclier bleu avec une coche dorée, relié à quatre étapes validées autour de lui.

La plupart des problèmes de sécurité des applications web ne sont pas des attaques ingénieuses. Ce sont des erreurs ordinaires : un mode debug resté actif en production, un secret copié d’un fichier d’exemple, un formulaire sans protection CSRF, un dossier de tests envoyé sur le serveur, un réglage de proxy qui fait confiance à tout Internet. Chacune est facile à commettre, et facile à manquer lors d’une relecture.

La réponse de Zana est de vérifier, à chaque étape de la vie d’un projet, ce que les gens oublient. Une application Zana est sûre par défaut quand on l’écrit. Elle refuse de démarrer si elle est déployée de façon dangereuse, elle est vérifiée par une seule commande avant chaque mise en ligne, puis de nouveau sur le serveur. Le framework lui-même passe des contrôles de sécurité, et des audits dont les résultats sont corrigés et publiés.

Tous les résultats affichés dans cet article sont réels. Ils viennent d’un projet créé aujourd’hui avec Zana 2.19, et de ce site.

1. Sûr par défaut, avant la première ligne de code

Un nouveau projet créé avec composer create-project mj-zana/skeleton-web envoie déjà des en-têtes de sécurité stricts sur chaque page :

X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: DENY
Cross-Origin-Opener-Policy: same-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none'; form-action 'self'; script-src 'self' 'nonce-3ej5GK7yHB_iS1i7EXZsHWRO'; img-src 'self' data:

La politique de sécurité du contenu n’exécute que les scripts du site lui-même, ou les scripts intégrés qui portent un nonce renouvelé à chaque requête. Même si un attaquant parvient à injecter une balise script, le navigateur ne l’exécutera pas.

Le reste suit la même idée : le choix sûr est intégré, et ne coûte qu’une ligne, voire aucune.

  • Les formulaires sont protégés contre le CSRF par un middleware sur la route ; les écrans de back-office générés par Zana l’ajoutent pour vous. Un formulaire envoyé depuis un autre site, ou sans jeton valide, est refusé. Sur ce site, une demande de projet envoyée sans son jeton reçoit 403 Forbidden. Un jeton d’accès ne dispense une requête de ce contrôle que s’il a été signé par l’application elle-même.
  • Le spam et les attaques par force brute sont limités. Deux réglages ajoutent un champ piège caché et un temps de remplissage minimum, qui arrêtent la plupart des robots sans CAPTCHA. #[RateLimit] limite la fréquence à laquelle un visiteur peut envoyer un formulaire. Les tentatives de connexion sont limitées par compte et par adresse.
  • Les mots de passe sont hachés avec Argon2id (ou bcrypt, en tenant compte de chaque octet d’un long mot de passe). Une connexion ratée ne révèle jamais si le compte existe, et la session change d’identifiant à la connexion.
  • Les sorties sont échappées. Les templates échappent le texte par défaut. Le Markdown rédigé dans le back-office échappe le HTML brut et supprime les liens javascript:.
  • Un fichier envoyé ne peut pas devenir du code. Les fichiers reçoivent un nom aléatoire. Leur type est détecté à partir de leur contenu, jamais d’après ce qu’annonce le navigateur. Les extensions qu’un serveur ou un navigateur exécuterait (php, html, svg, js, …) sont remplacées par celle du type détecté.
  • Les secrets ne fuient pas par les outils. php bin/console inspect montre les clés de configuration et leur type, jamais leur valeur. Les journaux d’erreurs ne contiennent ni traces d’exécution ni mots de passe.

Pour les applications qui en ont besoin, le même paquet propose une double authentification (TOTP et codes de secours), des codes à usage unique par SMS ou WhatsApp, des rôles par organisation ou par événement, et un journal d’audit de sécurité infalsifiable. Ces fonctions s’activent au besoin.

2. Un déploiement dangereux ne démarre pas

Les réglages par défaut protègent le code. Mais les défaillances les plus fréquentes viennent de la configuration. Quand une application Zana démarre, elle lance donc des contrôles de déploiement, et refuse de démarrer si quelque chose l’exposerait.

Voici le même projet, déployé avec le mode debug actif et le secret d’exemple encore en place :

APP_ENV=production APP_DEBUG=true APP_SECURITY__SECRET=change-me php bin/console check
  ✘ The application refuses to start:
  - Debug mode is on in production. Set APP_DEBUG=false.
Debug mode is on for the public URL https://www.example.org: errors would show code and paths to visitors. Set APP_ENV=production and APP_DEBUG=false.
security.secret is empty or a placeholder. Generate one, e.g. php -r "echo bin2hex(random_bytes(32));"

 [ERROR] The application cannot be built.

Dans un navigateur, les visiteurs ne voient ni trace d’exécution ni chemin de fichier. Ils voient une page neutre, tandis que la raison est inscrite dans le journal d’erreurs du serveur :

The site is temporarily unavailable. / Le site est momentanément indisponible.

C’est un choix délibéré. Un site brièvement indisponible se répare en quelques minutes. Un site qui montre son code à Internet, ou qui signe ses sessions avec un secret que n’importe qui peut trouver sur GitHub, est peut-être déjà compromis au moment où quelqu’un s’en aperçoit.

3. Une seule commande vérifie toute l’application

php bin/console check lance toutes les vérifications que Zana connaît, sur chaque projet, en développement et en intégration continue :

  ✔ The container builds
  ✔ Deployment checks: BuiltInChecks
  ✔ Deployment checks: LogFilesCheck
  ✔ All 58 singletons can be created
  ✔ Architecture: 1 module(s), no violation
  ✔ MiddlewareCheck
  ✔ TrustedProxiesCheck
  ✔ RouteHandlersCheck
  ✔ RoleAssignmentsCheck
  ✔ TwoFactorCheck
  ✔ AgentsCheck

 [OK] The application passes its checks.

Plusieurs de ces lignes concernent la sécurité :

  • Les middlewares : chaque middleware de sécurité (CSRF, authentification, limitation, en-têtes) peut réellement être créé. Une protection qui planterait à l’exécution est signalée maintenant, et non découverte lors d’un incident.
  • Les routes : chaque route mène à un handler enregistré et chaque middleware de route existe ; une protection déclarée sur une route s’exécute donc vraiment.
  • L’architecture : les frontières entre modules tiennent. La couche de présentation ne peut pas accéder directement à la base de données en contournant les règles de la couche application.
  • Proxys de confiance, double authentification et rôles : chaque fonction de sécurité que vous activez s’accompagne d’un contrôle de ses réglages.

Chaque problème est signalé avec un message clair et une piste de correction. Ici, une faute de frappe dans la plage d’adresses d’un répartiteur de charge :

  ✘ "10.0.0.0/33" in http.trusted_proxies is not an address or a range, so it matches nothing.
      → Write an address, such as 203.0.113.7, or a range, such as 10.0.0.0/8 or 2001:db8::/32.

 [ERROR] The application has errors.

check se termine avec un code d’erreur : une chaîne d’intégration continue arrête alors la mise en ligne. Les squelettes de projet fournissent cette chaîne, prête à l’emploi : chaque push et chaque pull request lancent les tests et check.

4. Le serveur est vérifié aussi

Après chaque déploiement, php bin/console doctor vérifie le serveur lui-même : la version de PHP et ses extensions, les dossiers accessibles en écriture, la longueur du secret et les contrôles de déploiement. Depuis la version 2.19, il repère aussi les fichiers qui n’ont rien à faire en ligne :

  ✔ PHP 8.5.12
  ✔ Extension json
  ✔ Extension mbstring
  ✔ C:/wamp64/www/lab/security-demo/var is writable
  ✔ security.secret is long enough
  ✔ Deployment checks: BuiltInChecks
  ✔ Deployment checks: LogFilesCheck
  ✔ All 58 singletons can be created
  ! These files do not belong on a live server: tests, .github, phpunit.xml.dist, .gitignore, AGENTS.md, CLAUDE.md, README.md, .env.example.
      → See what would go: php bin/console release:clean --dry-run

release:clean les supprime, et les archives produites par release:build les excluent dès le départ. Les mêmes règles s’appliquent quelle que soit la façon dont le code arrive sur le serveur : une archive, git pull ou un dossier copié.

5. Le framework se vérifie lui-même

Les contrôles ci-dessus protègent chaque projet de ses propres erreurs. Zana soumet aussi son propre code à des règles écrites.

  • Chaque changement doit passer PHPUnit et PHPStan au niveau 8, dans son paquet et dans tous les paquets qui en dépendent.
  • Un contrôle de sécurité pour les changements à risque. Un changement qui lit des données extérieures, écrit des fichiers, touche à la connexion, aux permissions, aux secrets ou à la cryptographie, construit du SQL ou du HTML, ou ajoute une dépendance, passe un contrôle de sécurité avant d’être intégré. Le contributeur nomme qui peut atteindre le code et le pire scénario. Il l’attaque ensuite avec des entrées hostiles sur le code réel, et garde un test pour chaque faille trouvée, pour qu’elle ne puisse jamais revenir sans être remarquée.
  • La même règle pour les agents IA. Depuis la version 2.19, le fichier AGENTS.md de chaque projet Zana demande aux agents de codage IA ce même contrôle de sécurité sur les changements à risque. Ils doivent indiquer ce qui a été attaqué, ce qui a été trouvé et ce qui n’a pas été couvert.

6. Les audits, et ce que nous faisons de leurs résultats

Des règles ne suffisent pas à elles seules : le code de sécurité de Zana est donc audité. Le dernier audit a porté sur le code de sécurité écrit de la version 2.1 à la version 2.18. Il a trouvé de vraies faiblesses, qui ont été corrigées et publiées le jour même dans les versions 2.19.1 et 2.19.2 :

  • une double authentification illisible, après un changement de sa clé, ne permet plus de se connecter avec le seul mot de passe ;
  • changer un mot de passe déconnecte les autres sessions du compte ;
  • seul un jeton d’accès valide dispense des contrôles CSRF. Auparavant, n’importe quel en-tête Authorization suffisait, et un lien d’aperçu du back-office pouvait aussi les contourner ;
  • les limites de connexion ne comptent que les tentatives échouées, et le point d’accès aux jetons du squelette d’API répond aussi lentement pour une adresse inconnue que pour une adresse connue : le temps de réponse ne révèle plus quels comptes existent ;
  • derrière un proxy, un seul en-tête de transfert est lu, et une plage d’adresses illisible ne correspond plus à rien au lieu de tout (le check montré plus haut) ;
  • les fichiers envoyés perdent les extensions qu’un navigateur exécuterait, comme .html et .svg ;
  • les pages qui portent un nonce CSP ne sont plus mises en cache.

Selon la règle du framework, chaque faille trouvée est conservée sous forme de test, pour qu’elle ne puisse pas revenir sans être remarquée. Les notes de version disent exactement ce qui a changé, ce qu’il faut vérifier et comment mettre à jour, ce qui, pour la plupart des projets, se résume à :

composer update
php bin/console check

Nous publions cette liste délibérément. Un framework qui n’annonce jamais de correctif de sécurité n’est pas un framework sans faiblesses : c’est un framework que personne n’examine. Ce qu’un projet doit attendre, c’est que les faiblesses soient recherchées, corrigées rapidement, expliquées clairement et protégées par des tests. C’est l’exigence que Zana s’impose.

Ce qui reste de votre ressort

Les contrôles couvrent la configuration, le câblage, le déploiement et le code de sécurité du framework. Ils ne peuvent pas connaître vos règles métier. Qui peut consulter le dossier d’un patient, rembourser un billet ou valider un paiement, c’est une décision que seule votre équipe peut prendre, et tester. Deux habitudes complètent le tableau :

  1. Restez à jour. Lancez composer update à chaque nouvelle version, et lisez ses notes. Tous les paquets Zana partagent le même numéro de version : la question de la bonne combinaison ne se pose jamais.
  2. Intégrez les contrôles à chaque mise en ligne. Lancez check et les tests en intégration continue, et doctor après chaque déploiement. Les squelettes font déjà le premier.

Une liste de contrôle de cinq minutes pour votre projet

  • Passer à Zana 2.19.2 ou plus récent : composer update.
  • php bin/console check passe sans erreur.
  • L’intégration continue lance les tests et check à chaque push.
  • Après chaque déploiement, php bin/console doctor passe, et release:clean fait partie du déploiement.
  • php bin/console make:agents --force, pour que les agents IA qui travaillent sur le projet reçoivent le contrôle de sécurité.

Si vous souhaitez un regard extérieur sur un projet Zana, ou sur une application plus ancienne que vous voulez amener à ce niveau, notre équipe propose des revues de sécurité et d’architecture. Parlez-nous de votre projet.

Recevez nos nouveaux articles par e-mail

Un e-mail par mois. Vous confirmez votre adresse avant tout envoi.