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:
parent
65d3ba64f9
commit
8b1608f99d
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user