Repository: nautobot/nautobot

Issue:
Custom field descriptions shouldn't allow rendering of arbitrary HTML in model forms
### Environment
* Python version:
* Nautobot version: 1.2.0a1

### Steps to Reproduce
1. Define a custom field containing arbitrary HTML tags in its `description` and associate it with a content type
2. Create or edit an object of the associated content type.

<!-- What did you expect to happen? -->
### Expected Behavior

Arbitrary HTML should not be rendered.

<!-- What happened instead? -->
### Observed Behavior

HTML in the specified `description` is rendered as-is without escaping or sanitization.

### Comments

This is a relatively minor risk in 1.1.x and earlier versions, as there, custom field definitions can only be manipulated by users with administrative privileges. This should be fixed before releasing 1.2.0, since #735 moved the custom field definition functionality to be potentially available to any user with appropriate object permission privileges.


Discussion:

Resolving patch:
diff --git a/nautobot/extras/forms.py b/nautobot/extras/forms.py
--- a/nautobot/extras/forms.py
+++ b/nautobot/extras/forms.py
@@ -421,6 +421,14 @@ class ConfigContextSchemaFilterForm(BootstrapMixin, forms.Form):
 
 class CustomFieldForm(BootstrapMixin, forms.ModelForm):
     # TODO: Migrate custom field model from name to slug #464
+    # Once that's done we can set "name" as a proper (Auto)SlugField,
+    # but for the moment, that field only works with fields specifically named "slug"
+    description = forms.CharField(
+        required=False,
+        help_text="Also used as the help text when editing models using this custom field.<br>"
+        '<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank">'
+        "Markdown</a> syntax is supported.",
+    )
     content_types = MultipleContentTypeField(
         feature="custom_fields", help_text="The object(s) to which this field applies."
     )
diff --git a/nautobot/extras/models/customfields.py b/nautobot/extras/models/customfields.py
--- a/nautobot/extras/models/customfields.py
+++ b/nautobot/extras/models/customfields.py
@@ -28,6 +28,7 @@
     add_blank_choice,
 )
 from nautobot.utilities.querysets import RestrictedQuerySet
+from nautobot.utilities.templatetags.helpers import render_markdown
 from nautobot.utilities.utils import render_jinja2
 from nautobot.utilities.validators import validate_regex
 
@@ -211,7 +212,7 @@ class CustomField(BaseModel):
         help_text="The type of value(s) allowed for this field.",
     )
     # TODO: Migrate custom field model from name to slug #464
-    name = models.CharField(max_length=50, unique=True, verbose_name="Slug", help_text="Internal field name.")
+    name = models.CharField(max_length=50, unique=True, verbose_name="Slug", help_text="URL-friendly unique shorthand.")
     label = models.CharField(
         max_length=50,
         blank=True,
@@ -259,7 +260,7 @@ class CustomField(BaseModel):
         verbose_name="Validation regex",
         help_text=(
             "Regular expression to enforce on text field values. Use ^ and $ to force matching of entire string. For "
-            "example, <code>^[A-Z]{3}$</code> will limit values to exactly three uppercase letters.",
+            "example, <code>^[A-Z]{3}$</code> will limit values to exactly three uppercase letters."
         ),
     )
 
@@ -407,8 +408,10 @@ def to_form_field(self, set_initial=True, enforce_required=True, for_csv_import=
 
         field.model = self
         field.label = str(self)
+
         if self.description:
-            field.help_text = self.description
+            # Avoid script injection and similar attacks! Output HTML but only accept Markdown as input
+            field.help_text = render_markdown(self.description)
 
         return field
 
diff --git a/nautobot/extras/tables.py b/nautobot/extras/tables.py
--- a/nautobot/extras/tables.py
+++ b/nautobot/extras/tables.py
@@ -39,6 +39,8 @@
     TaggedItem,
     Webhook,
 )
+from nautobot.utilities.templatetags.helpers import render_markdown
+
 
 TAGGED_ITEM = """
 {% if value.get_absolute_url %}
@@ -221,6 +223,11 @@ class Meta(BaseTable.Meta):
             "weight",
         )
 
+    def render_description(self, record):
+        if record.description:
+            return mark_safe(render_markdown(record.description))
+        return self.default
+
 
 class CustomLinkTable(BaseTable):
     pk = ToggleColumn()


Test patch: