Développeurs

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

Les deux contextes d'exécution de Dodock pour le code saisi dans l'interface : le rendu Jinja en lecture seule et les scripts serveur.

Exécution sécurisée des scripts

Dodock exécute du code saisi dans l'interface : scripts serveur, formats d'impression, modèles d'e-mail. Ce code passe par RestrictedPython, qui bloque les imports et les attributs internes de Python. Il ne voit que les fonctions d'une liste fixe, appelée espace de noms. Dodock utilise deux espaces de noms.

Contexte de rendu : render_safe_globals

Le moteur Jinja (get_jenv) utilise ce contexte pour les formats d'impression, les modèles d'e-mail et les autres modèles. Il donne seulement un accès en lecture :

FonctionRésultat
frappe.get_doc, frappe.get_cached_doc, frappe.get_last_docUne copie du document en dictionnaire (SafeDoc), sans save, submit ni delete
frappe.get_metaLes métadonnées en dictionnaire, avec get_field, has_field, get_options et get_label
frappe.get_list, frappe.get_allDes listes de documents. Les champs qui contiennent une parenthèse (fonctions SQL) sont retirés
frappe.db.get_value, frappe.db.get_single_value, frappe.db.exists, frappe.db.countDes valeurs lues en base
frappe.db.sqlSeulement les requêtes SELECT et EXPLAIN
frappe.make_get_requestUne requête HTTP GET, avec un contrôle des adresses internes
frappe.utils, frappe.format_value, frappe.render_templateLe formatage des dates, nombres et devises

Le contexte de rendu n'a pas frappe.new_doc, frappe.sendmail ni frappe.enqueue.

{% set client = frappe.get_doc("Customer", doc.customer) %}
{{ client.customer_name }}

client.save() échoue dans ce modèle, car SafeDoc n'a pas cette méthode.

Un administrateur du serveur peut désactiver ce contexte avec la clé disable_render_safe_exec du fichier common_site_config.json. Les modèles Jinja reçoivent alors le contexte des scripts serveur. Ne le faites pas sur un site où des utilisateurs non sûrs modifient des modèles.

Contexte d'exécution : exec_safe_globals

Les Scripts serveur utilisent ce contexte. Il reprend le contexte de rendu et remplace les fonctions de lecture par les vraies fonctions de Dodock :

FonctionUsage
frappe.get_doc, frappe.new_doc, frappe.copy_doc, frappe.get_mapped_docDocuments complets, avec insert, save, submit
frappe.delete_doc, frappe.rename_docSupprimer ou renommer un document
frappe.db.set_value, frappe.db.commit, frappe.db.rollbackÉcrire en base et gérer la transaction
frappe.sendmail, frappe.get_print, frappe.attach_printE-mails et impressions
frappe.enqueue, frappe.is_job_queuedLancer une méthode whitelistée en arrière-plan
frappe.callAppeler une méthode whitelistée
frappe.make_post_request, make_put_request, make_patch_request, make_delete_requestRequêtes HTTP

frappe.db.sql reste limité à SELECT et EXPLAIN. Une requête frappe.qb lancée depuis un script serveur doit aussi être un SELECT, sinon Dodock lève une PermissionError.

Dans un script de type Evénement déclencheur (DocType Event), frappe.db.commit et frappe.db.rollback ne sont pas disponibles.

# Script serveur de type API (exemple fictif)
commande = frappe.get_doc("Sales Order", frappe.form_dict.commande)
commande.add_comment("Comment", "Relance envoyée")
frappe.sendmail(
    recipients=[commande.contact_email],
    subject=f"Commande {commande.name}",
    message="Votre commande est en cours de préparation.",
)

Activer les scripts serveur

Les scripts serveur sont désactivés par défaut. Activez-les dans la configuration du bench :

bench set-config -g server_script_enabled 1

Sans cette clé, safe_exec refuse d'exécuter le code et affiche « Server Scripts are disabled ».

Appeler safe_exec depuis une application

from frappe.utils.safe_exec import safe_exec

local_vars = {}
safe_exec("resultat = frappe.db.count('Customer')", _locals=local_vars)
local_vars["resultat"]

La signature est safe_exec(script, _globals=None, _locals=None, *, restrict_commit_rollback=False, script_filename=None). restrict_commit_rollback=True retire commit, rollback et add_index. Pendant l'exécution, frappe.flags.in_safe_exec vaut True.

Pour une expression seule, utilisez safe_eval(code, eval_globals=None, eval_locals=None) du même module.