diff --git a/src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py b/src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py index 7f94e5d6..9c8c63a9 100644 --- a/src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py +++ b/src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py @@ -42,7 +42,7 @@ # EA1: Unrestricted Tool Access EA1_PATTERNS = [ - (r"(?:tools?|permissions?)\s*:\s*\[?\s*['\"]?\*['\"]?\s*\]?", 0.85), + (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/nodes/analyzers/test_static_patterns.py b/tests/nodes/analyzers/test_static_patterns.py index 34fd1eab..b7a16e0e 100644 --- a/tests/nodes/analyzers/test_static_patterns.py +++ b/tests/nodes/analyzers/test_static_patterns.py @@ -309,6 +309,48 @@ def test_p9_category_and_name_and_text(self): assert pattern_defaults.get_remediation("P9").strip() +class TestRunStaticPatternsExcessiveAgencyEA1: + """EA1_PATTERNS[0] must not match a blank-line gap or a markdown bold span.""" + + def test_ea1_multiline_gap_not_flagged(self): + """A blank-line gap between the colon and a bold heading is not a wildcard grant.""" + state = { + "components": ["skill.md"], + "file_cache": { + "skill.md": "tool:\n\n**Input Schema:**", + }, + } + findings = static_runner.run_static_patterns(state, [excessive_agency_module]) + assert not any(f.rule_id == "EA1" for f in findings) + + def test_ea1_bold_span_not_flagged(self): + """The first '*' of a '**bold**' heading is not a wildcard grant.""" + state = { + "components": ["skill.md"], + "file_cache": { + "skill.md": "**API Coverage vs. Workflow Tools:**", + }, + } + findings = static_runner.run_static_patterns(state, [excessive_agency_module]) + assert not any(f.rule_id == "EA1" for f in findings) + + @pytest.mark.parametrize( + "content", + ['tools: "*"', "tools: [*]", "permissions: '*'"], + ids=["quoted_star", "bracket_star", "single_quoted_star"], + ) + def test_ea1_real_wildcard_still_flagged(self, content: str): + """The three real wildcard-grant shapes still produce EA1, MEDIUM severity.""" + state = { + "components": ["skill.md"], + "file_cache": {"skill.md": content}, + } + findings = static_runner.run_static_patterns(state, [excessive_agency_module]) + ea1 = [f for f in findings if f.rule_id == "EA1"] + assert len(ea1) >= 1 + assert ea1[0].severity == "MEDIUM" + + class TestRunStaticPatternsDataExfiltration: """run_static_patterns with data_exfiltration: E1, E2, E5."""