1. Portée
Ces règles s'appliquent à toute application ou thème proposé sur le Magasin par un éditeur partenaire. Elles font partie du Contrat éditeur. Leur tableau des contrôles automatiques (section 7) décrit exactement ce que vérifient nos robots ; toute évolution des robots donne lieu à une nouvelle version de ces règles, annoncée 15 jours à l'avance.
2. Avant de soumettre
- compte partenaire actif, avec double authentification ;
- identification complète de la société et engagement de conformité ;
- acceptation du Contrat éditeur et des présentes règles, dans leur version en vigueur.
3. Exigences techniques
- dépôt Git accessible en https sur GitHub ou GitLab, branche de la série (20.0), un module par dossier ;
- manifeste lisible sans exécution, version 20.0.x.y.z, licence et auteur déclarés, dépendances Community uniquement ;
- uniquement des sources : aucun fichier compilé, binaire, archive ou code obscurci ; bibliothèques tierces dans static/lib/ ;
- installation, mise à jour et désinstallation sans erreur dans une base Odoo 20 neuve, sans accès à Internet ;
- dépôt de moins de 300 Mo ; aucun fichier de plus de 20 Mo sans justification.
4. Contenu de la fiche
- décrire uniquement ce que fait le code livré : jamais une fonction prévue présentée comme disponible ;
- une section « Ce que l'application ne fait pas » ;
- aucune icône, capture ou marque d'Odoo S.A. ni de tiers sans autorisation ; aucun nom d'application contenant une marque d'autrui ;
- aucun lien publicitaire ou de redirection commerciale dans la description ; captures fidèles à la version publiée ;
- déclarer tout service externe appelé par l'application, les données envoyées et leur destinataire.
5. Sécurité et données
- aucune donnée du client ne quitte son espace sans action explicite d'un utilisateur, après information ;
- aucun appel réseau à l'installation, au chargement du module ou au démarrage du serveur ;
- aucun secret (jeton, clé, mot de passe) dans le dépôt ; les secrets se lisent dans la configuration ;
- requêtes SQL paramétrées ; pas d'évaluation de données reçues ; routes publiques limitées au strict nécessaire ;
- aucune modification des comptes, groupes ou mots de passe existants à l'installation ; aucune neutralisation des contrôles d'accès d'Odoo ;
- une désinstallation ne supprime que les données créées par l'application.
6. Déroulement
- Soumission depuis l'espace partenaire : pré-contrôle et robots internes immédiats.
- Analyse complète par les robots (quelques minutes) : résultats visibles dans l'espace partenaire, avec la façon de corriger.
- Quarantaine : installation, tests, mise à jour et désinstallation d'essai, sans Internet, ressources limitées.
- Décision humaine : toute première publication et toute alerte « à revoir » sont examinées par une personne, qui motive sa décision (faux positif accepté, risque accepté, correction exigée).
- Publication du seul commit vérifié. Chaque nouveau commit est analysé ; il n'est publié qu'après le même parcours.
- Ré-analyse quotidienne du code publié (nouvelles vulnérabilités connues, robots mis à jour) : une alerte peut conduire à un retrait, motivé.
7. Tableau des contrôles automatiques
Effets : « Bloquant » = ni installation d'essai ni publication ; « À revoir par un humain » = publication seulement après décision motivée d'une personne ; « Déclasse la fiche » = publiée, marquée « à vérifier », moins bien classée ; « Information » = affichée, sans effet. Un robot qui n'a pas pu fonctionner n'est jamais compté comme conforme.
| Contrôle | Effet | Justification | Comment corriger |
|---|---|---|---|
| Manifeste et licence | |||
| Manifeste lisible sans exécution (manifeste) | Bloquant | Le magasin lit les manifestes sans exécuter le code du partenaire. | Écrire __manifest__.py comme un dictionnaire Python littéral (aucun appel, aucune variable). |
| Série 20.0 (serie) | Bloquant | Omnifloo fonctionne sur Odoo 20 : un module d'une autre série ne s'installe pas. | Version 20.0.x.y.z sur la branche 20.0. |
| Licence déclarée et autorisée (licence) | Bloquant | La licence fixe ce qu'Omnifloo et le client ont le droit de faire ; OEEL-1 (Odoo Enterprise) interdit la redistribution. | Clé license : LGPL-3, AGPL-3, GPL-3, MIT, Apache-2.0, autre licence OSI, OPL-1 ou « Other proprietary ». |
| Aucune dépendance vers Odoo Enterprise (enterprise-deps) | Bloquant | Les modules Enterprise ne sont pas disponibles sur Omnifloo (Community). | Retirer les modules Enterprise de depends. |
| Aucun import de code Enterprise (enterprise-code) | Bloquant | Code sous licence OEEL-1 non redistribuable. | Supprimer les imports odoo.addons.<module Enterprise>. |
| Aucun en-tête de licence Enterprise (enterprise-licence) | Bloquant | Un fichier LICENSE Enterprise signale du code non redistribuable. | Retirer le code concerné ; écrire le module en salle blanche. |
| Auteur déclaré (auteur) | Bloquant | Traçabilité de l'éditeur (règlement (UE) 2022/2065, art. 30) et support. | Renseigner la clé author du manifeste. |
| Sécurité du code Python et d'Odoo | |||
| Route publique ou sans authentification utilisant sudo() (route-public-sudo) | À revoir par un humain | Une route auth='public' ou auth='none' qui passe en superutilisateur peut exposer ou modifier des données de toute la base à un visiteur anonyme. | Limiter la lecture aux champs nécessaires, filtrer par jeton d'accès, ou exiger auth='user'. Faux positifs connus : Pages publiques qui lisent des enregistrements publiés (sudo() + domaine is_published). |
| Formulaire POST sans protection CSRF (csrf=False) (csrf-off) | À revoir par un humain | Un site tiers peut déclencher l'action à l'insu d'un utilisateur connecté. | Garder csrf=True et envoyer csrf_token ; pour un webhook, vérifier une signature HMAC. Faux positifs connus : Webhooks entrants signés (signature vérifiée dans la méthode). |
| Requête SQL construite par formatage de chaîne (sql-injection) | À revoir par un humain | Une valeur insérée dans la requête par %, +, f-string ou format() permet l'injection SQL. | Passer les valeurs en paramètres : cr.execute(query, params) ou odoo.tools.SQL. Faux positifs connus : Noms de table ou de colonne internes (self._table) : signalé pour relecture seulement. |
| Requête SQL construite avec une donnée reçue du navigateur (sql-injection-request) | Bloquant | Injection SQL exploitable à distance. | Paramètres de requête (cr.execute(query, params)) ou odoo.tools.SQL, jamais de formatage. |
| eval / exec / compile / safe_eval (eval-exec) | À revoir par un humain | Exécuter une chaîne revient à exécuter du code arbitraire si la chaîne vient d'un utilisateur. | Remplacer par ast.literal_eval, json.loads ou une table de correspondance. Faux positifs connus : safe_eval sur des domaines ou expressions saisis par un administrateur. |
| eval / exec / safe_eval sur une donnée du navigateur (eval-request) | Bloquant | Exécution de code à distance. | Ne jamais évaluer une donnée reçue ; utiliser json.loads. |
| Lancement de commandes système (subprocess) | À revoir par un humain | subprocess, os.system, os.popen, pty, os.exec*, os.spawn* sortent du bac à sable d'Odoo. | Supprimer l'appel ou le justifier (commande fixe, sans donnée utilisateur) dans le README. Faux positifs connus : Conversion de fichiers par un utilitaire documenté (wkhtmltopdf, libreoffice). |
| Commande système au chargement du module ou dans un hook d'installation (subprocess-hook) | Bloquant | Exécutée sur le serveur du client sans action de sa part. | Retirer la commande des hooks et du niveau module. |
| Surcharge d'une méthode de sécurité d'Odoo (security-override) | À revoir par un humain | _check_access, check_credentials, _compute_session_token, authenticate… décident de qui accède à quoi. | Appeler super() sans affaiblir le contrôle ; décrire la raison dans le README. Faux positifs connus : Ajout d'un journal de connexion qui appelle super(). |
| Contrôle de sécurité neutralisé (security-bypass) | Bloquant | Une méthode de sécurité surchargée qui renvoie toujours vrai ou n'appelle pas super() ouvre la base. | Supprimer la surcharge ou appeler super(). |
| Hook qui modifie utilisateurs, groupes ou mots de passe (hook-users) | Bloquant | Création de comptes cachés ou élévation de droits à l'installation. | Déclarer les groupes en XML (implied_ids) ; ne jamais toucher aux mots de passe ni aux comptes existants. |
| Hook de désinstallation destructeur (uninstall-destructive) | À revoir par un humain | Un uninstall_hook qui supprime des données hors de son module détruit les données du client. | Ne supprimer que les données créées par le module ; laisser Odoo supprimer ses propres tables. Faux positifs connus : Nettoyage de paramètres ir.config_parameter propres au module. |
| Suppression de base, de schéma ou de table système (drop-database) | Bloquant | DROP DATABASE / DROP SCHEMA / TRUNCATE de tables système : perte de données irréversible. | Supprimer l'instruction. |
| Code exécuté au démarrage du serveur (post_load) (post-load) | À revoir par un humain | post_load s'exécute au démarrage pour toutes les bases, y compris celles qui n'ont pas installé le module. | Éviter post_load ; si indispensable, le limiter à une correction documentée. |
| cr.commit() explicite (cr-commit) | À revoir par un humain | Casse l'atomicité des transactions et les retours arrière d'Odoo. | Laisser Odoo valider la transaction ; en cron, découper par lots documentés. Faux positifs connus : Crons de traitement par lots documentés. |
| Désérialisation dangereuse (pickle, marshal, yaml.load) (pickle) | À revoir par un humain | Charger des données non fiables par pickle ou yaml.load exécute du code. | json ou yaml.safe_load. |
| Réseau et exfiltration | |||
| Appel réseau sortant (network) | Information | Le client doit savoir quelles données partent vers quel service. | Déclarer le service externe et les données envoyées dans la description. |
| Appel réseau au chargement du module ou dans un hook d'installation (network-hook) | Bloquant | Envoi de données à l'insu du client dès l'installation (exfiltration). | Ne contacter un service qu'à la demande explicite de l'utilisateur, après configuration. |
| Hôte distant codé en dur (network-hardcoded) | À revoir par un humain | Une adresse fixe dans le code peut servir de point de collecte. | Rendre l'adresse configurable et la documenter. Faux positifs connus : API publiques documentées (services officiels). |
| Lecture de secrets puis envoi réseau (exfiltration) | Bloquant | Lire mots de passe, clés, paramètres système ou variables d'environnement puis appeler le réseau dans la même fonction est le motif type du vol de données. | Séparer strictement : aucun secret du client ne doit quitter la base sans action explicite. |
| Appel réseau sans délai maximal (network-timeout) | À revoir par un humain | Un appel sans timeout peut bloquer un processus du serveur du client. | Passer timeout=… à chaque appel. |
| Code caché ou illisible | |||
| Code caché exécuté (exec/eval de base64, zlib, marshal) (obfuscation-exec) | Bloquant | Technique classique des modules malveillants : le code réel n'est pas lisible à la relecture. | Livrer le code source en clair. |
| Gros bloc encodé (base64/hex) dans le code (obfuscation-blob) | À revoir par un humain | Peut cacher du code ou des données exécutées plus tard. | Placer les images et données dans des fichiers dédiés (static/, data/). Faux positifs connus : Images ou polices intégrées en base64. |
| Import dynamique (__import__, importlib) d'un nom calculé (dynamic-import) | À revoir par un humain | Masque les dépendances réelles du module. | Importer explicitement. Faux positifs connus : Chargement de connecteurs optionnels par nom fixe. |
| Fichier compilé ou binaire (.pyc, .so, .dll, .exe…) (compiled-file) | Bloquant | Code impossible à relire ; interdit sur le magasin comme sur l'Odoo Apps Store. | Livrer uniquement des sources. |
| JavaScript minifié sans source (minified-js) | À revoir par un humain | Illisible à la relecture. | Fournir la source non minifiée ; les bibliothèques connues vont dans static/lib/. Faux positifs connus : Bibliothèques tierces connues placées hors de static/lib/. |
| JavaScript obscurci (eval(atob(…)), identifiants _0x…) (js-obfuscated) | Bloquant | Signature des scripts malveillants injectés dans les sites. | Livrer le code en clair. |
| Encodage du code Python autre que UTF-8, ASCII ou Latin-1 (déclaration PEP 263 ou BOM UTF-16/32) (source-encoding) | Bloquant | Un encodage exotique (UTF-7…) permet de cacher du code exécuté à l'import derrière ce qui ressemble à un commentaire : la relecture et les robots ne voient pas ce que Python exécute. | Enregistrer le fichier en UTF-8 et supprimer la ligne « # -*- coding: … -*- » (ou la mettre en utf-8). |
| Fichier Python illisible par l'analyseur (décodage ou syntaxe) (python-unparsable) | À revoir par un humain | Un fichier que l'analyseur ne peut pas lire n'a pas été contrôlé : jamais « conforme » par défaut. | Corriger la syntaxe ou l'encodage (UTF-8) ; vérifier que le fichier s'importe sous Python 3.12+. Faux positifs connus : Fichier de plus de 2 Mo (analyse tronquée) ou syntaxe d'une version de Python plus récente que l'analyseur. |
| JavaScript, gabarits QWeb et pages | |||
| Markup() sur une valeur non littérale (markup-python) | À revoir par un humain | Marque comme sûr un HTML qui peut contenir un script injecté par un utilisateur. | Échapper la valeur (markupsafe.escape) ou assainir (odoo.tools.html_sanitize). Faux positifs connus : Valeur déjà assainie par html_sanitize. |
| eval / new Function / setTimeout(chaîne) en JavaScript (js-eval) | À revoir par un humain | Exécution de code arbitraire dans le navigateur du client. | Supprimer ; utiliser JSON.parse. |
| Script ou feuille de style chargé depuis un domaine externe (external-script) | À revoir par un humain | Le code réellement exécuté peut changer à tout moment hors de la vérification du magasin, et le visiteur est pisté par un tiers. | Embarquer la ressource dans static/lib/ ou la déclarer dans la description. Faux positifs connus : Polices ou cartes d'un service déclaré (Google Fonts, OpenStreetMap). |
| Accès à document.cookie (document-cookie) | À revoir par un humain | Permet de lire ou poser des traceurs ; les cookies de session d'Odoo sont protégés mais pas les autres. | Utiliser les services d'Odoo ; déclarer tout traceur (consentement). Faux positifs connus : Mémorisation d'une préférence d'affichage. |
| Gabarit QWeb t-raw (HTML non échappé) (t-raw) | À revoir par un humain | t-raw affiche du HTML brut : injection de script si la valeur vient d'un utilisateur. | Utiliser t-out ; assainir la valeur côté serveur. |
| markup() JavaScript sur une valeur non littérale (js-markup) | À revoir par un humain | Même risque que t-raw côté navigateur. | N'utiliser markup() que sur du HTML assaini. |
| Minage de cryptomonnaie (cryptominer) | Bloquant | Utilise les processeurs des visiteurs à leur insu. | Supprimer. |
| Script dans la description publique (description-script) | À revoir par un humain | La description est affichée sur le magasin : aucun script n'y est exécuté. | Retirer <script> et les attributs on* de static/description/index.html. |
| Ressource externe déclarée dans les assets du manifeste (manifest-external-asset) | À revoir par un humain | Charge du code tiers sur chaque page du client. | Embarquer la ressource dans static/lib/. |
| Fichiers et secrets | |||
| Fichier interdit (.env, clé privée, sauvegarde, base de données) (forbidden-file) | Bloquant | Secrets ou données personnelles publiés avec le module. | Supprimer le fichier de l'historique Git et révoquer les secrets exposés. |
| Secret détecté (jeton, clé d'API, clé privée) (secret) | Bloquant | Un secret publié est compromis : il doit être révoqué. | Révoquer le secret, le retirer de l'historique Git, le lire depuis la configuration. Faux positifs connus : Jetons d'exemple manifestement factices : à confirmer par le relecteur. |
| Chaîne à forte entropie ou mot de passe probable (secret-probable) | À revoir par un humain | Peut être un secret. | Retirer ou confirmer qu'il s'agit d'une valeur factice. Faux positifs connus : Empreintes, identifiants publics, données de test. |
| Archive dans le dépôt (.zip, .tar, .7z…) (archive) | À revoir par un humain | Contenu non relu par les robots. | Livrer les fichiers décompressés ou les retirer. |
| Lien symbolique (symlink) | Bloquant | Peut pointer hors du module sur le serveur. | Remplacer par une copie du fichier. |
| Fichier très volumineux (plus de 20 Mo) (big-file) | À revoir par un humain | Alourdit chaque espace client. | Compresser ou héberger ailleurs les médias. |
| Dépôt trop volumineux (plus de 300 Mo) (repo-size) | Bloquant | Limite de la quarantaine. | Découper le dépôt ou alléger les médias. |
| Dépendances externes | |||
| Dépendance Python connue comme malveillante (malicious-package) | Bloquant | Paquet signalé dans la base publique OSV (avis MAL-…), souvent un typosquat. | Corriger le nom du paquet. |
| Dépendance Python avec vulnérabilité connue (vulnerable-package) | À revoir par un humain | Version épinglée concernée par un avis de sécurité public. | Monter de version. |
| Dépendance Python introuvable sur PyPI (unknown-package) | À revoir par un humain | Nom mal orthographié (typosquat) ou paquet privé non installable. | Corriger le nom. |
| Dépendance vers un programme du système (bin-dependency) | Information | Programme à installer sur le serveur ; non fourni par Omnifloo sans accord. | Documenter le besoin ; prévoir un fonctionnement dégradé. |
| Qualité et présentation | |||
| Icône (icone) | Déclasse la fiche | Fiche lisible dans le magasin. | Fournir static/description/icon.png (aucune icône d'Odoo S.A. reprise). |
| Description (description) | Déclasse la fiche | Le client doit savoir ce que fait l'application avant de l'installer. | Fournir static/description/index.html (sans script) ou un README. |
| Tests automatisés (tests) | Déclasse la fiche | Des tests réduisent le risque de régression chez les clients. | Ajouter tests/test_*.py. |
| Traduction française (traduction-fr) | Information | Clientèle francophone majoritaire. | Ajouter i18n/fr.po. |
| Section « Ce que le module ne fait pas » (limites) | Information | Honnêteté de la fiche : le client sait ce qui manque. | Ajouter la section dans la description ou dans catalogue.yml. |
| Installation d'essai (quarantaine) | |||
| Installation d'essai sur Odoo 20 (install) | Bloquant | Un module qui ne s'installe pas casse l'espace du client. | Corriger les erreurs du journal d'installation. |
| Tests du module en échec (module-tests) | À revoir par un humain | Tests rouges dans une base propre : régression probable. | Corriger les tests ou le code. |
| Mise à jour du module (-u) en échec (upgrade) | Bloquant | Les clients reçoivent les nouvelles versions par mise à jour. | Corriger les scripts de migration. |
| Désinstallation en échec (uninstall) | À revoir par un humain | Le client doit pouvoir retirer une application. | Corriger les dépendances et le uninstall_hook. |
| Limite de temps ou de mémoire atteinte en quarantaine (resource-limit) | À revoir par un humain | Coût anormal pour chaque espace client. | Alléger les données installées. |
| Robots externes | |||
| Constat de sécurité Bandit (gravité haute) (bandit) | À revoir par un humain | Motif Python dangereux connu (Bandit, PyCQA, Apache-2.0). | Voir la référence Bandit indiquée. Faux positifs connus : Motifs déjà justifiés dans le code. |
| Constat Bandit de gravité faible ou moyenne (bandit-low) | Information | Signal faible, affiché pour information. | Voir la référence Bandit indiquée. |
| Constat de sécurité pylint-odoo (OCA) (pylint-odoo) | À revoir par un humain | Règles de l'OCA pour Odoo (injection SQL, commit, eval, appel sans délai…). | Voir le message pylint-odoo. |
| Analyse non effectuée (tool-missing) | À revoir par un humain | Un robot n'a pas pu tourner : l'analyse est incomplète, jamais « conforme » par défaut. | Rien côté partenaire : l'équipe Omnifloo relit ou relance l'analyse. |
8. Faux positifs
Un robot peut se tromper. L'éditeur peut expliquer un constat en répondant dans son espace ou à contact@omnifloo.com ; la personne qui examine le dossier peut le classer « faux positif » en motivant sa décision. Cette décision est conservée tant que le code concerné ne change pas.
9. Manquements
Un manquement entraîne, selon sa gravité : une demande de correction, un déclassement, un refus de publication, un retrait ou une suspension, dans les conditions du Contrat éditeur (article 9).