From 69fadcbcd7ad15dcd45991485781bcdeea97483b Mon Sep 17 00:00:00 2001 From: Cole Munz Date: Tue, 14 Jul 2026 23:29:45 -0500 Subject: [PATCH] ui,pop-ups: fix duplicated a-z class in auto-generated rule names Regexps built for appimage/snap paths use a mixed-case character class ([0-9A-Za-z]). get_rule_name() slugifies the whole regexp to build a human-readable name, and slugify lowercases everything, so that class turns into "0-9a-za-z" in the name - looking like the a-z range got duplicated. The actual matching regexp saved on the rule is unaffected, this only cleans up the generated name. Strip alnum character classes out of regexp data before slugifying it, since they don't add anything identifiable to the name anyway. Limited to plain digit/letter/hyphen classes so a hand-typed regexp matching a literal bracket (e.g. r'\[test\]') isn't mangled. Fixes #1587 --- ui/opensnitch/dialogs/prompt/utils.py | 16 ++++++++- ui/tests/dialogs/test_prompt_utils.py | 52 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 ui/tests/dialogs/test_prompt_utils.py diff --git a/ui/opensnitch/dialogs/prompt/utils.py b/ui/opensnitch/dialogs/prompt/utils.py index a362748cf9..98f1f45d8a 100644 --- a/ui/opensnitch/dialogs/prompt/utils.py +++ b/ui/opensnitch/dialogs/prompt/utils.py @@ -1,5 +1,6 @@ from slugify import slugify import os +import re import ipaddress from PyQt6.QtCore import QCoreApplication as QC @@ -23,7 +24,20 @@ def get_rule_name(rule, is_list): rule_temp_name = "%s-list" % rule_temp_name else: rule_temp_name = "%s-simple" % rule_temp_name - rule_temp_name = slugify("%s %s" % (rule_temp_name, rule.operator.data)) + + rule_data = rule.operator.data + if rule.operator.type == Config.RULE_TYPE_REGEXP: + # Alnum character classes (e.g. [0-9A-Za-z]) don't carry any + # identifying info for a rule name, and slugify() lowercases + # everything, so a mixed-case range like [0-9A-Za-z] turns into + # "0-9a-za-z", which looks like a duplicated a-z class. Strip them + # out instead of slugifying them (#1587). + # Only match classes made up of digits/letters/hyphens, so we don't + # touch escaped brackets or other regexp syntax a user may have + # typed by hand (e.g. r'\[test\]' or r'[a-z\]]'). + rule_data = re.sub(r'\[[0-9A-Za-z\-]+\]', '', rule_data) + + rule_temp_name = slugify("%s %s" % (rule_temp_name, rule_data)) return rule_temp_name[:128] diff --git a/ui/tests/dialogs/test_prompt_utils.py b/ui/tests/dialogs/test_prompt_utils.py new file mode 100644 index 0000000000..f166bee3e6 --- /dev/null +++ b/ui/tests/dialogs/test_prompt_utils.py @@ -0,0 +1,52 @@ +# +# pytest -v tests/dialogs/test_prompt_utils.py +# + +import opensnitch.proto as proto +ui_pb2, ui_pb2_grpc = proto.import_() + +from opensnitch.config import Config +from opensnitch.dialogs.prompt import utils + + +def _make_rule(data, rule_type=Config.RULE_TYPE_REGEXP): + rule = ui_pb2.Rule(name="user.choice") + rule.action = Config.ACTION_ALLOW + rule.duration = Config.DURATION_ONCE + rule.operator.type = rule_type + rule.operator.operand = Config.OPERAND_PROCESS_PATH + rule.operator.data = data + return rule + + +class TestPromptUtils(): + + def test_get_rule_name_appimage_regexp_no_duplicated_class(self): + """ Regexps built for appimage/snap paths use a mixed-case character + class ([0-9A-Za-z]). slugify() lowercases everything, which used to + turn it into "0-9a-za-z" in the rule name, looking like a duplicated + a-z class (#1587). + """ + rule = _make_rule(r'^/tmp/\.mount_handy_[0-9A-Za-z]+\/.*handy$') + name = utils.get_rule_name(rule, False) + + assert "0-9a-za-z" not in name + assert "handy" in name + + def test_get_rule_name_simple_rule_unaffected(self): + """ Non-regexp rules aren't touched by the character-class stripping. + """ + rule = _make_rule("www.google.com", rule_type=Config.RULE_TYPE_SIMPLE) + name = utils.get_rule_name(rule, False) + + assert name == "allow-once-simple-www-google-com" + + def test_get_rule_name_escaped_brackets_not_mangled(self): + """ A hand-typed regexp matching a literal bracket must not be + treated as a character class and stripped. + """ + rule = _make_rule(r'^/opt/\[legacy\]/app$') + name = utils.get_rule_name(rule, False) + + assert "legacy" in name + assert "app" in name