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
9 changes: 6 additions & 3 deletions docs/B.3.1-mcp-least-privilege.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,13 @@ hiding its true behavior. This is the strongest indicator of deceptive intent
among the LP rules -- the skill is actively performing operations it claims not
to need.

**Example:** A skill declares `permissions: [read]` but its code contains
`httpx.post(...)`. LP1 fires for the undeclared `network` capability.
**Example:** An Agent Skills `SKILL.md` declares `allowed-tools: Read` but its
code contains `httpx.post(...)`. LP1 fires for the undeclared `network`
capability.

**Remediation:** Add the missing permission to SKILL.md, or remove the code
**Remediation:** For Agent Skills `SKILL.md`, add a tool that covers the missing
capability to the `allowed-tools` frontmatter field. For MCP server manifests,
add the missing capability to the `permissions` list. Otherwise, remove the code
that requires it.

---
Expand Down
15 changes: 12 additions & 3 deletions src/skillspector/nodes/analyzers/mcp_least_privilege.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,17 @@ def node(state: SkillspectorState) -> AnalyzerNodeResponse:
confidence = _clamp(0.55 if is_test_only else 0.75)
source_files = [p for p, caps in file_capabilities.items() if cap in caps]
primary_file = source_files[0] if source_files else "SKILL.md"
if allowed_tools:
remediation = (
f"Add a tool that covers the '{cap}' capability to the "
"'allowed-tools' frontmatter field in SKILL.md, or remove "
"the code that requires it."
)
else:
remediation = (
f"Add the '{cap}' capability to the MCP server manifest's "
"'permissions' list, or remove the code that requires it."
)
logger.debug(
"%s: LP1 underdeclared capability %s in %s", ANALYZER_ID, cap, primary_file
)
Expand All @@ -383,9 +394,7 @@ def node(state: SkillspectorState) -> AnalyzerNodeResponse:
f"The skill uses '{cap}' capability that is not listed in its permissions. "
"This may indicate deceptive intent or missing permission declarations."
),
remediation=(
f"Add the '{cap}' permission to SKILL.md, or remove the code that requires it."
),
remediation=remediation,
)
)

Expand Down
2 changes: 1 addition & 1 deletion src/skillspector/nodes/analyzers/pattern_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ class PatternCategory(StrEnum):
"YR3": "Remove all cryptocurrency mining code, pool references, and miner binaries. Mining in agent skills is unauthorized resource abuse. Report the skill as malicious.",
"YR4": "Remove offensive tool references and exploit code. Legitimate agent skills should not contain penetration testing tools, exploit frameworks, or reconnaissance utilities.",
# MCP Least Privilege (B.3.1)
"LP1": "Add the missing permission to SKILL.md, or remove the code that requires it.",
"LP1": "Declare the missing capability in the manifest type being scanned: for Agent Skills SKILL.md, add a covering tool to the 'allowed-tools' frontmatter field; for MCP server manifests, add the capability to the 'permissions' list. Otherwise, remove the code that requires it.",
"LP2": "Replace wildcard permissions ('*', 'all', 'full', 'any') with an explicit list of required permissions.",
"LP3": "Declare the skill's tool scope: for Claude Code / Agent Skills SKILL.md, list the tools the skill may invoke in the 'allowed-tools' frontmatter field; for MCP server manifests, add a 'permissions' list naming the required capabilities.",
"LP4": "Remove the declared permission if the corresponding capability is no longer used.",
Expand Down
6 changes: 6 additions & 0 deletions tests/test_mcp_least_privilege.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ def test_underdeclared_has_high_severity_for_lp1(self):
for lp1 in lp1_findings:
assert lp1.severity == "HIGH", f"Expected HIGH severity for LP1, got {lp1.severity}"
assert lp1.confidence >= 0.70
assert lp1.remediation is not None
assert "'permissions' list" in lp1.remediation
assert "SKILL.md" not in lp1.remediation


class TestLP3NoPermissions:
Expand Down Expand Up @@ -306,6 +309,9 @@ def test_allowed_tools_underdeclared_fires_lp1(self):
)
for lp1 in lp1_findings:
assert lp1.severity == "HIGH", f"Expected HIGH severity for LP1, got {lp1.severity}"
assert lp1.remediation is not None
assert "'allowed-tools'" in lp1.remediation
assert "'permissions'" not in lp1.remediation

def test_allowed_tools_fully_covered_no_lp1(self):
"""allowed-tools: [Bash] + only shell code → no LP1 (capability is covered)."""
Expand Down
Loading