fix: Audit-Findings — Bugs, Security-Hardening und Performance

- BUG-02: cancel_session bei mehrstufiger Auth (z.B. TOTP)
- BUG-03: CalledProcessError bei reboot/shutdown abfangen
- M-1+EH-01: configparser interpolation=None + Error-Handling
- H-1: Exec-Cmd Validierung (absoluter Pfad erforderlich)
- PERF: Theme-Guard, Default-Avatar-Cache, Avatar-Cache per User
- Mutable Default Arguments in sessions.py (list → tuple)
- Ungenutzten Gio-Import entfernt
This commit is contained in:
2026-03-26 11:35:14 +01:00
parent af01e0a44d
commit c4b3dc833b
6 changed files with 139 additions and 21 deletions
+46 -15
View File
@@ -5,13 +5,14 @@ import os
import shlex
import socket
import stat
import subprocess
from importlib.resources import files
from pathlib import Path
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Gdk", "4.0")
from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
from moongreet.config import load_config
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
@@ -38,6 +39,8 @@ class GreeterWindow(Gtk.ApplicationWindow):
self._sessions = get_sessions()
self._selected_user: User | None = None
self._greetd_sock: socket.socket | None = None
self._default_avatar_pixbuf: GdkPixbuf.Pixbuf | None = None
self._avatar_cache: dict[str, GdkPixbuf.Pixbuf] = {}
self._build_ui()
self._select_initial_user()
@@ -217,16 +220,19 @@ class GreeterWindow(Gtk.ApplicationWindow):
self._password_entry.set_text("")
self._error_label.set_visible(False)
# Update avatar
avatar_path = get_avatar_path(
user.username, home_dir=user.home
)
if avatar_path and avatar_path.exists():
self._set_avatar_from_file(avatar_path)
elif DEFAULT_AVATAR_PATH.exists():
self._set_default_avatar()
# Update avatar (use cache if available)
if user.username in self._avatar_cache:
self._avatar_image.set_from_pixbuf(self._avatar_cache[user.username])
else:
self._avatar_image.set_from_icon_name("avatar-default-symbolic")
avatar_path = get_avatar_path(
user.username, home_dir=user.home
)
if avatar_path and avatar_path.exists():
self._set_avatar_from_file(avatar_path, user.username)
elif DEFAULT_AVATAR_PATH.exists():
self._set_default_avatar()
else:
self._avatar_image.set_from_icon_name("avatar-default-symbolic")
# Apply user's GTK theme if available
self._apply_user_theme(user)
@@ -243,9 +249,10 @@ class GreeterWindow(Gtk.ApplicationWindow):
if settings is None:
return
if theme_name:
current = settings.get_property("gtk-theme-name")
if theme_name and current != theme_name:
settings.set_property("gtk-theme-name", theme_name)
else:
elif not theme_name and current:
settings.reset_property("gtk-theme-name")
def _get_foreground_color(self) -> str:
@@ -258,6 +265,9 @@ class GreeterWindow(Gtk.ApplicationWindow):
def _set_default_avatar(self) -> None:
"""Load the default avatar SVG, tinted with the GTK foreground color."""
if self._default_avatar_pixbuf:
self._avatar_image.set_from_pixbuf(self._default_avatar_pixbuf)
return
try:
svg_text = DEFAULT_AVATAR_PATH.read_text()
fg_color = self._get_foreground_color()
@@ -269,16 +279,19 @@ class GreeterWindow(Gtk.ApplicationWindow):
loader.close()
pixbuf = loader.get_pixbuf()
if pixbuf:
self._default_avatar_pixbuf = pixbuf
self._avatar_image.set_from_pixbuf(pixbuf)
except (GLib.Error, OSError):
self._avatar_image.set_from_icon_name("avatar-default-symbolic")
def _set_avatar_from_file(self, path: Path) -> None:
def _set_avatar_from_file(self, path: Path, username: str | None = None) -> None:
"""Load an image file and set it as the avatar, scaled to AVATAR_SIZE."""
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
str(path), AVATAR_SIZE, AVATAR_SIZE, True
)
if username:
self._avatar_cache[username] = pixbuf
self._avatar_image.set_from_pixbuf(pixbuf)
except GLib.Error:
self._avatar_image.set_from_icon_name("avatar-default-symbolic")
@@ -379,9 +392,21 @@ class GreeterWindow(Gtk.ApplicationWindow):
self._close_greetd_sock()
return
if response.get("type") == "auth_message":
# Multi-stage auth (e.g. TOTP) is not supported
cancel_session(sock)
self._close_greetd_sock()
self._show_error("Mehrstufige Authentifizierung wird nicht unterstützt")
return
# Step 3: Start session
if response.get("type") == "success":
cmd = shlex.split(session.exec_cmd)
if not cmd or not Path(cmd[0]).is_absolute():
self._show_error("Ungültiger Session-Befehl")
cancel_session(sock)
self._close_greetd_sock()
return
response = start_session(sock, cmd)
if response.get("type") == "success":
@@ -438,11 +463,17 @@ class GreeterWindow(Gtk.ApplicationWindow):
def _on_reboot_clicked(self, button: Gtk.Button) -> None:
"""Handle reboot button click."""
reboot()
try:
reboot()
except subprocess.CalledProcessError:
self._show_error("Neustart fehlgeschlagen")
def _on_shutdown_clicked(self, button: Gtk.Button) -> None:
"""Handle shutdown button click."""
shutdown()
try:
shutdown()
except subprocess.CalledProcessError:
self._show_error("Herunterfahren fehlgeschlagen")
@staticmethod
def _load_last_user() -> str | None:
+4 -4
View File
@@ -5,8 +5,8 @@ import configparser
from dataclasses import dataclass
from pathlib import Path
DEFAULT_WAYLAND_DIRS = [Path("/usr/share/wayland-sessions")]
DEFAULT_XSESSION_DIRS = [Path("/usr/share/xsessions")]
DEFAULT_WAYLAND_DIRS = (Path("/usr/share/wayland-sessions"),)
DEFAULT_XSESSION_DIRS = (Path("/usr/share/xsessions"),)
@dataclass
@@ -37,8 +37,8 @@ def _parse_desktop_file(path: Path, session_type: str) -> Session | None:
def get_sessions(
wayland_dirs: list[Path] = DEFAULT_WAYLAND_DIRS,
xsession_dirs: list[Path] = DEFAULT_XSESSION_DIRS,
wayland_dirs: tuple[Path, ...] = DEFAULT_WAYLAND_DIRS,
xsession_dirs: tuple[Path, ...] = DEFAULT_XSESSION_DIRS,
) -> list[Session]:
"""Discover available sessions from .desktop files."""
sessions: list[Session] = []
+5 -2
View File
@@ -95,8 +95,11 @@ def get_user_gtk_theme(config_dir: Path | None = None) -> str | None:
if not settings_file.exists():
return None
config = configparser.ConfigParser()
config.read(settings_file)
config = configparser.ConfigParser(interpolation=None)
try:
config.read(settings_file)
except configparser.Error:
return None
if config.has_option("Settings", "gtk-theme-name"):
return config.get("Settings", "gtk-theme-name")