From 0e56aa4976670a09d2be9228baa20f36453254a7 Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Fri, 21 Aug 2026 23:40:00 +0800 Subject: [PATCH] fix(ea1): bound wildcard-grant pattern to a single line and a standalone '*' The EA1 wildcard-tool-access regex used \s* between the colon and the expected value. Python's \s matches newlines even under re.MULTILINE, so the pattern bridged blank lines (e.g. "For each tool:\n\n**Input Schema:**") and treated the first '*' of markdown bold as a wildcard grant, producing EA1/MEDIUM false positives on otherwise benign skills. Constrain the gap to [ \t]* (same line) and require the matched '*' not to be followed by another '*' or a word character, so tools: "*", [*] and '*' still match while bold text and word-adjacent '*' do not. Fixes #405 Signed-off-by: rootkiller6788 --- .../static_patterns_excessive_agency.py | 6 +++++- tests/unit/test_patterns_new.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py b/src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py index 04ba47f7a..87d56f127 100644 --- a/src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py +++ b/src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py @@ -42,7 +42,11 @@ # EA1: Unrestricted Tool Access EA1_PATTERNS = [ - (r"(?:tools?|permissions?)\s*:\s*\[?\s*['\"]?\*['\"]?\s*\]?", 0.85), + # EA1 wildcard grant: keep the value on the same line as the colon (the + # previous \s* spanned blank lines and bridged unrelated headings) and + # require the '*' to be a standalone token so markdown bold (**) or a + # word-adjacent '*' (e.g. a YAML anchor) is not treated as a wildcard. + (r"(?:tools?|permissions?)\s*:[ \t]*\[?[ \t]*['\"]?\*(?!\*|\w)['\"]?[ \t]*\]?", 0.85), (r"(?:allow|grant|enable)\s+(?:access\s+to\s+)?(?:all|any|every)\s+tools?", 0.8), ( r"(?:no|without)\s+(?:tool|permission|access|capability)\s+(?:restrictions?|constraints?|limitations?)", diff --git a/tests/unit/test_patterns_new.py b/tests/unit/test_patterns_new.py index d96e5f58e..fb7fed8dc 100644 --- a/tests/unit/test_patterns_new.py +++ b/tests/unit/test_patterns_new.py @@ -88,6 +88,8 @@ class TestExcessiveAgency: "content", [ pytest.param('tools: ["*"]', id="wildcard_tools"), + pytest.param("tools: *", id="wildcard_tools_bare"), + pytest.param("permissions: '*'", id="wildcard_permissions"), pytest.param("Allow access to all tools without restriction.", id="grant_all_tools"), pytest.param("This skill can execute arbitrary commands.", id="arbitrary_commands"), pytest.param( @@ -104,6 +106,21 @@ def test_ea1_read_only_docs_not_flagged(self) -> None: ) assert not any(f.rule_id == "EA1" for f in findings) + @pytest.mark.parametrize( + "content", + [ + pytest.param( + "For each tool:\n\n**Input Schema:**", + id="no_cross_heading_bridge", + ), + pytest.param("**API Coverage vs. Workflow Tools:**", id="no_bold_markdown_collision"), + pytest.param("Tools: **Read**, **Write**", id="no_bolded_named_tool_list"), + ], + ) + def test_ea1_no_false_positive_on_markdown_bold_or_heading(self, content: str) -> None: + findings = ea_mod.analyze(content, "SKILL.md", "markdown") + assert not any(f.rule_id == "EA1" for f in findings) + @pytest.mark.parametrize( "content,filename,filetype", [