fix: Audit-Findings — Theme-Validierung, locale-unabhängige Tests

- Theme-Name aus settings.ini gegen Regex validiert (nur [A-Za-z0-9_-]),
  verhindert Path-Traversal über GTK-Theme-Loading (S-05)
- Faillock-Tests nutzen expliziten strings-Parameter statt System-Locale,
  Tests laufen jetzt auch auf EN-Systemen (MAINT-4)
- Test für Path-Traversal im Theme-Namen ergänzt
This commit is contained in:
nevaforget 2026-03-26 12:36:16 +01:00
parent 65d3ba64f9
commit 8b1608f99d
3 changed files with 31 additions and 9 deletions

View File

@ -2,9 +2,12 @@
# ABOUTME: Provides User dataclass and helper functions for the greeter UI. # ABOUTME: Provides User dataclass and helper functions for the greeter UI.
import configparser import configparser
import re
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
VALID_THEME_NAME = re.compile(r"^[A-Za-z0-9_-]+$")
NOLOGIN_SHELLS = {"/usr/sbin/nologin", "/sbin/nologin", "/bin/false", "/usr/bin/nologin"} NOLOGIN_SHELLS = {"/usr/sbin/nologin", "/sbin/nologin", "/bin/false", "/usr/bin/nologin"}
MIN_UID = 1000 MIN_UID = 1000
MAX_UID = 65533 MAX_UID = 65533
@ -102,6 +105,9 @@ def get_user_gtk_theme(config_dir: Path | None = None) -> str | None:
return None return None
if config.has_option("Settings", "gtk-theme-name"): if config.has_option("Settings", "gtk-theme-name"):
return config.get("Settings", "gtk-theme-name") theme = config.get("Settings", "gtk-theme-name")
# Validate against path traversal — only allow safe theme names
if theme and VALID_THEME_NAME.match(theme):
return theme
return None return None

View File

@ -11,6 +11,7 @@ from pathlib import Path
import pytest import pytest
from moongreet.greeter import faillock_warning, FAILLOCK_MAX_ATTEMPTS from moongreet.greeter import faillock_warning, FAILLOCK_MAX_ATTEMPTS
from moongreet.i18n import load_strings
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
@ -182,22 +183,26 @@ class TestFaillockWarning:
"""Tests for the faillock warning message logic.""" """Tests for the faillock warning message logic."""
def test_no_warning_on_first_attempt(self) -> None: def test_no_warning_on_first_attempt(self) -> None:
assert faillock_warning(1) is None strings = load_strings("de")
assert faillock_warning(1, strings) is None
def test_warning_on_second_attempt(self) -> None: def test_warning_on_second_attempt(self) -> None:
warning = faillock_warning(2) strings = load_strings("de")
warning = faillock_warning(2, strings)
assert warning is not None assert warning is not None
assert "1" in warning # 1 Versuch übrig assert "1" in warning # 1 Versuch übrig
def test_warning_on_third_attempt(self) -> None: def test_warning_on_third_attempt(self) -> None:
warning = faillock_warning(3) strings = load_strings("de")
warning = faillock_warning(3, strings)
assert warning is not None assert warning is not None
assert "gesperrt" in warning.lower() assert warning == strings.faillock_locked
def test_warning_beyond_max_attempts(self) -> None: def test_warning_beyond_max_attempts(self) -> None:
warning = faillock_warning(4) strings = load_strings("de")
warning = faillock_warning(4, strings)
assert warning is not None assert warning is not None
assert "gesperrt" in warning.lower() assert warning == strings.faillock_locked
def test_max_attempts_constant_is_three(self) -> None: def test_max_attempts_constant_is_three(self) -> None:
assert FAILLOCK_MAX_ATTEMPTS == 3 assert FAILLOCK_MAX_ATTEMPTS == 3

View File

@ -187,7 +187,7 @@ class TestGetUserGtkTheme:
assert result is None assert result is None
def test_handles_interpolation_characters(self, tmp_path: Path) -> None: def test_handles_interpolation_characters(self, tmp_path: Path) -> None:
"""Theme names with % characters should not trigger interpolation errors.""" """Theme names with % characters are rejected by validation."""
gtk_dir = tmp_path / ".config" / "gtk-4.0" gtk_dir = tmp_path / ".config" / "gtk-4.0"
gtk_dir.mkdir(parents=True) gtk_dir.mkdir(parents=True)
settings = gtk_dir / "settings.ini" settings = gtk_dir / "settings.ini"
@ -195,7 +195,18 @@ class TestGetUserGtkTheme:
result = get_user_gtk_theme(config_dir=gtk_dir) result = get_user_gtk_theme(config_dir=gtk_dir)
assert result == "My%Theme" assert result is None
def test_rejects_path_traversal_theme_name(self, tmp_path: Path) -> None:
"""Theme names with path traversal characters should be rejected."""
gtk_dir = tmp_path / ".config" / "gtk-4.0"
gtk_dir.mkdir(parents=True)
settings = gtk_dir / "settings.ini"
settings.write_text("[Settings]\ngtk-theme-name=../../../../etc/evil\n")
result = get_user_gtk_theme(config_dir=gtk_dir)
assert result is None
def test_ignores_symlinked_accountsservice_icon(self, tmp_path: Path) -> None: def test_ignores_symlinked_accountsservice_icon(self, tmp_path: Path) -> None:
"""AccountsService icon as symlink should be ignored to prevent traversal.""" """AccountsService icon as symlink should be ignored to prevent traversal."""