# PYTHON-DJANGO-SEC-053: Django SafeString Subclass Audit

> **Severity:** MEDIUM | **CWE:** CWE-79 | **OWASP:** A03:2021

- **Language:** Python
- **Category:** Django
- **URL:** https://codepathfinder.dev/registry/python/django/PYTHON-DJANGO-SEC-053
- **Detection:** `pathfinder scan --ruleset python/PYTHON-DJANGO-SEC-053 --project .`

## Description

This audit rule flags all class definitions that extend SafeString or SafeData
from django.utils.safestring. Classes that inherit from SafeString are treated
as pre-validated safe HTML by Django's template engine, bypassing automatic
escaping when their instances are rendered in templates.

SafeString is the internal type returned by mark_safe() and format_html(). It is
designed for use within Django's own utilities, not as a base class for
application code. Subclassing SafeString is an unusual pattern that bypasses
auto-escaping at the class inheritance level, meaning all instances and operations
on instances inherit the "safe" designation.

This rule surfaces all such subclasses for security review to verify that the
class does not allow user-controlled data to flow through without escaping, and
that the subclassing is intentional and justified.


## Vulnerable Code

```python
from django.http import HttpResponse, HttpResponseBadRequest
from django.utils.safestring import mark_safe, SafeString
from django.utils.html import html_safe

# SEC-053: SafeString subclass (audit)
custom = SafeString("<b>bold</b>")
```

## Secure Code

```python
from django.utils.html import escape, format_html
from django.utils.safestring import SafeString

# SECURE: Use SafeString only for pre-validated static content
class SvgIcon(SafeString):
    ALLOWED_ICONS = {'home', 'user', 'settings', 'help'}

    def __new__(cls, icon_name):
        # Validate icon_name before constructing a SafeString instance
        if icon_name not in cls.ALLOWED_ICONS:
            raise ValueError(f"Unknown icon: {icon_name}")
        # Static template with no user data -- safe to store as SafeString
        svg = f'<svg class="icon-{icon_name}" aria-hidden="true"></svg>'
        instance = super().__new__(cls, svg)
        return instance

# BETTER ALTERNATIVE: Avoid subclassing SafeString entirely
# Use a plain class with format_html() for HTML generation
class UserLink:
    def __init__(self, username, url):
        self.username = username  # User-controlled
        self.url = url            # User-controlled

    def render(self):
        # format_html() escapes both username and url
        return format_html('<a href="{}">{}</a>', self.url, self.username)

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, flows, QueryType
from codepathfinder.presets import PropagationPresets

class DjangoSafeString(QueryType):
    fqns = ["django.utils.safestring", "django.utils.html"]

_DJANGO_SOURCES = [
    calls("request.GET.get"),
    calls("request.POST.get"),
    calls("request.GET"),
    calls("request.POST"),
    calls("request.COOKIES.get"),
    calls("request.FILES.get"),
    calls("*.GET.get"),
    calls("*.POST.get"),
]


@python_rule(
    id="PYTHON-DJANGO-SEC-053",
    name="Django SafeString Subclass (Audit)",
    severity="MEDIUM",
    category="django",
    cwe="CWE-79",
    tags="python,django,xss,safestring,audit,CWE-79",
    message="Class extends SafeString/SafeData. Content bypasses auto-escaping.",
    owasp="A03:2021",
)
def detect_django_safestring_subclass():
    """Audit: detects SafeString/SafeText subclass usage."""
    return DjangoSafeString.method("SafeString")
```

## How to Fix

- Avoid subclassing SafeString or SafeData in application code; use mark_safe() or format_html() at the specific point where content is validated to be safe instead.
- If a SafeString subclass is unavoidable, ensure the constructor validates all input against an allowlist or escapes all user-controlled values before calling super().__new__().
- Review all string operations on SafeString subclass instances to ensure user-controlled strings are never concatenated onto them without prior escaping.
- Consider replacing SafeString subclasses with plain classes that have a render() method using format_html(), which makes the escaping explicit and local.
- Add unit tests for SafeString subclasses that verify HTML-special characters in constructor arguments are properly escaped in the output.

