CVE-2019-15486
Description
Django JS Reverse before 0.9.1 is vulnerable to cross-site scripting (XSS) via the js_reverse_inline template tag due to insufficient output escaping.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Django JS Reverse before 0.9.1 is vulnerable to cross-site scripting (XSS) via the js_reverse_inline template tag due to insufficient output escaping.
Vulnerability
Overview
The js_reverse_inline template tag in django-js-reverse before version 0.9.1 fails to properly escape user-controlled data when generating inline JavaScript. This allows an attacker to inject arbitrary HTML or JavaScript into the page. The fix introduced a _safe_json function that escapes <, >, and & characters and removed the |safe filter from the template, ensuring that data is safely rendered [1][4].
Exploitation
An attacker can exploit this vulnerability by controlling input that is included in the JavaScript output, such as URL parameters or other data processed by the tag. The attack does not require authentication if the attacker can influence the data passed to the template. The vulnerability is triggered when a template uses the js_reverse_inline tag, which outputs inline JavaScript directly into the HTML [1][4].
Impact
Successful exploitation leads to cross-site scripting (XSS), enabling an attacker to execute arbitrary JavaScript in the context of the victim's browser. This can result in theft of cookies, session tokens, or other sensitive information, as well as unauthorized actions performed on behalf of the user [1].
Mitigation
The vulnerability is fixed in version 0.9.1. Users should upgrade to the latest version immediately. Additionally, the project maintainers note that inline JavaScript is not recommended because it makes deploying a secure Content Security Policy (CSP) difficult [2][4].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
django-js-reversePyPI | < 0.9.1 | 0.9.1 |
Affected products
2- Django/django-js-reversedescription
Patches
2a3b57d1e4424Merge pull request #81 from ierror/fix-xss-when-using-js-reverse-inline
5 files changed · +28 −2
django_js_reverse/core.py+12 −1 modified@@ -8,6 +8,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.template import loader +from django.utils.safestring import mark_safe from django.utils.encoding import force_text from . import rjsmin @@ -120,6 +121,16 @@ def generate_json(default_urlresolver, script_prefix=None): } +def _safe_json(obj): + return mark_safe( + json + .dumps(obj) + .replace('>', '\\u003E') + .replace('<', '\\u003C') + .replace('&', '\\u0026') + ) + + def generate_js(default_urlresolver): js_var_name = getattr(settings, 'JS_REVERSE_JS_VAR_NAME', JS_VAR_NAME) if not JS_IDENTIFIER_RE.match(js_var_name.upper()): @@ -147,7 +158,7 @@ def generate_js(default_urlresolver): data = generate_json(default_urlresolver, script_prefix) js_content = loader.render_to_string('django_js_reverse/urls_js.tpl', { - 'data': json.dumps(data), + 'data': _safe_json(data), 'js_name': '.'.join([js_global_object_name, js_var_name]), })
django_js_reverse/templates/django_js_reverse/urls_js.tpl+1 −1 modified@@ -1,6 +1,6 @@ {{ js_name }} = (function () { "use strict"; - var data = {{ data|safe }}; + var data = {{ data }}; function factory(d) { var url_patterns = d.urls; var url_prefix = d.prefix;
django_js_reverse/tests/test_urls.py+1 −0 modified@@ -20,6 +20,7 @@ # test urls url(r'^test_no_url_args/$', dummy_view, name='test_no_url_args'), + url(r'^test_script/$', dummy_view, name='</script><script>console.log(&)</script><!--'), url(r'^test_one_url_args/(?P<arg_one>[-\w]+)/$', dummy_view, name='test_one_url_args'), url(r'^test_two_url_args/(?P<arg_one>[-\w]+)-(?P<arg_two>[-\w]+)/$', dummy_view, name='test_two_url_args'), url(r'^test_optional_url_arg/(?:1_(?P<arg_one>[-\w]+)-)?2_(?P<arg_two>[-\w]+)/$', dummy_view,
django_js_reverse/tests/unit_tests.py+10 −0 modified@@ -265,6 +265,7 @@ def test_script_prefix(self): @override_settings( ROOT_URLCONF='django_js_reverse.tests.test_urls', + TEMPLATE_CONTEXT_PROCESSORS=['django.core.context_processors.request'], ) class JSReverseTemplateTagTest(AbstractJSReverseTestCase, TestCase): @@ -288,6 +289,15 @@ def test_tpl_tag_without_request_in_context(self): js_from_view = smart_str(self.client.post('/jsreverse/').content) self.assertEqual(js_from_tag, js_from_view) + def test_tpl_tag_escape_entities(self): + context_instance = Context() + tpl = Template('{% load js_reverse %}{% js_reverse_inline %}') + js = tpl.render(context_instance) + self.assertIn( + '\\u003C/script\\u003E\\u003Cscript\\u003Econsole.log(\\u0026amp;)' + '\\u003C/script\\u003E\\u003C!--', + js, + ) if __name__ == '__main__': unittest.main()
README.rst+4 −0 modified@@ -184,6 +184,10 @@ or, if you are using Django > 1.5 Usage as template tag _____________________ +You can place the js_reverse JavaScript inline into your templates, +however use of inline JavaScript is not recommended, because it +will make it impossible to deploy a secure Content Security Policy. +See `django-csp <https://django-csp.readthedocs.io/>`__ ::
78d6aff2276favoid xss when using js_reverse_inline
5 files changed · +28 −2
django_js_reverse/core.py+12 −1 modified@@ -8,6 +8,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.template import loader +from django.utils.safestring import mark_safe from django.utils.encoding import force_text from . import rjsmin @@ -120,6 +121,16 @@ def generate_json(default_urlresolver, script_prefix=None): } +def _safe_json(obj): + return mark_safe( + json + .dumps(obj) + .replace('>', '\\u003E') + .replace('<', '\\u003C') + .replace('&', '\\u0026') + ) + + def generate_js(default_urlresolver): js_var_name = getattr(settings, 'JS_REVERSE_JS_VAR_NAME', JS_VAR_NAME) if not JS_IDENTIFIER_RE.match(js_var_name.upper()): @@ -147,7 +158,7 @@ def generate_js(default_urlresolver): data = generate_json(default_urlresolver, script_prefix) js_content = loader.render_to_string('django_js_reverse/urls_js.tpl', { - 'data': json.dumps(data), + 'data': _safe_json(data), 'js_name': '.'.join([js_global_object_name, js_var_name]), })
django_js_reverse/templates/django_js_reverse/urls_js.tpl+1 −1 modified@@ -1,6 +1,6 @@ {{ js_name }} = (function () { "use strict"; - var data = {{ data|safe }}; + var data = {{ data }}; function factory(d) { var url_patterns = d.urls; var url_prefix = d.prefix;
django_js_reverse/tests/test_urls.py+1 −0 modified@@ -20,6 +20,7 @@ # test urls url(r'^test_no_url_args/$', dummy_view, name='test_no_url_args'), + url(r'^test_script/$', dummy_view, name='</script><script>console.log(&)</script><!--'), url(r'^test_one_url_args/(?P<arg_one>[-\w]+)/$', dummy_view, name='test_one_url_args'), url(r'^test_two_url_args/(?P<arg_one>[-\w]+)-(?P<arg_two>[-\w]+)/$', dummy_view, name='test_two_url_args'), url(r'^test_optional_url_arg/(?:1_(?P<arg_one>[-\w]+)-)?2_(?P<arg_two>[-\w]+)/$', dummy_view,
django_js_reverse/tests/unit_tests.py+10 −0 modified@@ -265,6 +265,7 @@ def test_script_prefix(self): @override_settings( ROOT_URLCONF='django_js_reverse.tests.test_urls', + TEMPLATE_CONTEXT_PROCESSORS=['django.core.context_processors.request'], ) class JSReverseTemplateTagTest(AbstractJSReverseTestCase, TestCase): @@ -288,6 +289,15 @@ def test_tpl_tag_without_request_in_context(self): js_from_view = smart_str(self.client.post('/jsreverse/').content) self.assertEqual(js_from_tag, js_from_view) + def test_tpl_tag_escape_entities(self): + context_instance = Context() + tpl = Template('{% load js_reverse %}{% js_reverse_inline %}') + js = tpl.render(context_instance) + self.assertIn( + '\\u003C/script\\u003E\\u003Cscript\\u003Econsole.log(\\u0026amp;)' + '\\u003C/script\\u003E\\u003C!--', + js, + ) if __name__ == '__main__': unittest.main()
README.rst+4 −0 modified@@ -184,6 +184,10 @@ or, if you are using Django > 1.5 Usage as template tag _____________________ +You can place the js_reverse JavaScript inline into your templates, +however use of inline JavaScript is not recommended, because it +will make it impossible to deploy a secure Content Security Policy. +See `django-csp <https://django-csp.readthedocs.io/>`__ ::
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-vx6v-2rg6-865hghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-15486ghsaADVISORY
- github.com/ierror/django-js-reverse/commit/78d6aff2276f2d341f643b095515f8aaba5e67c2ghsaWEB
- github.com/ierror/django-js-reverse/commit/a3b57d1e4424e2fadabcd526d170c4868d55159cghsaWEB
- github.com/ierror/django-js-reverse/compare/v0.9.0...v0.9.1ghsax_refsource_MISCWEB
- github.com/ierror/django-js-reverse/pull/81ghsax_refsource_MISCWEB
- github.com/pypa/advisory-database/tree/main/vulns/django-js-reverse/PYSEC-2019-19.yamlghsaWEB
News mentions
0No linked articles in our index yet.