Please report security issues privately through GitHub's private vulnerability reporting rather than opening a public issue.
Include what you did, what happened, and what you expected. A proof of concept helps but is not required. We will acknowledge receipt and keep you updated on remediation.
This project has not reached 1.0. Only the latest commit on the default branch receives fixes.
One Google Workspace mailbox, presented to several people as if each had their own. Members authenticate to LabMail — never to Google — and see only the mail attributed to their alias.
The Google account holds one undifferentiated mailbox. Separation between members exists only because LabMail's queries enforce it:
- Every read joins
message_ownerson the session alias. There is no second filter and no default scope. - Search runs against the local mirror through that same join, so a crafted query cannot widen it.
- Outgoing
Fromis set from the session, never from the request body. - The mailbox name and filter in a request select from a fixed whitelist of SQL predicates rather than being interpolated.
A missing join is not a display bug: it exposes the whole mailbox.
Organizations requiring genuine per-user isolation — for regulatory reasons, or because members are not mutually trusted — should provision real accounts. LabMail suits a mailbox with a legitimate reason to be shared.
Members must not hold the Google credentials for the shared account. Anyone who signs in to Gmail directly sees every message. This is an operational rule; no application code can enforce it.
labmail.db holds every mirrored message body and the Google refresh token in
the clear. Filesystem access to it is equivalent to mailbox access. It is also
the only thing that needs backing up.
Session cookies are HttpOnly and SameSite=Lax but are not marked Secure,
because the default deployment is plain HTTP on a local network address. Before
exposing the service beyond a trusted network, terminate TLS in front of it and
set PUBLIC_URL.
Mail written in the composer is sanitized on the server before it is assembled, from a tag and attribute allowlist. Entities are resolved before a URL is judged, so encoded schemes cannot slip through, and attribute values are escaped on output so an encoded quote cannot start a new attribute. The editor performs the same cleaning on paste, but that copy is a convenience for the author; the server pass is the control.
HTML message bodies are rendered in an iframe with an empty sandbox attribute:
no scripts, no forms, no top-level navigation, and a null origin, so a message
cannot reach LabMail's cookies or DOM.
LabMail requests gmail.modify rather than full mail access, so a bug cannot
permanently delete mail — deletions move messages to Trash, where they remain
recoverable. It also requests gmail.settings.sharing and
gmail.settings.basic to register send-as aliases, and
admin.directory.group to create member addresses.