Repository: netbox-community/netbox

Issue:
Markdown syntax not working correctly within comment fields
### NetBox version

v3.2.0

### Python version

3.8

### Steps to Reproduce

1. Navigate to any object that has a 'comments' field.
2. Attempt to create a URL using angle brackets < >.
3. Click save.

### Expected Behavior

Netbox should turn the text within the angle brackets into a clickable URL.

### Observed Behavior

Text contained within the angle brackets completely disappears after clicking save.
![1](https://user-images.githubusercontent.com/77940332/162644205-24ec92db-d160-4c81-b3d6-36af76436c57.PNG)
![2](https://user-images.githubusercontent.com/77940332/162644206-5debb1bd-7bce-42df-a9bf-d10ea1d9ebd4.PNG)
![3](https://user-images.githubusercontent.com/77940332/162644207-1be90862-01f1-44cd-9621-af0a649b2122.PNG)



Discussion:
The error states above is due to the missing "mailto:" tag `[fake@example](mailto:fake@example.com)`
I do understand what you mean, I'm just pointing that the error is not due to NetBox failing to transform the <>.

Resolving patch:
diff --git a/netbox/utilities/templatetags/builtins/filters.py b/netbox/utilities/templatetags/builtins/filters.py
--- a/netbox/utilities/templatetags/builtins/filters.py
+++ b/netbox/utilities/templatetags/builtins/filters.py
@@ -11,7 +11,7 @@
 
 from netbox.config import get_config
 from utilities.markdown import StrikethroughExtension
-from utilities.utils import foreground_color
+from utilities.utils import clean_html, foreground_color
 
 register = template.Library()
 
@@ -144,18 +144,6 @@ def render_markdown(value):
 
         {{ md_source_text|markdown }}
     """
-    schemes = '|'.join(get_config().ALLOWED_URL_SCHEMES)
-
-    # Strip HTML tags
-    value = strip_tags(value)
-
-    # Sanitize Markdown links
-    pattern = fr'\[([^\]]+)\]\(\s*(?!({schemes})).*:(.+)\)'
-    value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
-
-    # Sanitize Markdown reference links
-    pattern = fr'\[([^\]]+)\]:\s*(?!({schemes}))\w*:(.+)'
-    value = re.sub(pattern, '[\\1]: \\3', value, flags=re.IGNORECASE)
 
     # Render Markdown
     html = markdown(value, extensions=['def_list', 'fenced_code', 'tables', StrikethroughExtension()])
@@ -164,6 +152,11 @@ def render_markdown(value):
     if html:
         html = f'<div class="rendered-markdown">{html}</div>'
 
+    schemes = get_config().ALLOWED_URL_SCHEMES
+
+    # Sanitize HTML
+    html = clean_html(html, schemes)
+
     return mark_safe(html)
 
 
diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py
--- a/netbox/utilities/utils.py
+++ b/netbox/utilities/utils.py
@@ -4,6 +4,7 @@
 from decimal import Decimal
 from itertools import count, groupby
 
+import bleach
 from django.core.serializers import serialize
 from django.db.models import Count, OuterRef, Subquery
 from django.db.models.functions import Coalesce
@@ -385,3 +386,33 @@ def copy_safe_request(request):
         'path': request.path,
         'id': getattr(request, 'id', None),  # UUID assigned by middleware
     })
+
+
+def clean_html(html, schemes):
+    """
+    Sanitizes HTML based on a whitelist of allowed tags and attributes.
+    Also takes a list of allowed URI schemes.
+    """
+
+    ALLOWED_TAGS = [
+        "div", "pre", "code", "blockquote", "del",
+        "hr", "h1", "h2", "h3", "h4", "h5", "h6",
+        "ul", "ol", "li", "p", "br",
+        "strong", "em", "a", "b", "i", "img",
+        "table", "thead", "tbody", "tr", "th", "td",
+        "dl", "dt", "dd",
+    ]
+
+    ALLOWED_ATTRIBUTES = {
+        "div": ['class'],
+        "h1": ["id"], "h2": ["id"], "h3": ["id"], "h4": ["id"], "h5": ["id"], "h6": ["id"],
+        "a": ["href", "title"],
+        "img": ["src", "title", "alt"],
+    }
+
+    return bleach.clean(
+        html,
+        tags=ALLOWED_TAGS,
+        attributes=ALLOWED_ATTRIBUTES,
+        protocols=schemes
+    )


Test patch: