Cross-site scripting (XSS) vulnerability in the password reset endpoint
Description
Synapse is a Matrix reference homeserver written in python (pypi package matrix-synapse). Matrix is an ecosystem for open federated Instant Messaging and VoIP. In Synapse before version 1.27.0, the password reset endpoint served via Synapse was vulnerable to cross-site scripting (XSS) attacks. The impact depends on the configuration of the domain that Synapse is deployed on, but may allow access to cookies and other browser data, CSRF vulnerabilities, and access to other resources served on the same domain or parent domains. This is fixed in version 1.27.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
An XSS vulnerability in Synapse's password reset endpoint before 1.27.0 allows attackers to steal cookies, perform CSRF, and access same-domain resources.
Vulnerability
CVE-2021-21332 is a cross-site scripting (XSS) vulnerability in the password reset endpoint of Synapse, a Matrix homeserver. The root cause is that Jinja2 templates used for rendering password reset pages did not have autoescaping enabled, allowing injection of arbitrary HTML/JavaScript.
Exploitation
An attacker can exploit this by crafting a malicious password reset link containing XSS payloads. When a user clicks the link, the payload executes in the context of the Synapse domain. No authentication is required; the attacker only needs to trick a user into following the link.
Impact
Successful exploitation may allow the attacker to access cookies and other browser data, perform cross-site request forgery (CSRF), and access other resources served on the same domain or parent domains. The impact depends on the domain's configuration.
Mitigation
The vulnerability is fixed in Synapse version 1.27.0. The fix involved enabling Jinja2 autoescaping for HTML templates, including password_reset.html, as described in the upgrade notes [1][2]. Users should update to 1.27.0 or later.
AI Insight generated on May 21, 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 |
|---|---|---|
matrix-synapsePyPI | < 1.27.0 | 1.27.0 |
Affected products
2- matrix-org/synapsev5Range: < 1.27.0
Patches
1e54746bdf7d5Clean-up the template loading code. (#9200)
12 files changed · +96 −38
changelog.d/9200.misc+1 −0 added@@ -0,0 +1 @@ +Clean-up template loading code.
synapse/config/_base.py+26 −16 modified@@ -203,36 +203,50 @@ def read_file(cls, file_path, config_name): with open(file_path) as file_stream: return file_stream.read() + def read_template(self, filename: str) -> jinja2.Template: + """Load a template file from disk. + + This function will attempt to load the given template from the default Synapse + template directory. + + Files read are treated as Jinja templates. The templates is not rendered yet + and has autoescape enabled. + + Args: + filename: A template filename to read. + + Raises: + ConfigError: if the file's path is incorrect or otherwise cannot be read. + + Returns: + A jinja2 template. + """ + return self.read_templates([filename])[0] + def read_templates( - self, - filenames: List[str], - custom_template_directory: Optional[str] = None, - autoescape: bool = False, + self, filenames: List[str], custom_template_directory: Optional[str] = None, ) -> List[jinja2.Template]: """Load a list of template files from disk using the given variables. This function will attempt to load the given templates from the default Synapse template directory. If `custom_template_directory` is supplied, that directory is tried first. - Files read are treated as Jinja templates. These templates are not rendered yet. + Files read are treated as Jinja templates. The templates are not rendered yet + and have autoescape enabled. Args: filenames: A list of template filenames to read. custom_template_directory: A directory to try to look for the templates before using the default Synapse template directory instead. - autoescape: Whether to autoescape variables before inserting them into the - template. - Raises: ConfigError: if the file's path is incorrect or otherwise cannot be read. Returns: A list of jinja2 templates. """ - templates = [] search_directories = [self.default_template_dir] # The loader will first look in the custom template directory (if specified) for the @@ -249,7 +263,7 @@ def read_templates( search_directories.insert(0, custom_template_directory) loader = jinja2.FileSystemLoader(search_directories) - env = jinja2.Environment(loader=loader, autoescape=autoescape) + env = jinja2.Environment(loader=loader, autoescape=jinja2.select_autoescape(),) # Update the environment with our custom filters env.filters.update( @@ -259,12 +273,8 @@ def read_templates( } ) - for filename in filenames: - # Load the template - template = env.get_template(filename) - templates.append(template) - - return templates + # Load the templates + return [env.get_template(filename) for filename in filenames] def _format_ts_filter(value: int, format: str):
synapse/config/captcha.py+1 −3 modified@@ -28,9 +28,7 @@ def read_config(self, config, **kwargs): "recaptcha_siteverify_api", "https://www.recaptcha.net/recaptcha/api/siteverify", ) - self.recaptcha_template = self.read_templates( - ["recaptcha.html"], autoescape=True - )[0] + self.recaptcha_template = self.read_template("recaptcha.html") def generate_config_section(self, **kwargs): return """\
synapse/config/consent_config.py+1 −1 modified@@ -89,7 +89,7 @@ def __init__(self, *args): def read_config(self, config, **kwargs): consent_config = config.get("user_consent") - self.terms_template = self.read_templates(["terms.html"], autoescape=True)[0] + self.terms_template = self.read_template("terms.html") if consent_config is None: return
synapse/config/registration.py+1 −3 modified@@ -176,9 +176,7 @@ def read_config(self, config, **kwargs): self.session_lifetime = session_lifetime # The success template used during fallback auth. - self.fallback_success_template = self.read_templates( - ["auth_success.html"], autoescape=True - )[0] + self.fallback_success_template = self.read_template("auth_success.html") def generate_config_section(self, generate_secrets=False, **kwargs): if generate_secrets:
synapse/push/mailer.py+16 −2 modified@@ -668,6 +668,15 @@ def make_unsubscribe_link( def safe_markup(raw_html: str) -> jinja2.Markup: + """ + Sanitise a raw HTML string to a set of allowed tags and attributes, and linkify any bare URLs. + + Args + raw_html: Unsafe HTML. + + Returns: + A Markup object ready to safely use in a Jinja template. + """ return jinja2.Markup( bleach.linkify( bleach.clean( @@ -684,8 +693,13 @@ def safe_markup(raw_html: str) -> jinja2.Markup: def safe_text(raw_text: str) -> jinja2.Markup: """ - Process text: treat it as HTML but escape any tags (ie. just escape the - HTML) then linkify it. + Sanitise text (escape any HTML tags), and then linkify any bare URLs. + + Args + raw_text: Unsafe text which might include HTML markup. + + Returns: + A Markup object ready to safely use in a Jinja template. """ return jinja2.Markup( bleach.linkify(bleach.clean(raw_text, tags=[], attributes={}, strip=False))
synapse/res/templates/sso_auth_bad_user.html+1 −1 modified@@ -5,7 +5,7 @@ <body> <div> <p> - We were unable to validate your <tt>{{server_name | e}}</tt> account via + We were unable to validate your <tt>{{ server_name }}</tt> account via single-sign-on (SSO), because the SSO Identity Provider returned different details than when you logged in. </p>
synapse/res/templates/sso_auth_confirm.html+2 −2 modified@@ -5,8 +5,8 @@ <body> <div> <p> - A client is trying to {{ description | e }}. To confirm this action, - <a href="{{ redirect_url | e }}">re-authenticate with single sign-on</a>. + A client is trying to {{ description }}. To confirm this action, + <a href="{{ redirect_url }}">re-authenticate with single sign-on</a>. If you did not expect this, your account may be compromised! </p> </div>
synapse/res/templates/sso_error.html+1 −1 modified@@ -12,7 +12,7 @@ <p> There was an error during authentication: </p> - <div id="errormsg" style="margin:20px 80px">{{ error_description | e }}</div> + <div id="errormsg" style="margin:20px 80px">{{ error_description }}</div> <p> If you are seeing this page after clicking a link sent to you via email, make sure you only click the confirmation link once, and that you open the
synapse/res/templates/sso_login_idp_picker.html+6 −6 modified@@ -3,22 +3,22 @@ <head> <meta charset="UTF-8"> <link rel="stylesheet" href="/_matrix/static/client/login/style.css"> - <title>{{server_name | e}} Login</title> + <title>{{ server_name }} Login</title> </head> <body> <div id="container"> - <h1 id="title">{{server_name | e}} Login</h1> + <h1 id="title">{{ server_name }} Login</h1> <div class="login_flow"> <p>Choose one of the following identity providers:</p> <form> - <input type="hidden" name="redirectUrl" value="{{redirect_url | e}}"> + <input type="hidden" name="redirectUrl" value="{{ redirect_url }}"> <ul class="radiobuttons"> {% for p in providers %} <li> - <input type="radio" name="idp" id="prov{{loop.index}}" value="{{p.idp_id}}"> - <label for="prov{{loop.index}}">{{p.idp_name | e}}</label> + <input type="radio" name="idp" id="prov{{ loop.index }}" value="{{ p.idp_id }}"> + <label for="prov{{ loop.index }}">{{ p.idp_name }}</label> {% if p.idp_icon %} - <img src="{{p.idp_icon | mxc_to_http(32, 32)}}"/> + <img src="{{ p.idp_icon | mxc_to_http(32, 32) }}"/> {% endif %} </li> {% endfor %}
synapse/res/templates/sso_redirect_confirm.html+3 −3 modified@@ -5,10 +5,10 @@ <title>SSO redirect confirmation</title> </head> <body> - <p>The application at <span style="font-weight:bold">{{ display_url | e }}</span> is requesting full access to your <span style="font-weight:bold">{{ server_name }}</span> Matrix account.</p> + <p>The application at <span style="font-weight:bold">{{ display_url }}</span> is requesting full access to your <span style="font-weight:bold">{{ server_name }}</span> Matrix account.</p> <p>If you don't recognise this address, you should ignore this and close this tab.</p> <p> - <a href="{{ redirect_url | e }}">I trust this address</a> + <a href="{{ redirect_url }}">I trust this address</a> </p> </body> -</html> \ No newline at end of file +</html>
UPGRADE.rst+37 −0 modified@@ -85,6 +85,43 @@ for example: wget https://packages.matrix.org/debian/pool/main/m/matrix-synapse-py3/matrix-synapse-py3_1.3.0+stretch1_amd64.deb dpkg -i matrix-synapse-py3_1.3.0+stretch1_amd64.deb +Upgrading to v1.27.0 +==================== + +Changes to HTML templates +------------------------- + +The HTML templates for SSO and email notifications now have `Jinja2's autoescape <https://jinja.palletsprojects.com/en/2.11.x/api/#autoescaping>`_ +enabled for files ending in ``.html``, ``.htm``, and ``.xml``. If you hae customised +these templates and see issues when viewing them you might need to update them. +It is expected that most configurations will need no changes. + +If you have customised the templates *names* for these templates it is recommended +to verify they end in ``.html`` to ensure autoescape is enabled. + +The above applies to the following templates: + +* ``add_threepid.html`` +* ``add_threepid_failure.html`` +* ``add_threepid_success.html`` +* ``notice_expiry.html`` +* ``notice_expiry.html`` +* ``notif_mail.html`` (which, by default, includes ``room.html`` and ``notif.html``) +* ``password_reset.html`` +* ``password_reset_confirmation.html`` +* ``password_reset_failure.html`` +* ``password_reset_success.html`` +* ``registration.html`` +* ``registration_failure.html`` +* ``registration_success.html`` +* ``sso_account_deactivated.html`` +* ``sso_auth_bad_user.html`` +* ``sso_auth_confirm.html`` +* ``sso_auth_success.html`` +* ``sso_error.html`` +* ``sso_login_idp_picker.html`` +* ``sso_redirect_confirm.html`` + Upgrading to v1.26.0 ====================
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-246w-56m2-5899ghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/TNNAJOZNMVMXM6AS7RFFKB4QLUJ4IFEY/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2021-21332ghsaADVISORY
- github.com/matrix-org/synapse/commit/e54746bdf7d5c831eabe4dcea76a7626f1de73dfghsax_refsource_MISCWEB
- github.com/matrix-org/synapse/pull/9200ghsax_refsource_MISCWEB
- github.com/matrix-org/synapse/releases/tag/v1.27.0ghsax_refsource_MISCWEB
- github.com/matrix-org/synapse/security/advisories/GHSA-246w-56m2-5899ghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/matrix-synapse/PYSEC-2021-133.yamlghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/TNNAJOZNMVMXM6AS7RFFKB4QLUJ4IFEYghsaWEB
News mentions
0No linked articles in our index yet.