Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/frameforge/semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def validate_rules(rules):
if not isinstance(rule, dict) or not all(
isinstance(rule.get(k), str) and bool(rule[k].strip())
for k in ("id", "plugin_id", "topic", "recommendation")
):
) or any(rule[k] != rule[k].strip() for k in ("id", "plugin_id", "topic")):
raise ValueError("invalid semantic rule identity")
if rule["id"] in ids:
raise ValueError("duplicate semantic rule id")
Expand All @@ -74,7 +74,9 @@ def validate_rules(rules):
for field in ("paths", "tokens"):
values = rule.get(field)
if not isinstance(values, list) or not values or len(values) > 20 or not all(
isinstance(v, str) and 0 < len(v) <= 256 for v in values
isinstance(v, str) and 0 < len(v) <= 256
and (field != "tokens" or bool(v.strip()))
for v in values
):
raise ValueError("invalid semantic rule " + field)
if any(Path(v).is_absolute() or re.match(r"^[A-Za-z]:", v) or ".." in Path(v.replace("\\", "/")).parts or "\x00" in v or "\\" in v for v in rule["paths"]):
Expand Down
18 changes: 18 additions & 0 deletions tests/test_semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ def test_later_declared_path_is_not_ignored_after_first_file_exists(self):
self.assertEqual(result["status"], "pattern_observed_control_flow_unverified")
self.assertEqual({item["path"] for item in result["evidence"]}, {"Events.cs"})

def test_whitespace_only_tokens_are_rejected(self):
for token in ("", " ", " ", "\t"):
with self.subTest(token=repr(token)):
with self.assertRaisesRegex(ValueError, "tokens"):
validate_rules([dict(rule(), tokens=[token])])

def test_semantic_identity_fields_reject_surrounding_whitespace(self):
for field in ("id", "plugin_id", "topic"):
with self.subTest(field=field):
value = rule()
value[field] = " " + value[field]
with self.assertRaisesRegex(ValueError, "identity"):
validate_rules([value])

def test_code_tokens_may_contain_meaningful_spaces(self):
value = dict(rule(), tokens=["Event += OnEvent;"])
self.assertEqual(validate_rules([value]), [value])

def test_invalid_paths_and_duplicate_ids(self):
with self.assertRaisesRegex(ValueError,"path"):
validate_rules([rule(["../secret"])])
Expand Down
Loading