diff --git a/frappe/query_builder/utils.py b/frappe/query_builder/utils.py index cbd6147e01..1ddf4fc034 100644 --- a/frappe/query_builder/utils.py +++ b/frappe/query_builder/utils.py @@ -59,10 +59,28 @@ def patch_query_execute(): return frappe.db.sql(query, params, *args, **kwargs) # nosemgrep def prepare_query(query): + import inspect + param_collector = NamedParameterWrapper() query = query.get_sql(param_wrapper=param_collector) if frappe.flags.in_safe_exec and not query.lower().strip().startswith("select"): - raise frappe.PermissionError('Only SELECT SQL allowed in scripting') + callstack = inspect.stack() + if len(callstack) >= 3 and ".py" in callstack[2].filename: + # ignore any query builder methods called from python files + # assumption is that those functions are whitelisted already. + + # since query objects are patched everywhere any query.run() + # will have callstack like this: + # frame0: this function prepare_query() + # frame1: execute_query() + # frame2: frame that called `query.run()` + # + # if frame2 is server script it wont have a filename and hence + # it shouldn't be allowed. + # ps. stack() returns `""` as filename. + pass + else: + raise frappe.PermissionError('Only SELECT SQL allowed in scripting') return query, param_collector.get_parameters() query_class = get_attr(str(frappe.qb).split("'")[1])