* Sylvain Beucler: " Re: CVE triage for several issues in Tryton" (Mon, 21 Sep
  2026 21:30:59 +0200):

Hi,

> On 21/09/2026 10:09, Mathias Behrle wrote:
> > * Sylvain Beucler: " Re: CVE triage for several issues in Tryton" (Sat, 19
> > Sep 2026 18:13:24 +0200):
> >> I suggest you coordinate with the weasyprint maintainer to decide
> >> whether to backport fixes/features, or try and bump the version in
> >> bookworm.
> > 
> > Would it be allowed to have a newer version in bookworm than in stable?
> 
> No, because that would break dist upgrades.
> 
> However if you're going to fix this in bookworm it is expected that you 
> fix it in the other dists too :)
> 
> Special Debian version '-0+reallyX.Y.Z' (X.Y.Z being the new upstream 
> version) can also be used for such stable backports, depending on the 
> situation.

I had a second look at the weasyprint code and decided to adapt rather the
Tryton patch than the weasyprint code. The patch(es) for weasyprint would add
substantially more complexity than on the Tryton side and since the CVEs leading
to the changed code in weasyprint are marked as minor (no DSA) I guess it would
not be that simple to get that in Debian stable.

The result for bookworm is in the attached debdiff (and also on slasa in [1]).
The test suite is running smoothly. Please review and tell, if I can upload to
bookworm-security. I would like to ask the LTS team to do the paperwork.


Cheers,
Mathias

[1]
https://salsa.debian.org/tryton-team/tryton-server/-/tree/debian-bookworm?ref_type=heads



-- 

    Mathias Behrle
    PGP/GnuPG key availabable from any keyserver, ID: 0xD6D09BE48405BBF6
    AC29 7E5C 46B9 D0B6 1C71  7681 D6D0 9BE4 8405 BBF6
diff -Nru tryton-server-6.0.29/debian/changelog tryton-server-6.0.29/debian/changelog
--- tryton-server-6.0.29/debian/changelog	2025-11-25 13:02:57.000000000 +0100
+++ tryton-server-6.0.29/debian/changelog	2026-09-29 08:54:25.000000000 +0200
@@ -1,3 +1,30 @@
+tryton-server (6.0.29-2+deb12u5) bookworm-security; urgency=high
+
+  * Add 10_restrict_weasyprint_protocols.patch.
+    Forbid weasyprint to access local files
+    From https://discuss.tryton.org/t/security-release-for-issue-14947:
+    Cédric Krier has discovered that Tryton does not prevent weasyprint
+    to access local files when rendering HTML report to PDF.
+    
+    https://foss.heptapod.net/tryton/tryton/-/work_items/14947
+    The weasyprint documentation states that it can be used to access local files.
+    We should probably use a custom fetcher which forbid file:// protocol.
+    
+    This patch was adapted to match the weasyprint behavior also for versions < 68.
+
+  * Add 09_restrict_genshi_evaluation.patch.
+    Restrict Genshi evaluation
+    From https://discuss.tryton.org/t/security-release-for-issue-5160-and-14869:
+    Security Release for issue #5160 and #14869
+    The user titou has discovered that the administrator group can execute
+    Python code on the server which is hidden inside an uploaded report template.
+    And Dan Shallom has discovered that the same can also be accomplished by
+    the marketing group when uploading marketing email templates.
+    This patch also contains the subsequent fixes from
+    https://bugs.tryton.org/14928, https://bugs.tryton.org/14932
+
+ -- Mathias Behrle <[email protected]>  Tue, 29 Sep 2026 08:54:25 +0200
+
 tryton-server (6.0.29-2+deb12u4) bookworm-security; urgency=high
 
   * Add 06_traceback_in_RPC.patch,
