<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mail (webmail): a look at the security model, a few small suggestions]]></title><description><![CDATA[<p dir="auto">Hi <a class="plugin-mentions-user plugin-mentions-a" href="/user/nebulon" aria-label="Profile: nebulon">@<bdi>nebulon</bdi></a> , I've been using the new Mail app on one of our Cloudrons for a few weeks and it has quickly become my daily driver: sign in once with Cloudron and every mailbox I have access to is right there. Before rolling it out to more users I read through the code that runs in the container (1.1.1) with security in mind. Short version: it's very nicely done. A few notes and small suggestions below.</p>
<p dir="auto"><strong>What I really like</strong></p>
<ul>
<li>No master credential. The app has no password for the mail server at all: it uses the user's own OIDC token with a dedicated <code>mailclient</code> scope as the IMAP/SMTP/Sieve password, and the mail server checks per mailbox whether that token may access it. Even a bug in the app can't open a mailbox the user has no rights to. That is a better model than classic webmail, where the user types their mail password into the web app.</li>
<li>Login through Cloudron OIDC with PKCE and state, so Cloudron's 2FA applies automatically and nobody ever types a mail password into the webmail.</li>
<li>Mail HTML is rendered in a sandboxed iframe without <code>allow-scripts</code>, and the frame document gets its own CSP with <code>script-src 'none'</code>. Two locks, either one is enough.</li>
<li>Remote images and other external content are blocked by default, with a per-sender/per-domain allowlist. Great for tracking pixels.</li>
<li>Session cookie is <code>HttpOnly</code> and <code>SameSite=Lax</code>, and HSTS is on.</li>
</ul>
<p dir="auto"><strong>Small suggestions</strong></p>
<ol>
<li>Tokens in the session store end up in backups. Sessions are kept by tegel's file store in <code>/app/data/.session.store</code>, and each session file holds the access token, the refresh token (<code>offline_access</code>) and the <code>mailclient</code> token in plain text, valid for 14 rolling days. Since <code>/app/data</code> is backed up, anyone with access to a backup (unencrypted backups are not unusual) could use those tokens until they expire or are revoked. Sessions are disposable, so the simplest fix might be to keep the session store outside the backup (e.g. <code>/run</code> or a path excluded from backup) — after a restore users just sign in again. Encrypting the tokens at rest would also work.</li>
<li>The session cookie has no <code>Secure</code> flag. tegel sets <code>secure: process.env.NODE_ENV === 'production'</code>, and <code>NODE_ENV</code> is not set in the container. HSTS covers most of it, but setting <code>NODE_ENV=production</code> (together with <code>trust proxy</code>, since TLS ends at nginx) would close it.</li>
<li>No CSP or frame protection on the app itself. The message frame has a strict CSP, but the main page sends no <code>Content-Security-Policy</code> and no <code>X-Frame-Options</code>/<code>frame-ancestors</code>, so the webmail can be framed by another site (clickjacking). A <code>frame-ancestors 'self'</code> would already help, and the app looks like it could live with a fairly strict CSP.</li>
<li>Minor, defence in depth: <code>sanitizeHtml()</code> in MessageView only strips external content; scripts and event handlers stay in the HTML and are neutralised by the sandbox + CSP. That is fine as long as those stay in place; a DOMPurify pass would be a third lock if the frame setup ever changes.</li>
</ol>
<p dir="auto"><strong>A question about 1.2.0</strong></p>
<p dir="auto">1.2.0 mentions "token exchange authentication" and needs Cloudron 10.1.1, so I can't try it yet. Does it change what is kept in the session (e.g. no refresh token any more, or a short-lived exchanged token only)? If so, point 1 may already be (partly) solved.</p>
<p dir="auto">Thanks for building this, it's a really pleasant client to use already.</p>
]]></description><link>https://forum.cloudron.io/topic/16070/mail-webmail-a-look-at-the-security-model-a-few-small-suggestions</link><generator>RSS for Node</generator><lastBuildDate>Sat, 31 Oct 2026 21:16:18 GMT</lastBuildDate><atom:link href="https://forum.cloudron.io/topic/16070.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 05 Oct 2026 10:20:14 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Mail (webmail): a look at the security model, a few small suggestions on Tue, 06 Oct 2026 08:02:26 GMT]]></title><description><![CDATA[<p dir="auto">Thanks for all the points! The next version has secure cookies set when deployed, as well as setting <code>frame-ancestores: none</code> by default (tegel change so our other apps also benefit from this).</p>
<p dir="auto">Regarding the tokens, the mailbox listing and mail password via the <code>mailclient</code> scope got removed and the mail server now uses tokens whicih the mail app can get for a user via the cloudron app bridge (hence the new cloudron version requirement). This means nothing mail related ends up in the session store and the oidc related parts there are only for the login bits of the webui.</p>
<p dir="auto">For the last part about HTML sanitizing, we will have to do some more work on mail rendering and will look into that then.</p>
]]></description><link>https://forum.cloudron.io/post/130526</link><guid isPermaLink="true">https://forum.cloudron.io/post/130526</guid><dc:creator><![CDATA[nebulon]]></dc:creator><pubDate>Tue, 06 Oct 2026 08:02:26 GMT</pubDate></item></channel></rss>