Développeurs

Exécution sécurisée des scripts (safe_exec)

Architecture de sécurité de Dodock pour l'exécution des scripts Jinja et Python côté serveur — distinction entre contexte de rendu (lecture seule) et contexte d'exécution (lecture/écriture).

Exécution sécurisée des scripts

Dodock permet d'exécuter des scripts Python et des templates Jinja directement depuis l'interface (Scripts serveur, formats d'impression, e-mails, etc.). Pour protéger l'installation contre des accès non autorisés aux données ou aux opérations critiques, le moteur distingue deux contextes d'exécution aux permissions distinctes.

Deux contextes, deux niveaux de permissions

Contexte de rendu — render_safe_globals

Utilisé par le moteur de templates Jinja (formats d'impression, modèles d'e-mail, aperçus). Ce contexte est strictement en lecture : aucune opération d'écriture en base de données, aucune mise en file d'attente de tâches en arrière-plan ne sont accessibles.

Fonctions disponibles (liste principale) :

FonctionRôle
frappe.get_docRécupère un document (objet déshydraté, sans méthodes d'écriture)
frappe.get_list / frappe.get_allInterroge des listes de documents
frappe.get_metaRetourne les métadonnées d'un DocType (objet déshydraté)
frappe.get_cached_docRécupère un document depuis le cache
frappe.get_last_docRetourne le dernier document d'un type
frappe.copy_docCrée une copie locale d'un document
frappe.get_mapped_docMappe un document vers un autre type
frappe.sendmailEnvoie un e-mail (retourne le nom du message, sans accès aux files)
Filtres et macros Jinja standardsFormatage, dates, devises…

Remarque : Les objets retournés par ces fonctions sont déshydratés — ils exposent les données du document (champs, tables enfants) mais pas les méthodes Python internes (save, submit, delete, etc.). Cela empêche tout effet de bord depuis un template.

Contexte d'exécution — exec_safe_globals

Utilisé par les Scripts serveur, les automatisations et les crochets Python configurés via l'interface. Ce contexte étend le contexte de rendu avec des capacités d'écriture contrôlées.

Fonctions supplémentaires disponibles :

FonctionRôle
frappe.new_docCrée un nouveau document (objet déshydraté prêt à l'enregistrement)
frappe.enqueueMet une tâche en file d'attente en arrière-plan
QueryBuilder (lecture seule via frappe.qb)Construit des requêtes SQL en lecture — les opérations d'écriture (INSERT, UPDATE, DELETE) restent bloquées

Sécurité QueryBuilder : même dans le contexte d'exécution, le QueryBuilder est restreint aux opérations de lecture. Les tentatives d'écriture via frappe.qb déclenchent une exception.

Objets déshydratés

L'un des mécanismes clés de cette architecture est la déshydratation des objets retournés par les fonctions d'accès aux documents.

Un objet Document Frappe standard expose de nombreuses méthodes Python (save(), submit(), delete(), reload(), etc.) qui modifient la base de données. Dans un contexte sécurisé, ces méthodes ne doivent pas être accessibles à du code non vérifié.

La déshydratation consiste à convertir l'objet Document en un dictionnaire enrichi (_dict) qui :

  • expose tous les champs et leur valeur (y compris les tables enfants)
  • expose les métadonnées utiles (nom du DocType, statut de soumission, etc.)
  • ne contient aucune méthode pouvant modifier la base de données
# Ce que le script reçoit (objet déshydraté)
doc = frappe.get_doc("Sales Order", "SAL-ORD-2024-00001")
print(doc.customer)        # ✅ Lecture des champs
print(doc.items[0].qty)    # ✅ Accès aux tables enfants
doc.save()                 # ❌ AttributeError — méthode non disponible

Mode développement

En environnement de développement (developer_mode = 1), les restrictions du contexte de rendu peuvent être assouplies pour faciliter le débogage des templates. Ce comportement est opt-out : l'assouplissement est actif par défaut en mode développement mais toujours désactivé en production.

Ne jamais tester en production la permissivité du mode développement. Les restrictions de render_safe_globals sont conçues pour protéger les données en environnement de production ; leur désactivation expose l'installation à des risques de sécurité.

Cas d'usage et bonnes pratiques

Formats d'impression et modèles d'e-mail

Ces templates s'exécutent toujours dans le contexte render_safe_globals. Si un template génère une erreur du type AttributeError sur une méthode comme save, c'est le comportement attendu : le template tente d'appeler une opération d'écriture interdite.

Bonne pratique : limiter les templates Jinja à l'affichage de données. Toute logique de transformation complexe doit être déplacée vers un Script serveur ou un crochet Python.

Scripts serveur

Les Scripts serveur disposent du contexte exec_safe_globals. Ils peuvent créer des documents, envoyer des e-mails et déclencher des tâches en arrière-plan, mais restent isolés du reste de l'environnement Python (pas d'import de modules arbitraires, pas d'accès au système de fichiers).

# Exemple — Script serveur : créer un document et envoyer un e-mail
commande = frappe.new_doc("Sales Order")
commande.customer = "Maison Verte SARL"
commande.delivery_date = frappe.utils.add_days(frappe.utils.today(), 7)
# Ajout de lignes...
commande.insert()

frappe.sendmail(
    recipients=["contact@maisonverte.fr"],
    subject="Votre commande a été créée",
    message=f"Commande {commande.name} enregistrée."
)

Identifier le contexte actif

Si vous développez une fonction utilitaire appelée depuis plusieurs contextes, vous pouvez vérifier les globals disponibles plutôt que de supposer le contexte :

# Vérifier si l'exécution se trouve dans un contexte sécurisé
import frappe
if frappe.flags.in_safe_exec:
    # Comportement adapté au contexte sécurisé
    pass

Architecture — fichiers concernés

FichierRôle
frappe/utils/safe_exec.pyDéfinition de render_safe_globals, exec_safe_globals, safer_exec et des utilitaires de déshydratation
frappe/utils/jinja.pyIntégration de render_safe_globals dans le moteur de templates Jinja (get_jenv)
frappe/model/meta.pySérialisation des métadonnées en _dict pour la déshydratation
frappe/query_builder/utils.pyRestriction du QueryBuilder aux opérations de lecture dans les contextes sécurisés