diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py
index 344aa7bde0..101335f6f8 100644
--- a/src/backend/InvenTree/InvenTree/helpers.py
+++ b/src/backend/InvenTree/InvenTree/helpers.py
@@ -21,16 +21,19 @@ from django.http import StreamingHttpResponse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
-import bleach
-import bleach.css_sanitizer
-import bleach.sanitizer
+import nh3
import structlog
-from bleach import clean
from djmoney.money import Money
from PIL import Image
from stdimage.models import StdImageField, StdImageFieldFile
from common.currency import currency_code_default
+from InvenTree.sanitizer import (
+ DEAFAULT_ATTRS,
+ DEFAULT_CSS,
+ DEFAULT_PROTOCOLS,
+ DEFAULT_TAGS,
+)
from .setting.storages import StorageBackends
from .settings import MEDIA_URL, STATIC_URL
@@ -895,13 +898,13 @@ def clean_decimal(number):
def strip_html_tags(value: str, raise_error=True, field_name=None):
- """Strip HTML tags from an input string using the bleach library.
+ """Strip HTML tags from an input string using the nh3 library.
If raise_error is True, a ValidationError will be thrown if HTML tags are detected
"""
value = str(value).strip()
- cleaned = clean(value, strip=True, tags=[], attributes=[])
+ cleaned = nh3.clean(value, tags=frozenset())
# Add escaped characters back in
replacements = {'>': '>', '<': '<', '&': '&'}
@@ -961,34 +964,32 @@ def clean_markdown(value: str) -> str:
output_format='html',
)
- # Bleach settings
- whitelist_tags = markdownify_settings.get(
- 'WHITELIST_TAGS', bleach.sanitizer.ALLOWED_TAGS
- )
- whitelist_attrs = markdownify_settings.get(
- 'WHITELIST_ATTRS', bleach.sanitizer.ALLOWED_ATTRIBUTES
- )
- whitelist_styles = markdownify_settings.get(
- 'WHITELIST_STYLES', bleach.css_sanitizer.ALLOWED_CSS_PROPERTIES
- )
+ # nh3 sanitizer settings
+ whitelist_tags = markdownify_settings.get('WHITELIST_TAGS', DEFAULT_TAGS)
+ whitelist_attrs = markdownify_settings.get('WHITELIST_ATTRS', DEAFAULT_ATTRS)
+ whitelist_styles = markdownify_settings.get('WHITELIST_STYLES', DEFAULT_CSS)
whitelist_protocols = markdownify_settings.get(
- 'WHITELIST_PROTOCOLS', bleach.sanitizer.ALLOWED_PROTOCOLS
+ 'WHITELIST_PROTOCOLS', DEFAULT_PROTOCOLS
)
- strip = markdownify_settings.get('STRIP', True)
- css_sanitizer = bleach.css_sanitizer.CSSSanitizer(
- allowed_css_properties=whitelist_styles
- )
- cleaner = bleach.Cleaner(
- tags=whitelist_tags,
- attributes=whitelist_attrs,
- css_sanitizer=css_sanitizer,
- protocols=whitelist_protocols,
- strip=strip,
- )
+ # Convert bleach-style attributes (list or dict) to nh3-compatible dict format
+ if isinstance(whitelist_attrs, (list, tuple, set, frozenset)):
+ attrs_dict = {'*': set(whitelist_attrs)}
+ elif isinstance(whitelist_attrs, dict):
+ attrs_dict = {tag: set(allowed) for tag, allowed in whitelist_attrs.items()}
+ else:
+ attrs_dict = None
# Clean the HTML content (for comparison). This must be the same as the original content
- clean_html = cleaner.clean(html)
+ clean_html = nh3.clean(
+ html,
+ tags=set(whitelist_tags),
+ attributes=attrs_dict,
+ url_schemes=set(whitelist_protocols),
+ filter_style_properties=set(whitelist_styles),
+ link_rel=None,
+ strip_comments=True,
+ )
if html != clean_html:
raise ValidationError(_('Data contains prohibited markdown content'))
diff --git a/src/backend/InvenTree/InvenTree/mixins.py b/src/backend/InvenTree/InvenTree/mixins.py
index 5dd51c30ca..5489404b10 100644
--- a/src/backend/InvenTree/InvenTree/mixins.py
+++ b/src/backend/InvenTree/InvenTree/mixins.py
@@ -19,7 +19,7 @@ from InvenTree.serializers import FilterableSerializerMixin
class CleanMixin:
- """Model mixin class which cleans inputs using the Mozilla bleach tools."""
+ """Model mixin class which cleans inputs using nh3."""
# Define a list of field names which will *not* be cleaned
SAFE_FIELDS = []
@@ -52,16 +52,7 @@ class CleanMixin:
return Response(serializer.data)
def clean_string(self, field: str, data: str) -> str:
- """Clean / sanitize a single input string.
-
- Note that this function will *allow* orphaned <>& characters,
- which would normally be escaped by bleach.
-
- Nominally, the only thing that will be "cleaned" will be HTML tags
-
- Ref: https://github.com/mozilla/bleach/issues/192
-
- """
+ """Clean / sanitize a single input string."""
cleaned = data
# By default, newline characters are removed
@@ -101,7 +92,7 @@ class CleanMixin:
def clean_data(self, data: dict) -> dict:
"""Clean / sanitize data.
- This uses Mozilla's bleach under the hood to disable certain html tags by
+ This uses nh3 under the hood to disable certain html tags by
encoding them - this leads to script tags etc. to not work.
The results can be longer then the input; might make some character combinations
`ugly`. Prevents XSS on the server-level.
diff --git a/src/backend/InvenTree/InvenTree/sanitizer.py b/src/backend/InvenTree/InvenTree/sanitizer.py
index ce093b96fe..55dbd24770 100644
--- a/src/backend/InvenTree/InvenTree/sanitizer.py
+++ b/src/backend/InvenTree/InvenTree/sanitizer.py
@@ -1,7 +1,66 @@
"""Functions to sanitize user input files."""
-from bleach import clean
-from bleach.css_sanitizer import CSSSanitizer
+import nh3
+
+# Allowed CSS properties for SVG sanitization (combines general CSS and SVG-specific properties)
+_SVG_ALLOWED_CSS_PROPERTIES = frozenset([
+ # General CSS (matching bleach's original ALLOWED_CSS_PROPERTIES)
+ 'azimuth',
+ 'background-color',
+ 'border-bottom-color',
+ 'border-collapse',
+ 'border-color',
+ 'border-left-color',
+ 'border-right-color',
+ 'border-top-color',
+ 'clear',
+ 'color',
+ 'cursor',
+ 'direction',
+ 'display',
+ 'elevation',
+ 'float',
+ 'font',
+ 'font-family',
+ 'font-size',
+ 'font-style',
+ 'font-variant',
+ 'font-weight',
+ 'height',
+ 'letter-spacing',
+ 'line-height',
+ 'overflow',
+ 'pause',
+ 'pause-after',
+ 'pause-before',
+ 'pitch',
+ 'pitch-range',
+ 'richness',
+ 'speak',
+ 'speak-header',
+ 'speak-numeral',
+ 'speak-punctuation',
+ 'speech-rate',
+ 'stress',
+ 'text-align',
+ 'text-decoration',
+ 'text-indent',
+ 'unicode-bidi',
+ 'vertical-align',
+ 'voice-family',
+ 'volume',
+ 'white-space',
+ 'width',
+ # SVG-specific CSS (matching bleach's ALLOWED_SVG_PROPERTIES)
+ 'fill',
+ 'fill-opacity',
+ 'fill-rule',
+ 'stroke',
+ 'stroke-linecap',
+ 'stroke-linejoin',
+ 'stroke-opacity',
+ 'stroke-width',
+])
ALLOWED_ELEMENTS_SVG = [
'a',
@@ -184,6 +243,74 @@ ALLOWED_ATTRIBUTES_SVG = [
'style',
]
+# Default allowlists (matching bleach's original defaults)
+# TODO: I do not see us needing a bunch of these but I do not want to introduce a breaking change; we might want to narroy this down with the next breaking change
+DEFAULT_TAGS = frozenset([
+ 'a',
+ 'abbr',
+ 'acronym',
+ 'b',
+ 'blockquote',
+ 'code',
+ 'em',
+ 'i',
+ 'li',
+ 'ol',
+ 'strong',
+ 'ul',
+])
+DEAFAULT_ATTRS = {'a': {'href', 'title'}, 'abbr': {'title'}, 'acronym': {'title'}}
+DEFAULT_CSS = frozenset([
+ 'azimuth',
+ 'background-color',
+ 'border-bottom-color',
+ 'border-collapse',
+ 'border-color',
+ 'border-left-color',
+ 'border-right-color',
+ 'border-top-color',
+ 'clear',
+ 'color',
+ 'cursor',
+ 'direction',
+ 'display',
+ 'elevation',
+ 'float',
+ 'font',
+ 'font-family',
+ 'font-size',
+ 'font-style',
+ 'font-variant',
+ 'font-weight',
+ 'height',
+ 'letter-spacing',
+ 'line-height',
+ 'overflow',
+ 'pause',
+ 'pause-after',
+ 'pause-before',
+ 'pitch',
+ 'pitch-range',
+ 'richness',
+ 'speak',
+ 'speak-header',
+ 'speak-numeral',
+ 'speak-punctuation',
+ 'speech-rate',
+ 'stress',
+ 'text-align',
+ 'text-decoration',
+ 'text-indent',
+ 'unicode-bidi',
+ 'vertical-align',
+ 'voice-family',
+ 'volume',
+ 'white-space',
+ 'width',
+])
+# TODO: We might want to respect the setting EXTRA_URL_SCHEMES here but that would be breaking
+DEFAULT_PROTOCOLS = frozenset(['http', 'https', 'mailto'])
+
def sanitize_svg(
file_data,
@@ -206,13 +333,16 @@ def sanitize_svg(
if isinstance(file_data, bytes):
file_data = file_data.decode('utf-8')
- cleaned = clean(
+ # nh3 requires attributes as dict[str, set[str]]; convert from list (allowed for all elements)
+ attrs_dict = {elem: set(attributes) for elem in elements}
+
+ cleaned = nh3.clean(
file_data,
- tags=elements,
- attributes=attributes,
- strip=strip,
+ tags=set(elements),
+ attributes=attrs_dict,
+ filter_style_properties=_SVG_ALLOWED_CSS_PROPERTIES,
strip_comments=strip,
- css_sanitizer=CSSSanitizer(),
+ link_rel=None,
)
return cleaned
diff --git a/src/backend/InvenTree/InvenTree/tests.py b/src/backend/InvenTree/InvenTree/tests.py
index c8a999b563..7a97997b42 100644
--- a/src/backend/InvenTree/InvenTree/tests.py
+++ b/src/backend/InvenTree/InvenTree/tests.py
@@ -1604,11 +1604,11 @@ class SanitizerTest(TestCase):
def test_svg_sanitizer(self):
"""Test that SVGs are sanitized accordingly."""
valid_string = """"""
dangerous_string = valid_string.format('')
- # Test that valid string
+ # Test that valid string passes through unchanged
self.assertEqual(valid_string, sanitize_svg(valid_string))
# Test that invalid string is cleaned
diff --git a/src/backend/InvenTree/part/test_api.py b/src/backend/InvenTree/part/test_api.py
index cc6317b930..557d594f14 100644
--- a/src/backend/InvenTree/part/test_api.py
+++ b/src/backend/InvenTree/part/test_api.py
@@ -276,7 +276,7 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
# There should not be any templates left at this point
self.assertEqual(PartCategoryParameterTemplate.objects.count(), 0)
- def test_bleach(self):
+ def test_sanitizer(self):
"""Test that the data cleaning functionality is working.
This helps to protect against XSS injection
diff --git a/src/backend/InvenTree/report/templatetags/barcode.py b/src/backend/InvenTree/report/templatetags/barcode.py
index 9a87adb3b3..d6641a0abe 100644
--- a/src/backend/InvenTree/report/templatetags/barcode.py
+++ b/src/backend/InvenTree/report/templatetags/barcode.py
@@ -33,7 +33,7 @@ def image_data(img, fmt='PNG') -> str:
def clean_barcode(data):
"""Return a 'cleaned' string for encoding into a barcode / qrcode.
- - This function runs the data through bleach, and removes any malicious HTML content.
+ - This function sanitizes the data using nh3, and removes any malicious HTML content.
- Used to render raw barcode data into the rendered HTML templates
"""
from InvenTree.helpers import strip_html_tags
diff --git a/src/backend/requirements-3.14.txt b/src/backend/requirements-3.14.txt
index dcc30ec14c..227ce1506f 100644
--- a/src/backend/requirements-3.14.txt
+++ b/src/backend/requirements-3.14.txt
@@ -89,9 +89,9 @@ bcrypt==5.0.0 \
# via
# -c src/backend/requirements.txt
# paramiko
-bleach[css]==6.3.0 \
- --hash=sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22 \
- --hash=sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6
+bleach==4.1.0 \
+ --hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \
+ --hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994
# via
# -c src/backend/requirements.txt
# django-markdownify
@@ -631,9 +631,9 @@ django-maintenance-mode==0.22.0 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
-django-markdownify==0.9.6 \
- --hash=sha256:9863b2bfa6d159ad1423dc93bf0d6eadc6413776de304049aa9fcfa5edd2ce1c \
- --hash=sha256:edcf47b2026d55a8439049d35c8b54e11066a4856c4fad1060e139cb3d2eee52
+django-markdownify==0.9.1 \
+ --hash=sha256:06ff2994ff09ce030b50de8c6fc5b89b9c25a66796948aff55370716ca1233af \
+ --hash=sha256:24ba68b8a5996b6ec9632d11a3fd2e7159cb7e6becd3104e0a9372b5a2a148ef
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
@@ -1277,6 +1277,37 @@ markupsafe==3.0.3 \
# via
# -c src/backend/requirements.txt
# jinja2
+nh3==0.3.4 \
+ --hash=sha256:07999b998bf89692738f15c0eac76a416382932f855709e0b7488b595c30ec89 \
+ --hash=sha256:0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304 \
+ --hash=sha256:0d825722a1e8cbc87d7ca1e47ffb1d2a6cf343ad4c1b8465becf7cadcabcdfd0 \
+ --hash=sha256:18a2e44ccb29cbb45071b8f3f2dab9ebfb41a6516f328f91f1f1fd18196239a4 \
+ --hash=sha256:3390e4333883673a684ce16c1716b481e91782d6f56dec5c85fed9feedb23382 \
+ --hash=sha256:41e46b3499918ab6128b6421677b316e79869d0c140da24069d220a94f4e72d1 \
+ --hash=sha256:43ad4eedee7e049b9069bc015b7b095d320ed6d167ecec111f877de1540656e9 \
+ --hash=sha256:47d749d99ae005ab19517224140b280dd56e77b33afb82f9b600e106d0458003 \
+ --hash=sha256:4aa8b43e68c26b68069a3b6cef09de166d1d7fa140cf8d77e409a46cbf742e44 \
+ --hash=sha256:554cc2bab281758e94d770c3fb0bf2d8be5fb403ef6b2e8841dd7c1615df7a0f \
+ --hash=sha256:72e4e9ca1c4bd41b4a28b0190edc2e21e3f71496acd36a0162858e1a28db3d7e \
+ --hash=sha256:75643c22f5092d8e209f766ee8108c400bc1e44760fc94d2d638eb138d18f853 \
+ --hash=sha256:7cae217f031809321db962cd7e092bda8d4e95a87f78c0226628fa6c2ea8ebc5 \
+ --hash=sha256:80b955d802bf365bd42e09f6c3d64567dce777d20e97968d94b3e9d9e99b265e \
+ --hash=sha256:87dac8d611b4a478400e0821a13b35770e88c266582f065e7249d6a37b0f86e8 \
+ --hash=sha256:883d5a6d6ee8078c4afc8e96e022fe579c4c265775ff6ee21e39b8c542cabab3 \
+ --hash=sha256:8b61058f34c2105d44d2a4d4241bacf603a1ef5c143b08766bbd0cf23830118f \
+ --hash=sha256:8d697e19f2995b337f648204848ac3a528eaafffc39e7ce4ac6b7a2fbe6c84af \
+ --hash=sha256:9337517edb7c10228252cce2898e20fb3d77e32ffaccbb3c66897927d74215a0 \
+ --hash=sha256:96709a379997c1b28c8974146ca660b0dcd3794f4f6d50c1ea549bab39ac6ade \
+ --hash=sha256:c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75 \
+ --hash=sha256:ca90397c8d36c1535bf1988b2bed006597337843a164c7ec269dc8813f37536b \
+ --hash=sha256:d866701affe67a5171b916b5c076e767a74c6a9efb7fb2006eb8d3c5f9a293d5 \
+ --hash=sha256:d8bebcb20ab4b91858385cd98fe58046ec4a624275b45ef9b976475604f45b49 \
+ --hash=sha256:dbe76feaa44e2ef9436f345016012a591550e77818876a8de5c8bc2a248e08df \
+ --hash=sha256:f5f214618ad5eff4f2a6b13a8d4da4d9e7f37c569d90a13fb9f0caaf7d04fe21 \
+ --hash=sha256:f987cb56458323405e8e5ea827e1befcf141ffa0c0ac797d6d02e6b646056d9a
+ # via
+ # -c src/backend/requirements.txt
+ # -r src/backend/requirements.in
oauthlib==3.3.1 \
--hash=sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9 \
--hash=sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1
@@ -1447,6 +1478,7 @@ packaging==26.0 \
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
# via
# -c src/backend/requirements.txt
+ # bleach
# gunicorn
# opentelemetry-instrumentation
paramiko==4.0.0 \
@@ -2086,6 +2118,7 @@ six==1.17.0 \
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
# via
# -c src/backend/requirements.txt
+ # bleach
# python-dateutil
sqlparse==0.5.5 \
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
@@ -2106,12 +2139,11 @@ tablib[xls, xlsx, yaml]==3.9.0 \
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
-tinycss2==1.4.0 \
- --hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \
- --hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289
+tinycss2==1.5.1 \
+ --hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661 \
+ --hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
# via
# -c src/backend/requirements.txt
- # bleach
# cssselect2
# weasyprint
tinyhtml5==2.1.0 \
@@ -2165,9 +2197,9 @@ wcwidth==0.6.0 \
# -c src/backend/requirements.txt
# blessed
# prettytable
-weasyprint==66.0 \
- --hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \
- --hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40
+weasyprint==68.1 \
+ --hash=sha256:4dc3ba63c68bbbce3e9617cb2226251c372f5ee90a8a484503b1c099da9cf5be \
+ --hash=sha256:d3b752049b453a5c95edb27ce78d69e9319af5a34f257fa0f4c738c701b4184e
# via
# -c src/backend/requirements.txt
# -r src/backend/requirements.in
diff --git a/src/backend/requirements.in b/src/backend/requirements.in
index 661392d88d..203bc9d74e 100644
--- a/src/backend/requirements.in
+++ b/src/backend/requirements.in
@@ -37,6 +37,7 @@ drf-spectacular # DRF API documentation
feedparser # RSS newsfeed parser
gunicorn # Gunicorn web server
jinja2 # Jinja2 templating engine
+nh3 # HTML sanitization (replaces bleach)
pdf2image # PDF to image conversion
pillow # Image manipulation
pint # Unit conversion
diff --git a/src/backend/requirements.txt b/src/backend/requirements.txt
index 07c3464c1e..eb0818cb78 100644
--- a/src/backend/requirements.txt
+++ b/src/backend/requirements.txt
@@ -87,9 +87,9 @@ bcrypt==5.0.0 \
--hash=sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822 \
--hash=sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b
# via paramiko
-bleach[css]==6.3.0 \
- --hash=sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22 \
- --hash=sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6
+bleach==4.1.0 \
+ --hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \
+ --hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994
# via django-markdownify
blessed==1.33.0 \
--hash=sha256:1bc8ecac6d139286ea51ec1683433528ce75b0c60db77b7d881112bf9fc85b0f \
@@ -585,9 +585,9 @@ django-maintenance-mode==0.22.0 \
--hash=sha256:502f04f845d6996e8add321186b3b9236c3702de7cb0ab14952890af6523b9e5 \
--hash=sha256:a9cf2ba79c9945bd67f98755a6cfd281869d39b3745bbb5d1f571d058657aa85
# via -r src/backend/requirements.in
-django-markdownify==0.9.6 \
- --hash=sha256:9863b2bfa6d159ad1423dc93bf0d6eadc6413776de304049aa9fcfa5edd2ce1c \
- --hash=sha256:edcf47b2026d55a8439049d35c8b54e11066a4856c4fad1060e139cb3d2eee52
+django-markdownify==0.9.1 \
+ --hash=sha256:06ff2994ff09ce030b50de8c6fc5b89b9c25a66796948aff55370716ca1233af \
+ --hash=sha256:24ba68b8a5996b6ec9632d11a3fd2e7159cb7e6becd3104e0a9372b5a2a148ef
# via -r src/backend/requirements.in
django-money==3.6.0 \
--hash=sha256:94402f2831f2726b94ef2da35b4059441b4c0aedfc47b312472200d4ffdf8d73 \
@@ -1145,6 +1145,35 @@ markupsafe==3.0.3 \
--hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
# via jinja2
+nh3==0.3.4 \
+ --hash=sha256:07999b998bf89692738f15c0eac76a416382932f855709e0b7488b595c30ec89 \
+ --hash=sha256:0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304 \
+ --hash=sha256:0d825722a1e8cbc87d7ca1e47ffb1d2a6cf343ad4c1b8465becf7cadcabcdfd0 \
+ --hash=sha256:18a2e44ccb29cbb45071b8f3f2dab9ebfb41a6516f328f91f1f1fd18196239a4 \
+ --hash=sha256:3390e4333883673a684ce16c1716b481e91782d6f56dec5c85fed9feedb23382 \
+ --hash=sha256:41e46b3499918ab6128b6421677b316e79869d0c140da24069d220a94f4e72d1 \
+ --hash=sha256:43ad4eedee7e049b9069bc015b7b095d320ed6d167ecec111f877de1540656e9 \
+ --hash=sha256:47d749d99ae005ab19517224140b280dd56e77b33afb82f9b600e106d0458003 \
+ --hash=sha256:4aa8b43e68c26b68069a3b6cef09de166d1d7fa140cf8d77e409a46cbf742e44 \
+ --hash=sha256:554cc2bab281758e94d770c3fb0bf2d8be5fb403ef6b2e8841dd7c1615df7a0f \
+ --hash=sha256:72e4e9ca1c4bd41b4a28b0190edc2e21e3f71496acd36a0162858e1a28db3d7e \
+ --hash=sha256:75643c22f5092d8e209f766ee8108c400bc1e44760fc94d2d638eb138d18f853 \
+ --hash=sha256:7cae217f031809321db962cd7e092bda8d4e95a87f78c0226628fa6c2ea8ebc5 \
+ --hash=sha256:80b955d802bf365bd42e09f6c3d64567dce777d20e97968d94b3e9d9e99b265e \
+ --hash=sha256:87dac8d611b4a478400e0821a13b35770e88c266582f065e7249d6a37b0f86e8 \
+ --hash=sha256:883d5a6d6ee8078c4afc8e96e022fe579c4c265775ff6ee21e39b8c542cabab3 \
+ --hash=sha256:8b61058f34c2105d44d2a4d4241bacf603a1ef5c143b08766bbd0cf23830118f \
+ --hash=sha256:8d697e19f2995b337f648204848ac3a528eaafffc39e7ce4ac6b7a2fbe6c84af \
+ --hash=sha256:9337517edb7c10228252cce2898e20fb3d77e32ffaccbb3c66897927d74215a0 \
+ --hash=sha256:96709a379997c1b28c8974146ca660b0dcd3794f4f6d50c1ea549bab39ac6ade \
+ --hash=sha256:c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75 \
+ --hash=sha256:ca90397c8d36c1535bf1988b2bed006597337843a164c7ec269dc8813f37536b \
+ --hash=sha256:d866701affe67a5171b916b5c076e767a74c6a9efb7fb2006eb8d3c5f9a293d5 \
+ --hash=sha256:d8bebcb20ab4b91858385cd98fe58046ec4a624275b45ef9b976475604f45b49 \
+ --hash=sha256:dbe76feaa44e2ef9436f345016012a591550e77818876a8de5c8bc2a248e08df \
+ --hash=sha256:f5f214618ad5eff4f2a6b13a8d4da4d9e7f37c569d90a13fb9f0caaf7d04fe21 \
+ --hash=sha256:f987cb56458323405e8e5ea827e1befcf141ffa0c0ac797d6d02e6b646056d9a
+ # via -r src/backend/requirements.in
oauthlib==3.3.1 \
--hash=sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9 \
--hash=sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1
@@ -1282,6 +1311,7 @@ packaging==26.0 \
--hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
# via
+ # bleach
# gunicorn
# opentelemetry-instrumentation
paramiko==4.0.0 \
@@ -1859,7 +1889,9 @@ sgmllib3k==1.0.0 \
six==1.17.0 \
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
- # via python-dateutil
+ # via
+ # bleach
+ # python-dateutil
sqlparse==0.5.5 \
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
@@ -1874,11 +1906,10 @@ tablib[xls, xlsx, yaml]==3.9.0 \
--hash=sha256:1b6abd8edb0f35601e04c6161d79660fdcde4abb4a54f66cc9f9054bd55d5fe2 \
--hash=sha256:eda17cd0d4dda614efc0e710227654c60ddbeb1ca92cdcfc5c3bd1fc5f5a6e4a
# via -r src/backend/requirements.in
-tinycss2==1.4.0 \
- --hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \
- --hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289
+tinycss2==1.5.1 \
+ --hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661 \
+ --hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
# via
- # bleach
# cssselect2
# weasyprint
tinyhtml5==2.1.0 \
@@ -1926,9 +1957,9 @@ wcwidth==0.6.0 \
# via
# blessed
# prettytable
-weasyprint==66.0 \
- --hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \
- --hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40
+weasyprint==68.1 \
+ --hash=sha256:4dc3ba63c68bbbce3e9617cb2226251c372f5ee90a8a484503b1c099da9cf5be \
+ --hash=sha256:d3b752049b453a5c95edb27ce78d69e9319af5a34f257fa0f4c738c701b4184e
# via -r src/backend/requirements.in
webencodings==0.5.1 \
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \