From 92ba7c22678c600d42724a1cd3abf01201780daa Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 4 Jul 2026 08:42:53 +0900 Subject: [PATCH 1/7] chore: drop unused imports, vars --- tests/test_bare_conf_files.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_bare_conf_files.py b/tests/test_bare_conf_files.py index 9235390..d090f50 100644 --- a/tests/test_bare_conf_files.py +++ b/tests/test_bare_conf_files.py @@ -4,7 +4,6 @@ import os from dzgui.app_init import copy_bare_configs from dzgui.config.xdg import get_xdg_paths, parse_filepaths -from pathlib import Path CONF_STRING = "DZGUI_CONF\n" @@ -28,7 +27,6 @@ def state_files(): @pytest.fixture def xdg_paths(): - paths = [] routes = { "XDG_CONFIG_HOME": "", "XDG_STATE_HOME": "", From 6c86bff8691229cd145a30f01e591c0c799921f8 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 4 Jul 2026 08:46:54 +0900 Subject: [PATCH 2/7] fix: drop branch from diagnostic log --- dzgui/util/diag.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/dzgui/util/diag.py b/dzgui/util/diag.py index d027687..77342e3 100644 --- a/dzgui/util/diag.py +++ b/dzgui/util/diag.py @@ -1,3 +1,4 @@ +import logging import os import platform @@ -9,7 +10,9 @@ from dzgui.const.constants import APP_NAME from dzgui.const.enum import Preferences from dzgui.config.query import lookup from dzgui.init.prefix import get_version -from dzgui.util.redact import redact +from dzgui.util.redact import redact_home + +logger = logging.getLogger(APP_NAME) def get_cpu_model() -> str: @@ -41,12 +44,16 @@ def write_diagnostic(config: Path, outfile: Path) -> None: # TODO: test availability on other distros date = datetime.now().isoformat() - distro = platform.freedesktop_os_release()["ID_LIKE"] + try: + distro = platform.freedesktop_os_release()["ID_LIKE"] + except Exception as e: + logger.warn(e) + distro = "Unknown" + kernel = os.uname().release cpu = get_cpu_model() version = get_version() - branch = lookup(config, Preferences.BRANCH) debug = lookup(config, Preferences.DEBUG) install = lookup(config, Preferences.INSTALL) default = lookup(config, Preferences.DEFAULT) @@ -54,8 +61,8 @@ def write_diagnostic(config: Path, outfile: Path) -> None: steam_path = Path(default) workshop_path = get_local_mod_path(steam_path) - steam_redacted = redact(default) - workshop_redacted = redact(str(workshop_path)) + steam_redacted = redact_home(default) + workshop_redacted = redact_home(str(workshop_path)) mods = get_local_mod_ids(steam_path) mods_pretty = print_mods(mods) @@ -65,7 +72,7 @@ def write_diagnostic(config: Path, outfile: Path) -> None: # FIXME: extraneous newlines in lists of mods template = f"""\ - {APP_NAME} version {version} ({branch}) + {APP_NAME} version {version} Date: {date} =============================== Distribution: {distro} From 5bb4271d36d888b0c34f0d5934380ac8ecd196e1 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 4 Jul 2026 08:47:34 +0900 Subject: [PATCH 3/7] feat: log-level redaction filter --- dzgui/app_init.py | 10 ++++++++- dzgui/model/model_factory.py | 5 +---- dzgui/util/redact.py | 41 ++++++++++++++++++++++-------------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/dzgui/app_init.py b/dzgui/app_init.py index 3ed89d2..4644531 100644 --- a/dzgui/app_init.py +++ b/dzgui/app_init.py @@ -4,7 +4,7 @@ import shutil from typing import TYPE_CHECKING -from dzgui.const.constants import APP_NAME, APP_NAME_LOWER +from dzgui.const.constants import APP_NAME from dzgui.const.enum import Preferences from dzgui.config.query import lookup from dzgui.config.userprefs import UserPrefs @@ -21,6 +21,7 @@ from dzgui.strings import boot # from dzgui.util.map_count import get_map_count from dzgui.util.deck import is_steam_deck, is_game_mode from dzgui.util.localize import set_locale +from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS from dzgui.util.strings import init from dzgui.views.base import App @@ -40,13 +41,19 @@ def make_parents(path: "Path") -> None: def setup_logger(log_path: "Path") -> None: + # TODO: put in consts? _format = ( "%(asctime)s␞%(levelname)s␞%(filename)s::%(funcName)s::%(lineno)s␞%(message)s" ) + fh = logging.FileHandler(log_path) formatter = logging.Formatter(_format) fh.setFormatter(formatter) fh.setLevel(logging.DEBUG) + + _filter = RedactionFilter(patterns=REDACTION_PATTERNS) + fh.addFilter(_filter) + logger.setLevel(logging.DEBUG) logger.addHandler(fh) @@ -66,6 +73,7 @@ def copy_bare_configs(config: "Path", resolution: "Path") -> tuple[bool, bool]: ] config_changed = False state_changed = False + if config.parent.exists() is False: new_file = config old_file = config.parent.parent / conf diff --git a/dzgui/model/model_factory.py b/dzgui/model/model_factory.py index daefeb3..7dfffbf 100644 --- a/dzgui/model/model_factory.py +++ b/dzgui/model/model_factory.py @@ -2,7 +2,6 @@ from dataclasses import dataclass from typing import Any, Sequence, TYPE_CHECKING from dzgui.const.enum import HELP_MENU_ROWS -from dzgui.util.redact import redact_log from dzgui.util.strings import delimiter import gi @@ -134,9 +133,7 @@ class ModelFactory: with open(path, "r") as f: lines = [line.split(delimiter) for line in f.read().splitlines()] for record in lines: - # NOTE: strips PII and API keys - clean = redact_log(record) - store.append(clean) + store.append(record) return store def new_model_from_class(self, cls: type) -> FastInsertListStore: diff --git a/dzgui/util/redact.py b/dzgui/util/redact.py index 94d4bfb..75c2dc9 100644 --- a/dzgui/util/redact.py +++ b/dzgui/util/redact.py @@ -1,20 +1,29 @@ +import logging import re +from typing import Literal -def redact(text: str) -> str: - r = r"(/home/)([^/])*" - cleaned = re.sub(r, r"/home/REDACTED", text) +api_filter = r"(.*&key=)([^&]*)(.*)" +home_filter = r"(/home/)([^/]*)(.*)" +REDACTED = r"\1REDACTED\3" +REDACTION_PATTERNS = [api_filter, home_filter] + + +def redact_home(text: str) -> str: + pat = re.compile(home_filter) + cleaned = pat.sub(REDACTED, text) return cleaned -def redact_log(record: list) -> list[str]: - """ - requests library includes Steam API key in URL params - """ - clean = [] - for item in record: - if "&key=" in item: - pat = r"(.*&key=)(\S+)(.*)" - scrubbed = re.sub(pat, r"\1REDACTED\3", item) - clean.append(scrubbed) - else: - clean.append(item) - return clean + +class RedactionFilter(logging.Filter): + def __init__(self, patterns: list[str] | None = None) -> None: + super().__init__() + self._patterns = [re.compile(pat) for pat in (patterns or [])] + + def filter(self, record: logging.LogRecord) -> Literal[True]: + for pattern in self._patterns: + try: + record.msg = pattern.sub(REDACTED, record.msg) + except TypeError: + exception_text = f"{type(record.msg).__name__}: {record.msg}" + record.msg = pattern.sub(REDACTED, exception_text) + return True From d75f87768eabcfd74160a610e8679c138e835d37 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 4 Jul 2026 08:48:03 +0900 Subject: [PATCH 4/7] chore: drop extraneous newline --- dzgui/app_init.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dzgui/app_init.py b/dzgui/app_init.py index 4644531..3502040 100644 --- a/dzgui/app_init.py +++ b/dzgui/app_init.py @@ -73,7 +73,6 @@ def copy_bare_configs(config: "Path", resolution: "Path") -> tuple[bool, bool]: ] config_changed = False state_changed = False - if config.parent.exists() is False: new_file = config old_file = config.parent.parent / conf From 9fa71720563621ee29bd9303721215fe9f063872 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 4 Jul 2026 13:18:44 +0900 Subject: [PATCH 5/7] chore: add log redaction test --- dzgui/util/redact.py | 1 + tests/test_log_redaction.py | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/test_log_redaction.py diff --git a/dzgui/util/redact.py b/dzgui/util/redact.py index 75c2dc9..2f701cd 100644 --- a/dzgui/util/redact.py +++ b/dzgui/util/redact.py @@ -3,6 +3,7 @@ import re from typing import Literal api_filter = r"(.*&key=)([^&]*)(.*)" +# FIXME: handle whitespace after directory name ($HOME root) home_filter = r"(/home/)([^/]*)(.*)" REDACTED = r"\1REDACTED\3" REDACTION_PATTERNS = [api_filter, home_filter] diff --git a/tests/test_log_redaction.py b/tests/test_log_redaction.py new file mode 100644 index 0000000..eb7d88d --- /dev/null +++ b/tests/test_log_redaction.py @@ -0,0 +1,55 @@ +import logging +import pytest + +from dzgui.util.redact import RedactionFilter, REDACTION_PATTERNS + + +class RecordsListHandler(logging.Handler): + def __init__(self) -> None: + super().__init__() + self.records_list = [] + + def emit(self, record: logging.LogRecord) -> None: + self.records_list.append(record) + + def pop(self) -> None: + return self.records_list[-1].msg + + +@pytest.mark.redact +@pytest.mark.parametrize( + "log_error, expect", + [ + ("/home/SENSITIVE_USERNAME/subdir", "/home/REDACTED/subdir"), + ( + "https://url.com/?api&key=SENSITIVE_KEY&results=10", + "https://url.com/?api&key=REDACTED&results=10", + ), + ( + "https://url.com/?api&key=SENSITIVE_KEY", + "https://url.com/?api&key=REDACTED", + ), + ( + "Error in directory: '/home/SENSITIVE_USERNAME/'", + "Error in directory: '/home/REDACTED/'", + ), + ( + "Error in directory: '/home/SENSITIVE_USERNAME'", + "Error in directory: '/home/REDACTED'", + ), + ], +) +def test_log_redaction(log_error: str, expect: str) -> None: + logger = logging.getLogger("TEST") + + handler = RecordsListHandler() + logger.addHandler(handler) + logger.setLevel(logging.DEBUG) + + _filter = RedactionFilter(patterns=REDACTION_PATTERNS) + logger.addFilter(_filter) + + logger.critical(log_error) + redacted = handler.pop() + print(redacted) + assert expect == redacted From 2418d8be6807da593e38453076ac938040d0ef71 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:12:46 +0900 Subject: [PATCH 6/7] chore: update pytest marks --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 3b3615e..0972486 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,6 +110,7 @@ markers = [ "mods: tests mod metadata/link creation", "pefile: validate PE files", "post_install: requires a completed installation", + "redact: log redaction mechanisms", "slow: long-running tests", "webtest: checks remote endpoints" ] From 78151b3bb313fbbfb89b2389dec520b256486a18 Mon Sep 17 00:00:00 2001 From: aclist <92275929+aclist@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:30:59 +0900 Subject: [PATCH 7/7] fix: update regex --- dzgui/util/redact.py | 2 +- tests/test_log_redaction.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dzgui/util/redact.py b/dzgui/util/redact.py index 2f701cd..a21b2eb 100644 --- a/dzgui/util/redact.py +++ b/dzgui/util/redact.py @@ -4,7 +4,7 @@ from typing import Literal api_filter = r"(.*&key=)([^&]*)(.*)" # FIXME: handle whitespace after directory name ($HOME root) -home_filter = r"(/home/)([^/]*)(.*)" +home_filter = r"(/home/)([^\s'\/]*)(.*)" REDACTED = r"\1REDACTED\3" REDACTION_PATTERNS = [api_filter, home_filter] diff --git a/tests/test_log_redaction.py b/tests/test_log_redaction.py index eb7d88d..a96058c 100644 --- a/tests/test_log_redaction.py +++ b/tests/test_log_redaction.py @@ -51,5 +51,4 @@ def test_log_redaction(log_error: str, expect: str) -> None: logger.critical(log_error) redacted = handler.pop() - print(redacted) assert expect == redacted