Skip to content

fix: prevent context pollution#213

Merged
fsbraun merged 4 commits intodjango-cms:masterfrom
dnlzrgz:fix-context-leak
Mar 12, 2026
Merged

fix: prevent context pollution#213
fsbraun merged 4 commits intodjango-cms:masterfrom
dnlzrgz:fix-context-leak

Conversation

@dnlzrgz
Copy link
Contributor

@dnlzrgz dnlzrgz commented Mar 11, 2026

Description

I replaced context.update() with context.push() in order to avoid the template context being polluted after rendering the snippet. I also added a simple test case to check that the bug has been fixed and avoid regressions.

Related resources

Checklist

  • I have opened this pull request against master
  • I have added or modified the tests when changing logic
  • I have followed the conventional commits guidelines to add meaningful information into the changelog
  • I have read the contribution guidelines and I have joined #pr-review on
    Discord to find a “pr review buddy” who is
    going to review my pull request.

Summary by Sourcery

Prevent snippet template rendering from leaking variables into the outer template context and add regression coverage.

Bug Fixes:

  • Isolate snippet rendering by pushing a temporary context layer for the snippet object to avoid polluting the caller's context.

Tests:

  • Add a regression test ensuring snippet rendering does not modify existing context variables or leak additional context stack frames.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 11, 2026

Reviewer's Guide

Reworks snippet rendering to use a context stack push instead of direct updates so that the calling template context is not mutated, and adds a regression test verifying that snippet rendering does not pollute the template context or leak stack frames.

Sequence diagram for snippet rendering with context.push to prevent pollution

sequenceDiagram
    participant TemplateEngine
    participant SnippetRenderer
    participant Context
    participant DjangoTemplate as DjangoTemplateSystem

    TemplateEngine->>SnippetRenderer: get_content_render(context, instance)
    SnippetRenderer->>Context: push({object: instance})
    activate Context

    alt instance has template
        SnippetRenderer->>Context: update({html: mark_safe(instance.html)})
        SnippetRenderer->>DjangoTemplate: render_to_string(instance.template, context.flatten())
        DjangoTemplate-->>SnippetRenderer: content
    else instance has no template
        SnippetRenderer->>DjangoTemplate: Template(instance.html)
        DjangoTemplate-->>SnippetRenderer: compiled_template
        SnippetRenderer->>compiled_template: render(context)
        compiled_template-->>SnippetRenderer: content
    end

    SnippetRenderer-->>TemplateEngine: return content
    Context-->>Context: automatic pop on with block exit
    deactivate Context

    TemplateEngine->>Context: read outer keys
    Context-->>TemplateEngine: unchanged outer context (no pollution)
Loading

File-Level Changes

Change Details Files
Isolate snippet rendering variables using Context.push() to avoid polluting the caller's template context.
  • Wrap snippet rendering logic in a context.push({"object": instance}) context manager so that the "object" binding is scoped to the snippet rendering only.
  • Keep existing rendering branches for template-based and raw-HTML snippets, including TemplateDoesNotExist and generic exception handling paths.
  • Ensure the function still returns the rendered content from within the new context manager block.
src/djangocms_snippet/templatetags/snippet_tags.py
Add regression test to ensure snippet rendering does not modify the outer context or leak context stack frames.
  • Create a snippet via SnippetWithVersionFactory, publish it, and render it through the snippet_fragment tag inside a pre-populated Context with an initial "object" value.
  • Capture the initial context stack depth and assert it is unchanged after rendering the snippet.
  • Assert that the original "object" value in the outer context remains unchanged after rendering.
tests/test_templatetags.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • Inside the with context.push({"object": instance}): block you still call context.update({"html": ...}); consider passing "html" into the same push call for consistency and to keep all temporary keys clearly scoped to the pushed context.
  • The new test relies on len(context.dicts) to detect leaks; adding a short comment there explaining why checking the stack length is important will make the intent clearer to future maintainers.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Inside the `with context.push({"object": instance}):` block you still call `context.update({"html": ...})`; consider passing `"html"` into the same `push` call for consistency and to keep all temporary keys clearly scoped to the pushed context.
- The new test relies on `len(context.dicts)` to detect leaks; adding a short comment there explaining why checking the stack length is important will make the intent clearer to future maintainers.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Use single context.push() and add comments explaining the context stack
length assertion.
context = Context({"object": og_object})
initial_stack_len = len(context.dicts)

template_to_render = Template('{% load snippet_tags %}{% snippet_fragment "pollution_test" %}')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
template_to_render = Template('{% load snippet_tags %}{% snippet_fragment "pollution_test" %}')
template_to_render = Template('{% load snippet_tags %}{% snippet_fragment "pollution_test" %} "{{ object }}"')

If you render out {{ object }} you'll directly see what the template engine puts there. Should be This shouldn't change.

This way you do not have to rely on the render method to not push context itself.

Copy link
Member

@fsbraun fsbraun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just have a tiny suggestion to improve test clarity

Copy link
Member

@fsbraun fsbraun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, @dnlzrgz ! I appreciate the tests and that you use a context manager!

@fsbraun fsbraun merged commit 2613fbb into django-cms:master Mar 12, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants