Skip to content

HtmlComposer: embeds only images inside the base path - #108

Closed
vrana wants to merge 1 commit into
nette:masterfrom
vrana:embed-images-inside-base-path
Closed

vrana wants to merge 1 commit into
nette:masterfrom
vrana:embed-images-inside-base-path

Conversation

@vrana

@vrana vrana commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

This was an excellent gun how to shoot yourself in the foot. An innocent code like $message->setHtmlBody('<img src="assets/logo.png">' . htmlspecialchars($userText), 'assets') allowed embedding any server file in the message. A text like [[spoiler]] used in the message threw an exception.


setHtmlBody($html, $basePath) embedded any file that a reference in the HTML pointed to, including ../ paths. The [[...]] placeholder is matched anywhere in the text and HTML escaping doesn't touch it, so when the HTML contains text from users (e.g. a notification about a private message), a user writing [[../../config/local.neon]] got that file attached to the email.

Now only images inside the base path are embedded:

  • <img src>, <body background>, url(...): a reference to a file that is missing, is not an image or is outside the base path throws Nette\InvalidArgumentException.
  • [[...]]: left untouched in such a case, because it may be just text, so text from users cannot make building the email fail either.
  • Paths are normalized lexically, so symlinks inside the base path keep working. Absolute URLs are not matched, as before.

BC breaks:

  • A reference leading outside the base path (e.g. <img src="../images/x.png">) or to a non-image (e.g. a font in url(...)) throws instead of being embedded.
  • A missing file throws Nette\InvalidArgumentException instead of Nette\IOException.

🤖 Generated with Claude Code

A reference in HTML that comes from untrusted content, e.g. [[../config.neon]]
in a user's message, embedded any readable file into the email. A reference to
a file that is missing, is not an image or is outside the base path now throws
Nette\InvalidArgumentException (a missing file threw Nette\IOException before).
[[...]] may be just a text, so it is left untouched in such a case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dg

dg commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Thanks, Jakub, this was a real hole: [[...]] survives htmlspecialchars(), so user text could pull a server file into the email, and a harmless [[spoiler]] broke sending.

It's going into master in a narrower form, with you as the author, and that commit will close this PR. The check applies only to [[...]], the one reference that can come from escaped user text: it embeds only an existing image whose name has no .. segment, otherwise the text is left untouched. <img src>, <body background> and url(...) stay as they were, because they are written by the template author and restricting them would break legitimate setups (a base path of the templates dir with <img src="../images/logo.png">, fonts in url(...), and embedImages('.'), which stopped working since normalizePath('.') returns ''). So it's no longer a BC break for regular templates.

@vrana

vrana commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

Thanks. Feel free to update the docs according to the final state.

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