Skip to content

Admin: sandbox jinja2 templates in emails#4106

Merged
mathjazz merged 9 commits into
mozilla:mainfrom
functionzz:sandbox_jinja2_emails
May 20, 2026
Merged

Admin: sandbox jinja2 templates in emails#4106
mathjazz merged 9 commits into
mozilla:mainfrom
functionzz:sandbox_jinja2_emails

Conversation

@functionzz
Copy link
Copy Markdown
Collaborator

@functionzz functionzz commented Apr 23, 2026

This PR removes the ability for users with admin access to conduct RCE via jinja2 templates within Pontoon onboarding and user inactivity emails. It uses SandboxedEnvironment to prevent access to dangerous commands.

Fixes #3985.

Note: access to settings has been restricted to INACTIVE_CONTRIBUTOR_PERIOD, INACTIVE_TRANSLATOR_PERIOD and INACTIVE_MANAGER_PERIOD. Therefore, we must subsequently edit the onboarding and inactivity emails in the PROD and DEV dbs to reflect that, or else we risk crashing Pontoon.

Note 2: The above note expanded: all template tags in PROD and DEV must be of the format {placeholder}. full_url is no longer used within email templates, and jinja templating brackets ({{}}) are replaced with single brackets ({}).

@functionzz functionzz requested a review from mathjazz April 23, 2026 13:08
Copy link
Copy Markdown
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

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

I'm afraid this is not the right fix for #3985, which sadly has a misleading title.

The sandbox reduces risk but doesn't eliminate it, and it does so at the cost of ongoing complexity and trust in a security boundary that has already failed before. Eliminating the execution model entirely is a fundamentally stronger position.

Considering we still want editors to write template-like content in the DB, we should use a logic-less engine that has no concept of code execution.

We should probably be good with using str.format_map():

body = template_str.format_map({
    "INACTIVE_CONTRIBUTOR_PERIOD": settings.INACTIVE_CONTRIBUTOR_PERIOD,
    "homepage_url": request.build_absolute_uri(reverse("pontoon.homepage")),,
})

Alternatively, we could look into something more advanced like Chevron.

@functionzz
Copy link
Copy Markdown
Collaborator Author

I see what you mean - we want to eliminate SandboxedEnvironment entirely?

@mathjazz
Copy link
Copy Markdown
Collaborator

mathjazz commented May 5, 2026

Yeah.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 6, 2026

Codecov Report

❌ Patch coverage is 56.63717% with 49 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.84%. Comparing base (0766832) to head (629ca29).

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@functionzz functionzz force-pushed the sandbox_jinja2_emails branch from cbfb112 to 8c189d3 Compare May 6, 2026 14:58
@functionzz
Copy link
Copy Markdown
Collaborator Author

Just spent the last couple of hours debugging this - apparently the removal of jinja2 SandboxedEnvironment initialization in messaging somehow affects the way request.context was used for a specific test I wrote for #2133, which in turn was a flawed test because using request.context in such a way does not sit well with Jinja2, but was frailly working until the messaging related Jinja2 environment was removed.

I'm including the test change in this PR as its small and makes more sense to explain it here rather than merge seperately.

@functionzz functionzz requested a review from mathjazz May 6, 2026 18:19
Copy link
Copy Markdown
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

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

This moves in the right direction by removing Jinja execution for DB email content. 👏

Note that format_map() still allows attribute access and currently receives a Django model instance. Please replace it as suggested.

We should also update the template files accordingly.

First in the repo:
https://github.com/functionzz/pontoon/tree/eb266b7b8a870461e8e7baecdafba7932ce6720a/pontoon/messaging/templates/messaging/emails/content

Then in the DB.

Comment thread pontoon/messaging/emails.py Outdated
response = client_superuser.get(url)
assert response.status_code == 200
assert "translate_locale" not in response.context
assert "Translate" not in response.content.decode()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are these changes related to the PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, but in an indirect way. This test was failing due to reasons I posted about above, I'm including it in this PR so the test passes and there is a paper trail.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I just tested locally without this change and the tests are passing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The remote tests will not pass if the change is not applied. At least that's what I tested recently.

Just quoting myself because I didn't put this comment in the right place - If I push the old code it fails the pytest in CI.

Comment thread pontoon/messaging/emails.py
@functionzz functionzz requested a review from mathjazz May 7, 2026 19:24
Comment thread pontoon/messaging/emails.py Outdated
response = client_superuser.get(url)
assert response.status_code == 200
assert "translate_locale" not in response.context
assert "Translate" not in response.content.decode()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I just tested locally without this change and the tests are passing.

@functionzz
Copy link
Copy Markdown
Collaborator Author

The remote tests will not pass if the change is not applied. At least that's what I tested recently.

@functionzz functionzz requested a review from mathjazz May 8, 2026 21:59
@mathjazz
Copy link
Copy Markdown
Collaborator

The remote tests will not pass if the change is not applied. At least that's what I tested recently.

Could you please push? I'd like to look into it.

Comment thread pontoon/messaging/templates/messaging/emails/content/onboarding_1.html Outdated
Comment thread pontoon/messaging/tests/test_emails.py
@functionzz functionzz requested a review from mathjazz May 11, 2026 16:11
@mathjazz
Copy link
Copy Markdown
Collaborator

Could you please rebase against upstream/main?

@functionzz functionzz force-pushed the sandbox_jinja2_emails branch 4 times, most recently from 218740a to 41d9935 Compare May 11, 2026 23:39
@flodolo
Copy link
Copy Markdown
Collaborator

flodolo commented May 20, 2026

Like @mathjazz, this PR passes for me locally without any changes 🤔

@flodolo
Copy link
Copy Markdown
Collaborator

flodolo commented May 20, 2026

Correction after more ☕ : it fails locally as well, I was only running the emails test, not admin 😳

Looks like the removal of jinja_env = Jinja2.get_default().env is what triggered the failure in another place, so Jamie's original fix was probably the correct one?

@mathjazz
Copy link
Copy Markdown
Collaborator

Forgot to update this yesterday: The PR changes when the default Jinja backend is initialized, which seems to change what Django’s test client captures. so the fix here is to assert rendered behavior instead of Jinja internals.

@mathjazz mathjazz force-pushed the sandbox_jinja2_emails branch from 5217c5f to 629ca29 Compare May 20, 2026 08:15
@mathjazz mathjazz merged commit ee7fd9f into mozilla:main May 20, 2026
5 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.

Use sandboxed Jinja2 environment in EmailContent rendering paths

4 participants