CVE-2024-34707: Insufficient Information (NVD-CWE-noinfo)
value: Nautobot is a Network Source of Truth and Network Automation Platform. A Nautobot user with admin privileges can modify the `BANNER_TOP`, `BANNER_BOTTOM`, and `BANNER_LOGIN` configuration settings via the `/admin/constance/config/` endpoint. Normally these settings are used to provide custom banner text at the top and bottom of all Nautobot web pages (or specifically on the login page in the case of `BANNER_LOGIN`) but it was reported that an admin user can make use of these settings to inject arbitrary HTML, potentially exposing Nautobot users to security issues such as cross-site scripting (stored XSS). The vulnerability is fixed in Nautobot 1.6.22 and 2.2.4.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-05-14T15:39Z
Repository: https://github.com/nautobot/nautobot
Language: HTML
Files changed: changes/1858.added, changes/1858.security, nautobot/core/settings.py, nautobot/core/settings.yaml, nautobot/core/templates/admin/base.html, nautobot/core/templates/base_django.html, nautobot/core/templates/inc/javascript.html, nautobot/core/templates/inc/media.html, nautobot/core/templates/login.html, nautobot/core/templates/nautobot_config.py.j2, nautobot/core/tests/test_views.py
Commit date: 2024-05-10 12:37:39 -0400
Commit message: Add 'javascript' and 'css' to BRANDING_FILEPATHS options. Sanitize BANNER_ content (#5697)

* Add 'javascript' and 'css' to BRANDING_FILEPATHS options. Address GHSA-r2hr-4v48-fjv3
Diff: diff --git a/changes/1858.added b/changes/1858.added
new file mode 100644
index 0000000000..1797926355
--- /dev/null
+++ b/changes/1858.added
@@ -0,0 +1,2 @@
+Added support in `BRANDING_FILEPATHS` configuration to specify a custom `css` and/or `javascript` file to be added to Nautobot page content.
+Added Markdown support to the `BANNER_TOP`, `BANNER_BOTTOM`, and `BANNER_LOGIN` configuration settings.
diff --git a/changes/1858.security b/changes/1858.security
new file mode 100644
index 0000000000..48042c7916
--- /dev/null
+++ b/changes/1858.security
@@ -0,0 +1 @@
+Added sanitization of HTML tags in the content of `BANNER_TOP`, `BANNER_BOTTOM`, and `BANNER_LOGIN` configuration to prevent against potential injection of malicious scripts (stored XSS) via these features ([GHSA-r2hr-4v48-fjv3](https://github.com/nautobot/nautobot/security/advisories/GHSA-r2hr-4v48-fjv3)).
diff --git a/nautobot/core/settings.py b/nautobot/core/settings.py
index bbef709db5..289954fd63 100644
--- a/nautobot/core/settings.py
+++ b/nautobot/core/settings.py
@@ -67,11 +67,11 @@ ALLOWED_URL_SCHEMES = [
     "tftp",
     "vnc",
     "xmpp",
 ]
 
-# Banners to display to users. HTML is allowed.
+# Banners to display to users. Markdown and limited HTML are allowed.
 if "NAUTOBOT_BANNER_BOTTOM" in os.environ and os.environ["NAUTOBOT_BANNER_BOTTOM"] != "":
     BANNER_BOTTOM = os.environ["NAUTOBOT_BANNER_BOTTOM"]
 if "NAUTOBOT_BANNER_LOGIN" in os.environ and os.environ["NAUTOBOT_BANNER_LOGIN"] != "":
     BANNER_LOGIN = os.environ["NAUTOBOT_BANNER_LOGIN"]
 if "NAUTOBOT_BANNER_TOP" in os.environ and os.environ["NAUTOBOT_BANNER_TOP"] != "":
@@ -684,19 +684,19 @@ CONSTANCE_CONFIG = {
         help_text="Allow users to enable request profiling on their login session.",
         field_type=bool,
     ),
     "BANNER_BOTTOM": ConstanceConfigItem(
         default="",
-        help_text="Custom HTML to display in a banner at the bottom of all pages.",
+        help_text="Custom Markdown or limited HTML to display in a banner at the bottom of all pages.",
     ),
     "BANNER_LOGIN": ConstanceConfigItem(
         default="",
-        help_text="Custom HTML to display in a banner at the top of the login page.",
+        help_text="Custom Markdown or limited HTML to display in a banner at the top of the login page.",
     ),
     "BANNER_TOP": ConstanceConfigItem(
         default="",
-        help_text="Custom HTML to display in a banner at the top of all pages.",
+        help_text="Custom Markdown or limited HTML to display in a banner at the top of all pages.",
     ),
     "CHANGELOG_RETENTION": ConstanceConfigItem(
         default=90,
         help_text="Number of days to retain object changelog history.\nSet this to 0 to retain changes indefinitely.",
         field_type=int,
@@ -970,10 +970,12 @@ BRANDING_FILEPATHS = {
     ),  # mono-chrome icon used for the mask-icon header
     "header_bullet": os.getenv(
         "NAUTOBOT_BRANDING_FILEPATHS_HEADER_BULLET", None
     ),  # bullet image used for various view headers
     "nav_bullet": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_NAV_BULLET", None),  # bullet image used for nav menu headers
+    "css": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_CSS", None),  # Custom global CSS
+    "javascript": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT", None),  # Custom global JavaScript
 }
 
 # Title to use in place of "Nautobot"
 BRANDING_TITLE = os.getenv("NAUTOBOT_BRANDING_TITLE", "Nautobot")
 
diff --git a/nautobot/core/settings.yaml b/nautobot/core/settings.yaml
index 36dd0a1292..2ba4ee583b 100644
--- a/nautobot/core/settings.yaml
+++ b/nautobot/core/settings.yaml
@@ -126,86 +126,121 @@ properties:
       "Remote User Authentication": "./authentication/remote.md"
       "Single Sign On (SSO)": "./authentication/sso.md"
     type: "array"
   BANNER_BOTTOM:
     default: ""
-    description: "Custom content to be displayed in a banner at the bottom of the page. HTML is allowed."
+    description: >-
+      Custom content to be displayed in a banner at the bottom of all Nautobot pages.
+    details: |-
+      +/- 2.2.4
+          Markdown formatting is supported within this message, as well as
+          [a limited subset of HTML](../../platform-functionality/template-filters.md#render_markdown).
     environment_variable: "NAUTOBOT_BANNER_BOTTOM"
     is_constance_config: true
     type: "string"
   BANNER_LOGIN:
     default: ""
-    description: "Custom content to be displayed on the login page above the login form. HTML is allowed."
+    description: >-
+      Custom content to be displayed in a banner on the login page above the login form.
+    details: |-
+      +/- 2.2.4
+          Markdown formatting is supported within this message, as well as
+          [a limited subset of HTML](../../platform-functionality/template-filters.md#render_markdown).
     environment_variable: "NAUTOBOT_BANNER_LOGIN"
     is_constance_config: true
     type: "string"
   BANNER_TOP:
     default: ""
-    description: "Custom content to be displayed in a banner at the top of the page. HTML is allowed."
+    description: >-
+      Custom content to be displayed in a banner at the top of all Nautobot pages.
+    details: |-
+      +/- 2.2.4
+          Markdown formatting is supported within this message, as well as
+          [a limited subset of HTML](../../platform-functionality/template-filters.md#render_markdown).
     environment_variable: "NAUTOBOT_BANNER_TOP"
     is_constance_config: true
     type: "string"
   BRANDING_FILEPATHS:
     default:
+      css: null
       favicon: null
       header_bullet: null
       icon_16: null
       icon_32: null
       icon_180: null
       icon_192: null
       icon_mask: null
+      javascript: null
       logo: null
       nav_bullet: null
     description: >-
-      A set of filepaths relative to the [`MEDIA_ROOT`](#media_root) which locate image assets used for
-      custom branding. Each of these assets takes the place of the corresponding stock Nautobot asset.
+      A set of filepaths relative to the [`MEDIA_ROOT`](#media_root) which locate assets used for
+      custom branding of your Nautobot instance.
+      With the exception of `css` and `javascript`, which provide the option to add an _additional_ file to Nautobot
+      page content, each of the other assets takes the place of the corresponding stock Nautobot asset.
       This allows for, for instance, providing your own navbar logo and favicon.
-      If a custom image asset is not provided for any of the above options, the stock Nautobot asset is used.
+      If a custom asset is not provided for any of the above options, the stock Nautobot asset is used.
+    details: |-
+      +++ 2.1.0
+          The `header_bullet` and `nav_bullet` assets were added as options.
+
+      +++ 2.2.4
+          The `css` and `javascript` assets were added as options.
     properties:
+      css:
+        "$ref": "#/definitions/relative_path"
+        default: null
+        description: "Custom global CSS file"
+        environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_CSS"
       favicon:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "Browser favicon"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_FAVICON"
       header_bullet:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "Bullet image used for various view headers"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_HEADER_BULLET"
       icon_16:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "16x16px icon"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_ICON_16"
       icon_180:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "180x180px icon - used for the apple-touch-icon header"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_ICON_180"
       icon_192:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "192x192px icon"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_ICON_192"
       icon_32:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "32x32px icon"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_ICON_32"
       icon_mask:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "Mono-chrome icon used for the mask-icon header"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_ICON_MASK"
+      javascript:
+        "$ref": "#/definitions/relative_path"
+        default: null
+        description: "Custom global JavaScript file"
+        environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT"
       logo:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "Navbar logo"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_LOGO"
       nav_bullet:
         "$ref": "#/definitions/relative_path"
-        default: ""
+        default: null
         description: "Bullet image used for nav menu headers"
         environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_NAV_BULLET"
     type: "object"
     version_added: "1.2.0"
   BRANDING_PREPENDED_FILENAME:
diff --git a/nautobot/core/templates/admin/base.html b/nautobot/core/templates/admin/base.html
index 94ffe2ec37..ab4fa07b29 100644
--- a/nautobot/core/templates/admin/base.html
+++ b/nautobot/core/templates/admin/base.html
@@ -54,11 +54,11 @@
 
 <div class="container-fluid wrapper" id="main-content" {% if is_popup %}style="padding-bottom: 0px;"{% endif %}>
     {% if not is_popup %}
         {% if "BANNER_TOP"|settings_or_config %}
             <div class="alert alert-info text-center" role="alert">
-                {{ "BANNER_TOP"|settings_or_config|safe }}
+                {{ "BANNER_TOP"|settings_or_config|render_markdown }}
             </div>
         {% endif %}
         {% if settings.MAINTENANCE_MODE %}
             <div class="alert alert-warning text-center" role="alert">
                 <h4><i class="mdi mdi-alert"></i> Maintenance Mode</h4>
@@ -105,11 +105,11 @@
         </div>
     </div>
     <div class="push"></div>
     {% if "BANNER_BOTTOM"|settings_or_config %}
         <div class="alert alert-info text-center banner-bottom" role="alert">
-                {{ "BANNER_BOTTOM"|settings_or_config|safe }}
+                {{ "BANNER_BOTTOM"|settings_or_config|render_markdown }}
         </div>
     {% endif %}
 </div>
 {% if not is_popup %}
     {% include 'inc/footer.html' %}
diff --git a/nautobot/core/templates/base_django.html b/nautobot/core/templates/base_django.html
index 969d71d16f..9b688913ca 100644
--- a/nautobot/core/templates/base_django.html
+++ b/nautobot/core/templates/base_django.html
@@ -14,11 +14,11 @@
     <div class="container-fluid wrapper" id="main-content">
         <div class="banner-alert-area">
         {% if request.user.is_authenticated %}
             {% if "BANNER_TOP"|settings_or_config %}
                 <div class="alert alert-info text-center" role="alert">
-                    {{ "BANNER_TOP"|settings_or_config|safe }}
+                    {{ "BANNER_TOP"|settings_or_config|render_markdown }}
                 </div>
             {% endif %}
         {% endif %}
         {% if settings.MAINTENANCE_MODE %}
             <div class="alert alert-warning text-center" role="alert">
@@ -40,11 +40,11 @@
         {% block content %}{% endblock content %}
         <div class="push"></div>
         {% if request.user.is_authenticated %}
             {% if "BANNER_BOTTOM"|settings_or_config %}
                 <div class="alert alert-info text-center banner-bottom" role="alert">
-                    {{ "BANNER_BOTTOM"|settings_or_config|safe }}
+                    {{ "BANNER_BOTTOM"|settings_or_config|render_markdown }}
                 </div>
             {% endif %}
         {% endif %}
     </div>
     {% include 'modals/modal_theme.html' with name='theme'%}
diff --git a/nautobot/core/templates/inc/javascript.html b/nautobot/core/templates/inc/javascript.html
index 7ee44f719e..b2f96d21b4 100644
--- a/nautobot/core/templates/inc/javascript.html
+++ b/nautobot/core/templates/inc/javascript.html
@@ -38,5 +38,8 @@
 </script>
 <script type="text/javascript">
     hljs.configure({ cssSelector: '.language-graphql, .language-json, .language-xml, .language-yaml' });
     hljs.highlightAll();
 </script>
+{% if settings.BRANDING_FILEPATHS.javascript %}
+<script src="{% custom_branding_or_static 'javascript' None %}"></script>
+{% endif %}
diff --git a/nautobot/core/templates/inc/media.html b/nautobot/core/templates/inc/media.html
index 5b0f0fefb0..7d7957a04c 100644
--- a/nautobot/core/templates/inc/media.html
+++ b/nautobot/core/templates/inc/media.html
@@ -32,10 +32,13 @@
           href="{% versioned_static 'css/dark.css' %}"
           onerror="window.location='{% url 'media_failure' %}?filename=css/dark.css'"{% if request.COOKIES|get_item:"theme" != 'dark' %} disabled="disabled"{% endif %} />
     <link rel="stylesheet" id="base-theme"
           href="{% versioned_static 'css/base.css' %}"
           onerror="window.location='{% url 'media_failure' %}?filename=css/base.css'">
+    {% if settings.BRANDING_FILEPATHS.css %}
+    <link rel="stylesheet" id="custom-css" href="{% custom_branding_or_static 'css' None %}">
+    {% endif %}
     <link rel="apple-touch-icon" sizes="180x180" href="{% custom_branding_or_static 'icon_180' 'img/nautobot_icon_180x180.png' %}">
     <link rel="icon" type="image/png" sizes="32x32" href="{% custom_branding_or_static 'icon_32' 'img/nautobot_icon_32x32.png' %}">
     <link rel="icon" type="image/png" sizes="16x16" href="{% custom_branding_or_static 'icon_16' 'img/nautobot_icon_16x16.png' %}">
     <link rel="icon" type="image/png" sizes="192x192" href="{% custom_branding_or_static 'icon_192' 'img/nautobot_icon_192x192.png' %}">
     <link rel="mask-icon" type="image/png" color="#0097ff" href="{% custom_branding_or_static 'icon_mask' 'img/nautobot_icon_monochrome.png' %}">
diff --git a/nautobot/core/templates/login.html b/nautobot/core/templates/login.html
index 03b4dbb0ca..5be5401463 100644
--- a/nautobot/core/templates/login.html
+++ b/nautobot/core/templates/login.html
@@ -51,12 +51,12 @@
 
 {% block content %}
 <div class="row" style="margin-top: {% if 'BANNER_LOGIN'|settings_or_config %}100{% else %}150{% endif %}px;">
     <div class="col-sm-4 col-sm-offset-4">
         {% if "BANNER_LOGIN"|settings_or_config %}
-            <div style="margin-bottom: 25px">
-                {{ "BANNER_LOGIN"|settings_or_config|safe }}
+            <div class="alert alert-info text-center" role="alert">
+                {{ "BANNER_LOGIN"|settings_or_config|render_markdown }}
             </div>
         {% endif %}
         {% if form.non_field_errors %}
             <div class="panel panel-danger">
                 <div class="panel-heading"><strong>Errors</strong></div>
diff --git a/nautobot/core/templates/nautobot_config.py.j2 b/nautobot/core/templates/nautobot_config.py.j2
index b3e07c5a14..62ea08b28d 100644
--- a/nautobot/core/templates/nautobot_config.py.j2
+++ b/nautobot/core/templates/nautobot_config.py.j2
@@ -246,10 +246,12 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
 #     ),  # mono-chrome icon used for the mask-icon header
 #     "header_bullet": os.getenv(
 #         "NAUTOBOT_BRANDING_FILEPATHS_HEADER_BULLET", None
 #     ),  # bullet image used for various view headers
 #     "nav_bullet": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_NAV_BULLET", None),  # bullet image used for nav menu headers
+#     "css": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_CSS", None),  # Custom global CSS
+#     "javascript": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT", None),  # Custom global JavaScript
 # }
 
 # Prepended to CSV, YAML and export template filenames (i.e. `nautobot_device.yml`)
 #
 # BRANDING_PREPENDED_FILENAME = os.getenv("NAUTOBOT_BRANDING_PREPENDED_FILENAME", "nautobot_")
diff --git a/nautobot/core/tests/test_views.py b/nautobot/core/tests/test_views.py
index 689c0b5a99..7f36f50a16 100644
--- a/nautobot/core/tests/test_views.py
+++ b/nautobot/core/tests/test_views.py
@@ -144,10 +144,43 @@ class HomeViewTestCase(TestCase):
         self.client.logout()
         response = self.client.get(url)
         response_content = response.content.decode(response.charset).replace("\n", "")
         self.assertNotRegex(response_content, footer_hostname_version_pattern)
 
+    def test_banners_markdown(self):
+        url = reverse("home")
+        with override_settings(
+            BANNER_TOP="# Hello world",
+            BANNER_BOTTOM="[info](https://nautobot.com)",
+        ):
+            response = self.client.get(url)
+        self.assertInHTML("<h1>Hello world</h1>", response.content.decode(response.charset))
+        self.assertInHTML(
+            '<a href="https://nautobot.com" rel="noopener noreferrer">info</a>',
+            response.content.decode(response.charset),
+        )
+
+        with override_settings(BANNER_LOGIN="_Welcome to Nautobot!_"):
+            self.client.logout()
+            response = self.client.get(reverse("login"))
+        self.assertInHTML("<em>Welcome to Nautobot!</em>", response.content.decode(response.charset))
+
+    def test_banners_no_xss(self):
+        url = reverse("home")
+        with override_settings(
+            BANNER_TOP='<script>alert("Hello from above!");</script>',
+            BANNER_BOTTOM='<script>alert("Hello from below!");</script>',
+        ):
+            response = self.client.get(url)
+        self.assertNotIn("Hello from above", response.content.decode(response.charset))
+        self.assertNotIn("Hello from below", response.content.decode(response.charset))
+
+        with override_settings(BANNER_LOGIN='<script>alert("Welcome to Nautobot!");</script>'):
+            self.client.logout()
+            response = self.client.get(reverse("login"))
+        self.assertNotIn("Welcome to Nautobot!", response.content.decode(response.charset))
+
 
 @override_settings(BRANDING_TITLE="Nautobot")
 class SearchFieldsTestCase(TestCase):
     def test_search_bar_redirect_to_login(self):
         self.client.logout()

Vulnerable code: # Banners to display to users. HTML is allowed.
        help_text="Custom HTML to display in a banner at the bottom of all pages.",
        help_text="Custom HTML to display in a banner at the top of the login page.",
        help_text="Custom HTML to display in a banner at the top of all pages.",
    description: "Custom content to be displayed in a banner at the bottom of the page. HTML is allowed."
    description: "Custom content to be displayed on the login page above the login form. HTML is allowed."
    description: "Custom content to be displayed in a banner at the top of the page. HTML is allowed."
      A set of filepaths relative to the [`MEDIA_ROOT`](#media_root) which locate image assets used for
      custom branding. Each of these assets takes the place of the corresponding stock Nautobot asset.
      If a custom image asset is not provided for any of the above options, the stock Nautobot asset is used.
        default: ""
        default: ""
        default: ""
        default: ""
        default: ""
        default: ""
        default: ""
        default: ""
        default: ""
                {{ "BANNER_TOP"|settings_or_config|safe }}
                {{ "BANNER_BOTTOM"|settings_or_config|safe }}
                    {{ "BANNER_TOP"|settings_or_config|safe }}
                    {{ "BANNER_BOTTOM"|settings_or_config|safe }}
            <div style="margin-bottom: 25px">
                {{ "BANNER_LOGIN"|settings_or_config|safe }}
Fixed code: Added support in `BRANDING_FILEPATHS` configuration to specify a custom `css` and/or `javascript` file to be added to Nautobot page content.
Added Markdown support to the `BANNER_TOP`, `BANNER_BOTTOM`, and `BANNER_LOGIN` configuration settings.
Added sanitization of HTML tags in the content of `BANNER_TOP`, `BANNER_BOTTOM`, and `BANNER_LOGIN` configuration to prevent against potential injection of malicious scripts (stored XSS) via these features ([GHSA-r2hr-4v48-fjv3](https://github.com/nautobot/nautobot/security/advisories/GHSA-r2hr-4v48-fjv3)).
# Banners to display to users. Markdown and limited HTML are allowed.
        help_text="Custom Markdown or limited HTML to display in a banner at the bottom of all pages.",
        help_text="Custom Markdown or limited HTML to display in a banner at the top of the login page.",
        help_text="Custom Markdown or limited HTML to display in a banner at the top of all pages.",
    "css": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_CSS", None),  # Custom global CSS
    "javascript": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT", None),  # Custom global JavaScript
    description: >-
      Custom content to be displayed in a banner at the bottom of all Nautobot pages.
    details: |-
      +/- 2.2.4
          Markdown formatting is supported within this message, as well as
          [a limited subset of HTML](../../platform-functionality/template-filters.md#render_markdown).
    description: >-
      Custom content to be displayed in a banner on the login page above the login form.
    details: |-
      +/- 2.2.4
          Markdown formatting is supported within this message, as well as
          [a limited subset of HTML](../../platform-functionality/template-filters.md#render_markdown).
    description: >-
      Custom content to be displayed in a banner at the top of all Nautobot pages.
    details: |-
      +/- 2.2.4
          Markdown formatting is supported within this message, as well as
          [a limited subset of HTML](../../platform-functionality/template-filters.md#render_markdown).
      css: null
      javascript: null
      A set of filepaths relative to the [`MEDIA_ROOT`](#media_root) which locate assets used for
      custom branding of your Nautobot instance.
      With the exception of `css` and `javascript`, which provide the option to add an _additional_ file to Nautobot
      page content, each of the other assets takes the place of the corresponding stock Nautobot asset.
      If a custom asset is not provided for any of the above options, the stock Nautobot asset is used.
    details: |-
      +++ 2.1.0
          The `header_bullet` and `nav_bullet` assets were added as options.

      +++ 2.2.4
          The `css` and `javascript` assets were added as options.
      css:
        "$ref": "#/definitions/relative_path"
        default: null
        description: "Custom global CSS file"
        environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_CSS"
        default: null
        default: null
        default: null
        default: null
        default: null
        default: null
        default: null
      javascript:
        "$ref": "#/definitions/relative_path"
        default: null
        description: "Custom global JavaScript file"
        environment_variable: "NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT"
        default: null
        default: null
                {{ "BANNER_TOP"|settings_or_config|render_markdown }}
                {{ "BANNER_BOTTOM"|settings_or_config|render_markdown }}
                    {{ "BANNER_TOP"|settings_or_config|render_markdown }}
                    {{ "BANNER_BOTTOM"|settings_or_config|render_markdown }}
{% if settings.BRANDING_FILEPATHS.javascript %}
<script src="{% custom_branding_or_static 'javascript' None %}"></script>
{% endif %}
    {% if settings.BRANDING_FILEPATHS.css %}
    <link rel="stylesheet" id="custom-css" href="{% custom_branding_or_static 'css' None %}">
    {% endif %}
            <div class="alert alert-info text-center" role="alert">
                {{ "BANNER_LOGIN"|settings_or_config|render_markdown }}
#     "css": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_CSS", None),  # Custom global CSS
#     "javascript": os.getenv("NAUTOBOT_BRANDING_FILEPATHS_JAVASCRIPT", None),  # Custom global JavaScript
    def test_banners_markdown(self):
        url = reverse("home")
        with override_settings(
            BANNER_TOP="# Hello world",
            BANNER_BOTTOM="[info](https://nautobot.com)",
        ):
            response = self.client.get(url)
        self.assertInHTML("<h1>Hello world</h1>", response.content.decode(response.charset))
        self.assertInHTML(
            '<a href="https://nautobot.com" rel="noopener noreferrer">info</a>',
            response.content.decode(response.charset),
        )

        with override_settings(BANNER_LOGIN="_Welcome to Nautobot!_"):
            self.client.logout()
            response = self.client.get(reverse("login"))
        self.assertInHTML("<em>Welcome to Nautobot!</em>", response.content.decode(response.charset))

    def test_banners_no_xss(self):
        url = reverse("home")
        with override_settings(
            BANNER_TOP='<script>alert("Hello from above!");</script>',
            BANNER_BOTTOM='<script>alert("Hello from below!");</script>',
        ):
            response = self.client.get(url)
        self.assertNotIn("Hello from above", response.content.decode(response.charset))
        self.assertNotIn("Hello from below", response.content.decode(response.charset))

        with override_settings(BANNER_LOGIN='<script>alert("Welcome to Nautobot!");</script>'):
            self.client.logout()
            response = self.client.get(reverse("login"))
        self.assertNotIn("Welcome to Nautobot!", response.content.decode(response.charset))