Restrict Genshi evaluation

Closes #14869 #5160
(grafted from 101aaf12c6bb1a333c9105b1e6677a4a5f49605c)

--HG--
branch : 7.0
This commit is contained in:
Cédric Krier committed 2026-06-08 23:44:10 +02:00
1 parent ec42f804a6
commit 993cf9f948
4 files changed
+148

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Restrict Genshi evaluation (issue14869, issue5160)
Version 7.0.52 - 2026-06-18
---------------------------
+4
View File
@@ -10,6 +10,8 @@ from email import charset
import __main__
from lxml import etree, objectify
from ._safe_genshi import genshi_patch
__version__ = "7.0.53"
if not os.environ.get('TRYTOND_APPNAME'):
@@ -31,6 +33,8 @@ charset.add_charset('utf-8', charset.QP, charset.QP)
etree.set_default_parser(etree.XMLParser(resolve_entities=False))
objectify.set_default_parser(objectify.makeparser(resolve_entities=False))
genshi_patch()
class _RequestPatchFinder:
def find_spec(self, fullname, path, target=None):
+107
View File
@@ -0,0 +1,107 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from genshi.template.astutil import ASTTransformer
from genshi.template.eval import (
BUILTINS, Code, ExpressionASTTransformer, TemplateASTTransformer)
class _SafeASTTransformer(ASTTransformer):
def visit_Attribute(self, node):
if (node.attr.startswith('_')
and node.attr not in {'__class__', '__name__'}):
raise ValueError(f"invalid attribute {node.attr!r}")
return super().visit_Attribute(node)
def visit_Import(self, node):
raise ValueError("invalid import")
def visit_ImportFrom(self, node):
raise ValueError("invalid import from")
def visit_Name(self, node):
if (node.id.startswith('_')
and not node.id.startswith('__relatorio_')
and node.id not in {'_'}):
raise ValueError(f"invalid name {node.id!r}")
return super().visit_Name(node)
class SafeExpressionASTTransformer(
_SafeASTTransformer, ExpressionASTTransformer):
pass
class SafeTemplateASTTransformer(_SafeASTTransformer, TemplateASTTransformer):
pass
ALLOWED_BUILTINS = {
'False',
'True',
'None',
'abs',
'all',
'any',
'ascii',
'bin',
'bool',
'bytearray',
'bytes',
'chr',
'complex',
'dict',
'dir',
'divmod',
'enumerate',
'filter',
'float',
'format',
'frozenset',
'hasattr',
'hash',
'hex',
'int',
'iter',
'len',
'list',
'map',
'max',
'min',
'next',
'oct',
'ord',
'pow',
'range',
'repr',
'reversed',
'round',
'set'
'slice',
'sorted',
'str',
'sum',
'tuple',
'zip',
}
def genshi_patch():
code__init__ = Code.__init__
def patched_code__init__(
self, source, filename=None, lineno=-1, lookup='strict',
xform=None):
if self.mode == 'eval':
xform = SafeExpressionASTTransformer
else:
xform = SafeTemplateASTTransformer
code__init__(
self, source, filename=filename, lineno=lineno, lookup=lookup,
xform=xform)
Code.__init__ = patched_code__init__
for name in BUILTINS.keys() - ALLOWED_BUILTINS:
BUILTINS.pop(name)
+36
View File
@@ -0,0 +1,36 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import unittest
from genshi.template import MarkupTemplate, TextTemplate
from genshi.template.eval import UndefinedError
class GenshiTestCase(unittest.TestCase):
def test_no_builtins(self):
"Test no builtins"
with self.assertRaises(UndefinedError):
str(TextTemplate("${open('%s').read()}" % __file__).generate())
def test_no_private_name(self):
"Test no private name"
with self.assertRaisesRegex(ValueError, r"invalid name '__import__'"):
str(TextTemplate("${__import__('os')}").generate())
def test_no_private_attribute(self):
"Test no private attribute"
with self.assertRaisesRegex(
ValueError, r"invalid attribute '__getattribute__'"):
str(TextTemplate("${True.__getattribute__}").generate())
def test_no_import(self):
"Test no import"
with self.assertRaisesRegex(ValueError, r"invalid import"):
str(MarkupTemplate("<?python import os?>").generate())
def test_no_import_from(self):
"Test no import from"
with self.assertRaisesRegex(ValueError, r"invalid import from"):
str(MarkupTemplate("<?python from os import popen?>").generate())