[#8536] don't use jinja for site notifications; add autoescape
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index bc52a56..26dd2d9 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -790,10 +790,26 @@
 
 
 @pass_context
-def subrender_jinja_filter(context, value):
-    _template = context.eval_ctx.environment.from_string(value)
-    result = _template.render(**context)
-    return result
+def subrender_jinja_filter(context, html_tmpl: str) -> Markup:
+    # jinja templates can execute potentially dangerous things
+    #   _template = context.eval_ctx.environment.from_string(html_tmpl)
+    #   return _template.render(**context)
+
+    # so instead, support just a few things
+
+    limited_vars = {
+        '{{ c.project.url() }}': lambda: c.project.url(),
+    }
+    for var, fn in limited_vars.items():
+        if var not in html_tmpl:
+            continue
+        try:
+            val = fn()
+        except Exception:
+            log.exception(f'Could not replace {var} in jinja "subrender" for site notification')
+            continue
+        html_tmpl = html_tmpl.replace(var, val)
+    return Markup(html_tmpl)
 
 
 def nl2br_jinja_filter(value):
diff --git a/Allura/allura/lib/widgets/search.py b/Allura/allura/lib/widgets/search.py
index cec39ad..c772992 100644
--- a/Allura/allura/lib/widgets/search.py
+++ b/Allura/allura/lib/widgets/search.py
@@ -53,6 +53,7 @@
         super().__init__()
         # can't use g.jinja2_env since this widget gets imported too early :(
         jinja2_env = jinja2.Environment(
+            autoescape=True,
             loader=jinja2.PackageLoader('allura', 'templates/widgets'))
         self.content = Markup(jinja2_env.get_template('search_help.html').render(dict(
             comments=comments,
diff --git a/Allura/allura/public/nf/js/allura-base.js b/Allura/allura/public/nf/js/allura-base.js
index 779f4eb..839408d 100644
--- a/Allura/allura/public/nf/js/allura-base.js
+++ b/Allura/allura/public/nf/js/allura-base.js
@@ -209,7 +209,7 @@
     });
 
     $('#site-notification .btn-close').click(function(e) {
-        var $note = $(this).parent();
+        var $note = $(this).parents('section:first');
         $note.hide();
         var note_id = $note.attr('data-notification-id');
         var cookie = $.cookie('site-notification');
diff --git a/Allura/allura/templates/jinja_master/theme_macros.html b/Allura/allura/templates/jinja_master/theme_macros.html
index e06f5d7..c9ee789 100644
--- a/Allura/allura/templates/jinja_master/theme_macros.html
+++ b/Allura/allura/templates/jinja_master/theme_macros.html
@@ -178,7 +178,7 @@
     {% if note %}
         <div id="site-notification">
             <section class="site-message info" data-notification-id="{{note._id}}">
-                {{ note.content|subrender|safe }}
+                {{ note.content|subrender }}
                 <a href="" class="btn btn-close">Close</a>
             </section>
         </div>
diff --git a/Allura/allura/templates_responsive/jinja_master/theme_macros.html b/Allura/allura/templates_responsive/jinja_master/theme_macros.html
index 5c63911..dd0e70e 100644
--- a/Allura/allura/templates_responsive/jinja_master/theme_macros.html
+++ b/Allura/allura/templates_responsive/jinja_master/theme_macros.html
@@ -195,7 +195,7 @@
     {% if note %}
         <div id="site-notification">
             <section class="callout primary" data-notification-id="{{note._id}}">
-                {{note.content|safe}}
+                {{ note.content|subrender }}
                 <button class="close-button btn-close" aria-label="Dismiss alert" type="button">
                   {#  .btn-close instead of data-close, since allura-base.js handles closing it, not Foundation #}
                   <span aria-hidden="true">&times;</span>
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 9a12062..bb7908c 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -24,6 +24,8 @@
 import PIL
 from mock import Mock, patch
 from tg import tmpl_context as c
+from tg import config
+
 from alluratest.tools import module_not_available
 from webob import Request
 from webob.exc import HTTPUnauthorized
@@ -346,6 +348,23 @@
             Markup('foo&lt;script&gt;alert(1)&lt;/script&gt;<br>\nbar<br>\nbaz'))
 
 
+def test_subrender_jinja_filter():
+    # if we need a real ctx, have to do all this which probably isn't even quite right
+    # from allura.config.app_cfg import AlluraJinjaRenderer
+    # j2env = AlluraJinjaRenderer.create(config, None)['jinja'].jinja2_env
+    # from jinja2.runtime import new_context
+    # j_ctx = new_context(j2env, template_name=None, blocks={})
+    j_ctx = None
+
+    # HTML, but no jinja:
+    assert h.subrender_jinja_filter(j_ctx, '<b>hi</b>{% foo %}') == '<b>hi</b>{% foo %}'
+    # var does not error if no `c.project`
+    assert h.subrender_jinja_filter(j_ctx, '<a href="{{ c.project.url() }}"></a>') == '<a href="{{ c.project.url() }}"></a>'
+    # with `c.project` set, replacement of this var works:
+    with h.push_context('test', neighborhood='Projects'):
+        assert h.subrender_jinja_filter(j_ctx, '<a href="{{ c.project.url() }}"></a>') == '<a href="/p/test/"></a>'
+
+
 def test_split_select_field_options():
     assert (h.split_select_field_options('"test message" test2') ==
             ['test message', 'test2'])