Reserve privileged alias addresses for admins

Any user could create aliases like postmaster@, webmaster@ or abuse@ on
their domain. CAs use these addresses to validate domains, so a family
account (or a compromised one) could obtain TLS certificates or catch
abuse and system mails.

- Only admins may create aliases with a reserved local part (admin,
  administrator, webmaster, hostmaster, postmaster, abuse, security,
  noc, root, mailer-daemon). The acting user counts, so admins can
  still assign such aliases to other users.
- Escape the domains in the email regex, so "finn.st" no longer
  matches "finnXst".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
finnandClaude Opus 5.5 committed 2026-10-21 15:23:12 +00:00
1 parent fc24ba068d
commit 4a03b5c1a1
3 files changed
+29 -2

No files matched your search

+2 -1
View File
@@ -65,5 +65,6 @@
"Used as an alias by {alias}.": "Word für den Alias {alias} verwendet", "Used as an alias by {alias}.": "Word für den Alias {alias} verwendet",
"Invalid domain": "Invalide Domain", "Invalid domain": "Invalide Domain",
"Can use different alias domains": "Kann unterschiedliche Aliasdomains benutzen", "Can use different alias domains": "Kann unterschiedliche Aliasdomains benutzen",
"Can use different alias domains is not set": "Kann unterschiedliche Aliasdomains benutzen ist nicht gesetzt" "Can use different alias domains is not set": "Kann unterschiedliche Aliasdomains benutzen ist nicht gesetzt",
"This address is reserved and can only be created by an admin.": "Diese Adresse ist reserviert und kann nur von einem Admin angelegt werden."
} }
+6
View File
@@ -42,6 +42,12 @@ class AliasCreate(
full_email = f"{self.email}@{self.domain}" full_email = f"{self.email}@{self.domain}"
if not self.is_email_valid(full_email): if not self.is_email_valid(full_email):
error = True error = True
elif not self.auth_user.is_admin and self.is_reserved_email(full_email):
flash(
t("This address is reserved and can only be created by an admin."),
category="error",
)
error = True
enabled = self.request.form.get("enabled") enabled = self.request.form.get("enabled")
if enabled == "on": if enabled == "on":
+21 -1
View File
@@ -5,14 +5,34 @@ from flask import flash
from max.db import get_domains from max.db import get_domains
from max.translations import t from max.translations import t
RESERVED_LOCAL_PARTS = frozenset(
(
"admin",
"administrator",
"webmaster",
"hostmaster",
"postmaster",
"abuse",
"security",
"noc",
"root",
"mailer-daemon",
)
)
class CheckEmailMixin: class CheckEmailMixin:
def is_email_valid(self, email: str): def is_email_valid(self, email: str):
"""Checks for the email being valid and that it has a valid domain.""" """Checks for the email being valid and that it has a valid domain."""
regex = re.compile(f"^[a-zA-Z0-9+_-]+@({'|'.join(get_domains())})$") domains = "|".join(map(re.escape, get_domains()))
regex = re.compile(f"^[a-zA-Z0-9+_-]+@({domains})$")
match = regex.match(email) match = regex.match(email)
if not match: if not match:
flash(t("The email is not valid"), category="error") flash(t("The email is not valid"), category="error")
return False return False
return True return True
def is_reserved_email(self, email: str):
local_part = email.split("@")[0]
return local_part.lower() in RESERVED_LOCAL_PARTS