## Security Implications

- **Inherited Safety Bypass for All Instances:** Unlike mark_safe() which marks a specific string value as safe, subclassing
SafeString marks the entire class as safe. Any string operation on a SafeString
subclass instance that incorporates user-controlled content will produce an
output that is treated as safe by the template engine, potentially rendering
XSS payloads without escaping.

- **String Operations Propagate Safety Flag:** SafeString overrides string concatenation and other operations such that
SafeString + regular_string returns a SafeString. This means user input
concatenated onto a SafeString subclass instance inherits the safety flag
and is rendered without escaping in templates.

- **Widespread Impact from Shared Types:** SafeString subclasses are often used as shared utility types across the
application. A vulnerability in the class definition affects every view,
template, and component that uses that type, potentially exposing the
entire application to XSS.

- **Difficult-to-Audit Safety Inheritance:** SafeString subclassing creates implicit auto-escaping bypass that is not
obvious at template rendering time. Unlike mark_safe() which appears inline
where the unsafe content is introduced, SafeString inheritance separates
the escape bypass declaration from the template rendering site by potentially
many files and call stacks.


## FAQ

**Q: Why would anyone subclass SafeString instead of using mark_safe()?**

SafeString subclassing is used to create custom string types that are always
treated as safe HTML without needing to call mark_safe() at each use site.
Examples include custom icon renderers, HTML widget types, and reusable UI
components. The pattern trades convenience for safety -- the safety bypass
is in the class definition, not at the rendering point, making it harder to
audit.


**Q: Is there any legitimate reason to subclass SafeString?**

Rarely. Django's own codebase uses SafeString internally as the return type
of mark_safe() and format_html(). For application code, there is almost always
a better pattern: a class with a render() method that returns format_html()
output, or a custom template tag that handles escaping. If you find a SafeString
subclass in your codebase, consider whether it can be refactored to a safer
design.


**Q: How do string operations on SafeString subclass instances affect safety?**

Django's SafeString overrides __add__, __radd__, and other string operations.
SafeString + str returns a SafeString (inherits safe flag). str + SafeString
returns a regular str (does not inherit safe flag). This asymmetry means
that appending user input to a SafeString instance produces output that is
still treated as safe and rendered without escaping in templates.


**Q: Can I detect SafeString subclass XSS with PYTHON-DJANGO-SEC-050?**

PYTHON-DJANGO-SEC-050 detects flows from request parameters to HttpResponse.
SafeString subclass XSS typically flows through the template rendering system
rather than HttpResponse directly, so it may require SEC-051 or this audit
rule (SEC-053) for detection. Both the audit rules and taint rules complement
each other for complete XSS coverage.


**Q: Do third-party Django packages use SafeString subclasses?**

Some do. Django's own form widgets use SafeString internally. Third-party
widget libraries and rich text editors sometimes use SafeString subclasses.
If a third-party package's SafeString subclass is flagged, review whether
user input can influence the string content before escaping.


## References

- [CWE-79: Cross-site Scripting](https://cwe.mitre.org/data/definitions/79.html)
- [OWASP XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
- [Django safestring module](https://docs.djangoproject.com/en/stable/ref/utils/#module-django.utils.safestring)
- [Django Template Auto-escaping](https://docs.djangoproject.com/en/stable/ref/templates/language/#automatic-html-escaping)
- [Django mark_safe() documentation](https://docs.djangoproject.com/en/stable/ref/utils/#django.utils.html.mark_safe)
- [OWASP XSS Attack](https://owasp.org/www-community/attacks/xss/)

---

Source: https://codepathfinder.dev/registry/python/django/PYTHON-DJANGO-SEC-053
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
