Security

django-docutils renders reStructuredText to HTML and returns that HTML as safe template output. The default renderer is locked down for content that may come from users, editors, uploads, API requests, or other lower-trust sources.

These defaults implement the hardening the docutils project recommends for web applications in Deploying Docutils Securely. They reduce risk; they do not make untrusted markup safe to render. The riskiest input is dynamic content — reStructuredText (or any markup) that people type into your site themselves, such as comments, profile text, or CMS fields.

Default posture

Without extra configuration, django-docutils applies these Docutils settings on public rendering paths:

{
    "file_insertion_enabled": False,
    "raw_enabled": False,
    "_disable_config": True,
    "line_length_limit": 10_000,
}

This disables .. include::, .. raw::, .. csv-table:: :file:, URL-backed file insertion, and local docutils.conf overrides. The HTML publisher also removes raw nodes and blocks unsafe URI schemes such as javascript:, data:, file:, and vbscript: before output. URIs that cannot be parsed are treated as disallowed instead of failing the render. A .. meta:: directive that sets http-equiv=refresh is removed, since a meta refresh forces navigation regardless of URL scheme.

Markup generated by django-docutils itself renders normally. Pygments code blocks and highlighted inline code are marked trusted after package-controlled HTML generation; the kbd role renders escaped inline text instead of raw HTML. User-authored .. raw:: does not render under safe defaults.

How sanitization runs

Sanitization is the final step before HTML is written. Docutils applies every transform first — including any configured through DJANGO_DOCUTILS_LIB_RST['transforms'] — and django-docutils sanitizes after them. A transform that builds nodes from user content (for example, an autolinker) cannot emit a javascript: link or raw HTML that the locked-down default would otherwise strip from source.

Library-generated markup is exempt because it is marked trusted at the point of generation, so Pygments code blocks, highlighted inline code, and the kbd role survive while user-authored raw markup does not.

The sanitizer is available for custom pipelines: call sanitize_doctree() on a doctree directly, or add SanitizeTransform to your own docutils writer’s transform list so it runs last.

The default allowed URI schemes are:

DJANGO_DOCUTILS_LIB_RST = {
    "allowed_uri_schemes": ["http", "https", "mailto"],
}

Relative links and fragment links are still allowed.

Trusted RST opt-in

Only enable unsafe Docutils features for trusted static RST that ships with your application or documentation. Do not enable these settings for comments, CMS fields, profile text, uploaded files, or request data.

DJANGO_DOCUTILS_LIB_RST = {
    "allow_unsafe_docutils_settings": True,
    "docutils": {
        "raw_enabled": True,
        "file_insertion_enabled": True,
        "_disable_config": False,
    },
}

The opt-in flag is deliberate. Setting raw_enabled=True or file_insertion_enabled=True alone is ignored by the protected public helpers.

URI schemes

Add ordinary safe schemes directly:

DJANGO_DOCUTILS_LIB_RST = {
    "allowed_uri_schemes": ["http", "https", "mailto", "tel"],
}

Dangerous schemes such as javascript:, data:, file:, and vbscript: are ignored unless allow_unsafe_docutils_settings=True is also set. Treat that as trusted-content-only configuration.

Operational advice

Keep the normal Django security rules in place — see Security in Django for the framework-level picture:

  • Validate and limit user-submitted RST before rendering it.

  • Limit request and upload size at the web server or application boundary.

  • Use a Content Security Policy as defense in depth for rendered pages.

  • Avoid adding extra mark_safe() calls around user content.

  • Prefer separate trusted and untrusted rendering settings when your app has both static documentation and user-authored content.