mirror of
https://github.com/inventree/InvenTree.git
synced 2026-04-07 12:00:58 +00:00
refactor(backend): replace bleach with nh3 and bump weasy (#11655)
* Replace bleach with nh3 for HTML sanitization Agent-Logs-Url: https://github.com/matmair/InvenTree/sessions/913a447a-5efa-4fa3-b8b1-6af5feaa24f0 Co-authored-by: matmair <66015116+matmair@users.noreply.github.com> * reduce diff * bump weasy * fix name * remove old textual refs * move defaults * add some comments --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -21,16 +21,19 @@ from django.http import StreamingHttpResponse
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import bleach
|
import nh3
|
||||||
import bleach.css_sanitizer
|
|
||||||
import bleach.sanitizer
|
|
||||||
import structlog
|
import structlog
|
||||||
from bleach import clean
|
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from stdimage.models import StdImageField, StdImageFieldFile
|
from stdimage.models import StdImageField, StdImageFieldFile
|
||||||
|
|
||||||
from common.currency import currency_code_default
|
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 .setting.storages import StorageBackends
|
||||||
from .settings import MEDIA_URL, STATIC_URL
|
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):
|
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
|
If raise_error is True, a ValidationError will be thrown if HTML tags are detected
|
||||||
"""
|
"""
|
||||||
value = str(value).strip()
|
value = str(value).strip()
|
||||||
|
|
||||||
cleaned = clean(value, strip=True, tags=[], attributes=[])
|
cleaned = nh3.clean(value, tags=frozenset())
|
||||||
|
|
||||||
# Add escaped characters back in
|
# Add escaped characters back in
|
||||||
replacements = {'>': '>', '<': '<', '&': '&'}
|
replacements = {'>': '>', '<': '<', '&': '&'}
|
||||||
@@ -961,34 +964,32 @@ def clean_markdown(value: str) -> str:
|
|||||||
output_format='html',
|
output_format='html',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Bleach settings
|
# nh3 sanitizer settings
|
||||||
whitelist_tags = markdownify_settings.get(
|
whitelist_tags = markdownify_settings.get('WHITELIST_TAGS', DEFAULT_TAGS)
|
||||||
'WHITELIST_TAGS', bleach.sanitizer.ALLOWED_TAGS
|
whitelist_attrs = markdownify_settings.get('WHITELIST_ATTRS', DEAFAULT_ATTRS)
|
||||||
)
|
whitelist_styles = markdownify_settings.get('WHITELIST_STYLES', DEFAULT_CSS)
|
||||||
whitelist_attrs = markdownify_settings.get(
|
|
||||||
'WHITELIST_ATTRS', bleach.sanitizer.ALLOWED_ATTRIBUTES
|
|
||||||
)
|
|
||||||
whitelist_styles = markdownify_settings.get(
|
|
||||||
'WHITELIST_STYLES', bleach.css_sanitizer.ALLOWED_CSS_PROPERTIES
|
|
||||||
)
|
|
||||||
whitelist_protocols = markdownify_settings.get(
|
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(
|
# Convert bleach-style attributes (list or dict) to nh3-compatible dict format
|
||||||
allowed_css_properties=whitelist_styles
|
if isinstance(whitelist_attrs, (list, tuple, set, frozenset)):
|
||||||
)
|
attrs_dict = {'*': set(whitelist_attrs)}
|
||||||
cleaner = bleach.Cleaner(
|
elif isinstance(whitelist_attrs, dict):
|
||||||
tags=whitelist_tags,
|
attrs_dict = {tag: set(allowed) for tag, allowed in whitelist_attrs.items()}
|
||||||
attributes=whitelist_attrs,
|
else:
|
||||||
css_sanitizer=css_sanitizer,
|
attrs_dict = None
|
||||||
protocols=whitelist_protocols,
|
|
||||||
strip=strip,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Clean the HTML content (for comparison). This must be the same as the original content
|
# 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:
|
if html != clean_html:
|
||||||
raise ValidationError(_('Data contains prohibited markdown content'))
|
raise ValidationError(_('Data contains prohibited markdown content'))
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from InvenTree.serializers import FilterableSerializerMixin
|
|||||||
|
|
||||||
|
|
||||||
class CleanMixin:
|
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
|
# Define a list of field names which will *not* be cleaned
|
||||||
SAFE_FIELDS = []
|
SAFE_FIELDS = []
|
||||||
@@ -52,16 +52,7 @@ class CleanMixin:
|
|||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
def clean_string(self, field: str, data: str) -> str:
|
def clean_string(self, field: str, data: str) -> str:
|
||||||
"""Clean / sanitize a single input string.
|
"""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
|
|
||||||
|
|
||||||
"""
|
|
||||||
cleaned = data
|
cleaned = data
|
||||||
|
|
||||||
# By default, newline characters are removed
|
# By default, newline characters are removed
|
||||||
@@ -101,7 +92,7 @@ class CleanMixin:
|
|||||||
def clean_data(self, data: dict) -> dict:
|
def clean_data(self, data: dict) -> dict:
|
||||||
"""Clean / sanitize data.
|
"""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.
|
encoding them - this leads to script tags etc. to not work.
|
||||||
The results can be longer then the input; might make some character combinations
|
The results can be longer then the input; might make some character combinations
|
||||||
`ugly`. Prevents XSS on the server-level.
|
`ugly`. Prevents XSS on the server-level.
|
||||||
|
|||||||
@@ -1,7 +1,66 @@
|
|||||||
"""Functions to sanitize user input files."""
|
"""Functions to sanitize user input files."""
|
||||||
|
|
||||||
from bleach import clean
|
import nh3
|
||||||
from bleach.css_sanitizer import CSSSanitizer
|
|
||||||
|
# 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 = [
|
ALLOWED_ELEMENTS_SVG = [
|
||||||
'a',
|
'a',
|
||||||
@@ -184,6 +243,74 @@ ALLOWED_ATTRIBUTES_SVG = [
|
|||||||
'style',
|
'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(
|
def sanitize_svg(
|
||||||
file_data,
|
file_data,
|
||||||
@@ -206,13 +333,16 @@ def sanitize_svg(
|
|||||||
if isinstance(file_data, bytes):
|
if isinstance(file_data, bytes):
|
||||||
file_data = file_data.decode('utf-8')
|
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,
|
file_data,
|
||||||
tags=elements,
|
tags=set(elements),
|
||||||
attributes=attributes,
|
attributes=attrs_dict,
|
||||||
strip=strip,
|
filter_style_properties=_SVG_ALLOWED_CSS_PROPERTIES,
|
||||||
strip_comments=strip,
|
strip_comments=strip,
|
||||||
css_sanitizer=CSSSanitizer(),
|
link_rel=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|||||||
@@ -1604,11 +1604,11 @@ class SanitizerTest(TestCase):
|
|||||||
def test_svg_sanitizer(self):
|
def test_svg_sanitizer(self):
|
||||||
"""Test that SVGs are sanitized accordingly."""
|
"""Test that SVGs are sanitized accordingly."""
|
||||||
valid_string = """<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2" height="400" width="400">{0}
|
valid_string = """<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2" height="400" width="400">{0}
|
||||||
<path id="path1" d="m -151.78571,359.62883 v 112.76373 l 97.068507,-56.04253 V 303.14815 Z" style="fill:#ddbc91;"></path>
|
<path id="path1" d="m -151.78571,359.62883 v 112.76373 l 97.068507,-56.04253 V 303.14815 Z" style="fill:#ddbc91"></path>
|
||||||
</svg>"""
|
</svg>"""
|
||||||
dangerous_string = valid_string.format('<script>alert();</script>')
|
dangerous_string = valid_string.format('<script>alert();</script>')
|
||||||
|
|
||||||
# Test that valid string
|
# Test that valid string passes through unchanged
|
||||||
self.assertEqual(valid_string, sanitize_svg(valid_string))
|
self.assertEqual(valid_string, sanitize_svg(valid_string))
|
||||||
|
|
||||||
# Test that invalid string is cleaned
|
# Test that invalid string is cleaned
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
|
|||||||
# There should not be any templates left at this point
|
# There should not be any templates left at this point
|
||||||
self.assertEqual(PartCategoryParameterTemplate.objects.count(), 0)
|
self.assertEqual(PartCategoryParameterTemplate.objects.count(), 0)
|
||||||
|
|
||||||
def test_bleach(self):
|
def test_sanitizer(self):
|
||||||
"""Test that the data cleaning functionality is working.
|
"""Test that the data cleaning functionality is working.
|
||||||
|
|
||||||
This helps to protect against XSS injection
|
This helps to protect against XSS injection
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def image_data(img, fmt='PNG') -> str:
|
|||||||
def clean_barcode(data):
|
def clean_barcode(data):
|
||||||
"""Return a 'cleaned' string for encoding into a barcode / qrcode.
|
"""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
|
- Used to render raw barcode data into the rendered HTML templates
|
||||||
"""
|
"""
|
||||||
from InvenTree.helpers import strip_html_tags
|
from InvenTree.helpers import strip_html_tags
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ bcrypt==5.0.0 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# paramiko
|
# paramiko
|
||||||
bleach[css]==6.3.0 \
|
bleach==4.1.0 \
|
||||||
--hash=sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22 \
|
--hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \
|
||||||
--hash=sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6
|
--hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# django-markdownify
|
# django-markdownify
|
||||||
@@ -631,9 +631,9 @@ django-maintenance-mode==0.22.0 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# -r src/backend/requirements.in
|
# -r src/backend/requirements.in
|
||||||
django-markdownify==0.9.6 \
|
django-markdownify==0.9.1 \
|
||||||
--hash=sha256:9863b2bfa6d159ad1423dc93bf0d6eadc6413776de304049aa9fcfa5edd2ce1c \
|
--hash=sha256:06ff2994ff09ce030b50de8c6fc5b89b9c25a66796948aff55370716ca1233af \
|
||||||
--hash=sha256:edcf47b2026d55a8439049d35c8b54e11066a4856c4fad1060e139cb3d2eee52
|
--hash=sha256:24ba68b8a5996b6ec9632d11a3fd2e7159cb7e6becd3104e0a9372b5a2a148ef
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# -r src/backend/requirements.in
|
# -r src/backend/requirements.in
|
||||||
@@ -1277,6 +1277,37 @@ markupsafe==3.0.3 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# jinja2
|
# 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 \
|
oauthlib==3.3.1 \
|
||||||
--hash=sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9 \
|
--hash=sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9 \
|
||||||
--hash=sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1
|
--hash=sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1
|
||||||
@@ -1447,6 +1478,7 @@ packaging==26.0 \
|
|||||||
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
|
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
|
# bleach
|
||||||
# gunicorn
|
# gunicorn
|
||||||
# opentelemetry-instrumentation
|
# opentelemetry-instrumentation
|
||||||
paramiko==4.0.0 \
|
paramiko==4.0.0 \
|
||||||
@@ -2086,6 +2118,7 @@ six==1.17.0 \
|
|||||||
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
|
# bleach
|
||||||
# python-dateutil
|
# python-dateutil
|
||||||
sqlparse==0.5.5 \
|
sqlparse==0.5.5 \
|
||||||
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
||||||
@@ -2106,12 +2139,11 @@ tablib[xls, xlsx, yaml]==3.9.0 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# -r src/backend/requirements.in
|
# -r src/backend/requirements.in
|
||||||
tinycss2==1.4.0 \
|
tinycss2==1.5.1 \
|
||||||
--hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \
|
--hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661 \
|
||||||
--hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289
|
--hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# bleach
|
|
||||||
# cssselect2
|
# cssselect2
|
||||||
# weasyprint
|
# weasyprint
|
||||||
tinyhtml5==2.1.0 \
|
tinyhtml5==2.1.0 \
|
||||||
@@ -2165,9 +2197,9 @@ wcwidth==0.6.0 \
|
|||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# blessed
|
# blessed
|
||||||
# prettytable
|
# prettytable
|
||||||
weasyprint==66.0 \
|
weasyprint==68.1 \
|
||||||
--hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \
|
--hash=sha256:4dc3ba63c68bbbce3e9617cb2226251c372f5ee90a8a484503b1c099da9cf5be \
|
||||||
--hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40
|
--hash=sha256:d3b752049b453a5c95edb27ce78d69e9319af5a34f257fa0f4c738c701b4184e
|
||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# -r src/backend/requirements.in
|
# -r src/backend/requirements.in
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ drf-spectacular # DRF API documentation
|
|||||||
feedparser # RSS newsfeed parser
|
feedparser # RSS newsfeed parser
|
||||||
gunicorn # Gunicorn web server
|
gunicorn # Gunicorn web server
|
||||||
jinja2 # Jinja2 templating engine
|
jinja2 # Jinja2 templating engine
|
||||||
|
nh3 # HTML sanitization (replaces bleach)
|
||||||
pdf2image # PDF to image conversion
|
pdf2image # PDF to image conversion
|
||||||
pillow # Image manipulation
|
pillow # Image manipulation
|
||||||
pint # Unit conversion
|
pint # Unit conversion
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ bcrypt==5.0.0 \
|
|||||||
--hash=sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822 \
|
--hash=sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822 \
|
||||||
--hash=sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b
|
--hash=sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b
|
||||||
# via paramiko
|
# via paramiko
|
||||||
bleach[css]==6.3.0 \
|
bleach==4.1.0 \
|
||||||
--hash=sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22 \
|
--hash=sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da \
|
||||||
--hash=sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6
|
--hash=sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994
|
||||||
# via django-markdownify
|
# via django-markdownify
|
||||||
blessed==1.33.0 \
|
blessed==1.33.0 \
|
||||||
--hash=sha256:1bc8ecac6d139286ea51ec1683433528ce75b0c60db77b7d881112bf9fc85b0f \
|
--hash=sha256:1bc8ecac6d139286ea51ec1683433528ce75b0c60db77b7d881112bf9fc85b0f \
|
||||||
@@ -585,9 +585,9 @@ django-maintenance-mode==0.22.0 \
|
|||||||
--hash=sha256:502f04f845d6996e8add321186b3b9236c3702de7cb0ab14952890af6523b9e5 \
|
--hash=sha256:502f04f845d6996e8add321186b3b9236c3702de7cb0ab14952890af6523b9e5 \
|
||||||
--hash=sha256:a9cf2ba79c9945bd67f98755a6cfd281869d39b3745bbb5d1f571d058657aa85
|
--hash=sha256:a9cf2ba79c9945bd67f98755a6cfd281869d39b3745bbb5d1f571d058657aa85
|
||||||
# via -r src/backend/requirements.in
|
# via -r src/backend/requirements.in
|
||||||
django-markdownify==0.9.6 \
|
django-markdownify==0.9.1 \
|
||||||
--hash=sha256:9863b2bfa6d159ad1423dc93bf0d6eadc6413776de304049aa9fcfa5edd2ce1c \
|
--hash=sha256:06ff2994ff09ce030b50de8c6fc5b89b9c25a66796948aff55370716ca1233af \
|
||||||
--hash=sha256:edcf47b2026d55a8439049d35c8b54e11066a4856c4fad1060e139cb3d2eee52
|
--hash=sha256:24ba68b8a5996b6ec9632d11a3fd2e7159cb7e6becd3104e0a9372b5a2a148ef
|
||||||
# via -r src/backend/requirements.in
|
# via -r src/backend/requirements.in
|
||||||
django-money==3.6.0 \
|
django-money==3.6.0 \
|
||||||
--hash=sha256:94402f2831f2726b94ef2da35b4059441b4c0aedfc47b312472200d4ffdf8d73 \
|
--hash=sha256:94402f2831f2726b94ef2da35b4059441b4c0aedfc47b312472200d4ffdf8d73 \
|
||||||
@@ -1145,6 +1145,35 @@ markupsafe==3.0.3 \
|
|||||||
--hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
|
--hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
|
||||||
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
|
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
|
||||||
# via jinja2
|
# 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 \
|
oauthlib==3.3.1 \
|
||||||
--hash=sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9 \
|
--hash=sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9 \
|
||||||
--hash=sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1
|
--hash=sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1
|
||||||
@@ -1282,6 +1311,7 @@ packaging==26.0 \
|
|||||||
--hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
|
--hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \
|
||||||
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
|
--hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529
|
||||||
# via
|
# via
|
||||||
|
# bleach
|
||||||
# gunicorn
|
# gunicorn
|
||||||
# opentelemetry-instrumentation
|
# opentelemetry-instrumentation
|
||||||
paramiko==4.0.0 \
|
paramiko==4.0.0 \
|
||||||
@@ -1859,7 +1889,9 @@ sgmllib3k==1.0.0 \
|
|||||||
six==1.17.0 \
|
six==1.17.0 \
|
||||||
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
|
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
|
||||||
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
|
||||||
# via python-dateutil
|
# via
|
||||||
|
# bleach
|
||||||
|
# python-dateutil
|
||||||
sqlparse==0.5.5 \
|
sqlparse==0.5.5 \
|
||||||
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
||||||
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
||||||
@@ -1874,11 +1906,10 @@ tablib[xls, xlsx, yaml]==3.9.0 \
|
|||||||
--hash=sha256:1b6abd8edb0f35601e04c6161d79660fdcde4abb4a54f66cc9f9054bd55d5fe2 \
|
--hash=sha256:1b6abd8edb0f35601e04c6161d79660fdcde4abb4a54f66cc9f9054bd55d5fe2 \
|
||||||
--hash=sha256:eda17cd0d4dda614efc0e710227654c60ddbeb1ca92cdcfc5c3bd1fc5f5a6e4a
|
--hash=sha256:eda17cd0d4dda614efc0e710227654c60ddbeb1ca92cdcfc5c3bd1fc5f5a6e4a
|
||||||
# via -r src/backend/requirements.in
|
# via -r src/backend/requirements.in
|
||||||
tinycss2==1.4.0 \
|
tinycss2==1.5.1 \
|
||||||
--hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \
|
--hash=sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661 \
|
||||||
--hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289
|
--hash=sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957
|
||||||
# via
|
# via
|
||||||
# bleach
|
|
||||||
# cssselect2
|
# cssselect2
|
||||||
# weasyprint
|
# weasyprint
|
||||||
tinyhtml5==2.1.0 \
|
tinyhtml5==2.1.0 \
|
||||||
@@ -1926,9 +1957,9 @@ wcwidth==0.6.0 \
|
|||||||
# via
|
# via
|
||||||
# blessed
|
# blessed
|
||||||
# prettytable
|
# prettytable
|
||||||
weasyprint==66.0 \
|
weasyprint==68.1 \
|
||||||
--hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \
|
--hash=sha256:4dc3ba63c68bbbce3e9617cb2226251c372f5ee90a8a484503b1c099da9cf5be \
|
||||||
--hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40
|
--hash=sha256:d3b752049b453a5c95edb27ce78d69e9319af5a34f257fa0f4c738c701b4184e
|
||||||
# via -r src/backend/requirements.in
|
# via -r src/backend/requirements.in
|
||||||
webencodings==0.5.1 \
|
webencodings==0.5.1 \
|
||||||
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
|
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
|
||||||
|
|||||||
Reference in New Issue
Block a user