diff -Nru tryton-server-6.0.29/debian/patches/09_restrict_genshi_evaluation.patch tryton-server-6.0.29/debian/patches/09_restrict_genshi_evaluation.patch
--- tryton-server-6.0.29/debian/patches/09_restrict_genshi_evaluation.patch	1970-01-01 01:00:00.000000000 +0100
+++ tryton-server-6.0.29/debian/patches/09_restrict_genshi_evaluation.patch	2026-09-29 08:54:25.000000000 +0200
@@ -0,0 +1,188 @@
+Description: Restrict Genshi evaluation
+ From https://discuss.tryton.org/t/security-release-for-issue-5160-and-14869:
+ Security Release for issue #5160 and #14869
+ The user titou has discovered that the administrator group can execute
+ Python code on the server which is hidden inside an uploaded report template.
+ And Dan Shallom has discovered that the same can also be accomplished by
+ the marketing group when uploading marketing email templates.
+ This patch also contains the subsequent fixes from
+ https://bugs.tryton.org/14928, https://bugs.tryton.org/14932
+Author: Cédric Krier <[email protected]>, Adrià Tarroja Caubet <[email protected]>
+Last-Update: 2026-09-02
+Bug-Upstream: https://bugs.tryton.org/5160, https://bugs.tryton.org/14869,
+ https://bugs.tryton.org/14928, https://bugs.tryton.org/14932
+
+--- a/trytond/__init__.py
++++ b/trytond/__init__.py
+@@ -7,6 +7,8 @@
+ 
+ from lxml import etree, objectify
+ 
++from ._safe_genshi import genshi_patch
++
+ __version__ = "6.0.29"
+ 
+ os.environ['TZ'] = 'UTC'
+@@ -22,3 +24,5 @@
+ # prevent XML vulnerabilities by default
+ etree.set_default_parser(etree.XMLParser(resolve_entities=False))
+ objectify.set_default_parser(objectify.makeparser(resolve_entities=False))
++
++genshi_patch()
+--- /dev/null
++++ b/trytond/_safe_genshi.py
+@@ -0,0 +1,108 @@
++# 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__', '__url__', '__href__'}):
++            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)
+--- /dev/null
++++ b/trytond/tests/test_genshi.py
+@@ -0,0 +1,43 @@
++# 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())
++
++
++def suite():
++    suite_ = unittest.TestSuite()
++    suite_.addTests(
++        unittest.TestLoader().loadTestsFromTestCase(GenshiTestCase))
++    return suite_
diff -Nru tryton-server-6.0.29/debian/patches/10_restrict_weasyprint_protocols.patch tryton-server-6.0.29/debian/patches/10_restrict_weasyprint_protocols.patch
--- tryton-server-6.0.29/debian/patches/10_restrict_weasyprint_protocols.patch	1970-01-01 01:00:00.000000000 +0100
+++ tryton-server-6.0.29/debian/patches/10_restrict_weasyprint_protocols.patch	2026-09-29 08:54:25.000000000 +0200
@@ -0,0 +1,58 @@
+Description: Forbid weasyprint to access local files
+ From https://discuss.tryton.org/t/security-release-for-issue-14947:
+ Cédric Krier has discovered that Tryton does not prevent weasyprint
+ to access local files when rendering HTML report to PDF.
+ 
+ https://foss.heptapod.net/tryton/tryton/-/work_items/14947
+ The weasyprint documentation states that it can be used to access local files.
+ We should probably use a custom fetcher which forbid file:// protocol.
+
+ This patch was adapted to match the weasyprint behavior also for versions < 68.
+Author: Cédric Krier <[email protected]>, Mathias Behrle <[email protected]>
+Last-Update: 2026-09-28
+Bug-Upstream: https://bugs.tryton.org/14907
+--- a/trytond/report/report.py
++++ b/trytond/report/report.py
+@@ -29,6 +29,7 @@
+     weasyprint = None
+ 
+ from genshi.filters import Translator
++from urllib.parse import urlparse
+ 
+ from trytond.i18n import gettext
+ from trytond.pool import Pool, PoolBase
+@@ -91,6 +92,21 @@
+ 
+ REPORT_NAME_MAX_LENGTH = 200
+ 
++ALLOWED_PROTOCOLS = {'http', 'https'}
++
++
++def _restricted_url_fetcher(url, *args, **kwargs):
++    scheme = urlparse(url).scheme.lower()
++    if scheme not in ALLOWED_PROTOCOLS:
++        raise ValueError("URL scheme '%s' is not allowed: %s" % (scheme, url))
++    return weasyprint.default_url_fetcher(url, *args, **kwargs)
++
++
++def _get_url_fetcher():
++    if hasattr(weasyprint, 'URLFetcher'):  # WeasyPrint >= 68
++        return weasyprint.URLFetcher(allowed_protocols=ALLOWED_PROTOCOLS)
++    return _restricted_url_fetcher
++
+ 
+ class TranslateFactory:
+
+@@ -348,8 +356,10 @@
+         if (weasyprint
+                 and input_format in {'html', 'xhtml'}
+                 and output_format == 'pdf'):
+-            return output_format, weasyprint.HTML(string=data).write_pdf()
+-
++            return output_format, weasyprint.HTML(
++                string=data,
++                url_fetcher=_get_url_fetcher(),
++                ).write_pdf()
+         if input_format == output_format and output_format in MIMETYPES:
+             return output_format, data
+ 
diff -Nru tryton-server-6.0.29/debian/patches/series tryton-server-6.0.29/debian/patches/series
--- tryton-server-6.0.29/debian/patches/series	2025-11-25 12:55:56.000000000 +0100
+++ tryton-server-6.0.29/debian/patches/series	2026-09-29 08:54:25.000000000 +0200
@@ -6,3 +6,5 @@
 06_traceback_in_RPC.patch
 07_enforce_access_check_html_editor.patch
 08_enforce_access_check_export_data.patch
+09_restrict_genshi_evaluation.patch
+10_restrict_weasyprint_protocols.patch

Attachment: pgpn8x8QVawKa.pgp
Description: Digitale Signatur von OpenPGP

Reply via email to