diff --git a/netbox/extras/models/configs.py b/netbox/extras/models/configs.py index 235cf7160..f8e906663 100644 --- a/netbox/extras/models/configs.py +++ b/netbox/extras/models/configs.py @@ -316,6 +316,16 @@ class ConfigTemplate( self.template_code = self.data_file.data_as_string sync_data.alters_data = True + def get_environment_params(self): + """ + Config templates render plain text (network configs, scripts), not HTML. Force + autoescape off so environment_params cannot enable it and create a latent XSS sink + if output is ever rendered in an HTML context. + """ + params = super().get_environment_params() + params['autoescape'] = False + return params + def format_render_error(self, exc): """ Return a formatted error string for a rendering exception. When debug is enabled, the full diff --git a/netbox/extras/tests/test_models.py b/netbox/extras/tests/test_models.py index e55304cbd..ce7940a93 100644 --- a/netbox/extras/tests/test_models.py +++ b/netbox/extras/tests/test_models.py @@ -1310,6 +1310,24 @@ class RenderTemplateMixinRenderTestCase(TestCase): self.assertNotEqual(plain.render(ctx), trimmed.render(ctx)) self.assertEqual(trimmed.render(ctx).strip(), 'VALUE') + def test_configtemplate_autoescape_always_disabled(self): + """ + ConfigTemplate renders plain text (network configs, scripts); autoescape must stay off + even if environment_params explicitly requests it (#22652). + """ + t = ConfigTemplate(name='autoescape', template_code='{{ value }}', environment_params={'autoescape': True}) + self.assertEqual(t.render({'value': '