How MCP Least Privilege Analysis Detects Permission Mismatches in Skill Definitions
The MCP least privilege analyzer compares permissions declared in a skill's SKILL.md manifest against actual capabilities extracted from executable code, generating security findings when wildcards, missing declarations, or capability mismatches violate least-privilege principles.
The NVIDIA/SkillSpector repository provides a static analysis framework that enforces security boundaries for AI agent skills. Its MCP least privilege analysis module automatically detects permission mismatches by cross-referencing YAML manifest declarations with real-world code behavior, ensuring skills operate with minimal necessary permissions.
The Four-Step Detection Pipeline
The analyzer operates through a systematic pipeline defined in src/skillspector/nodes/analyzers/mcp_least_privilege.py. Each processing stage feeds into the next, culminating in four distinct security checks (LP1-LP4).
Step 1: Load Manifest and Executable Metadata
The node() function initiates analysis by reading the manifest dictionary from the state object, which contains the YAML front-matter from SKILL.md. It also retrieves component_metadata, a list describing every file's path, type, and executable status.
If the skill lacks a manifest or contains only documentation (no executable files), the analyzer returns early with no findings. This prevents false positives on non-functional assets.
Step 2: Scan Code for Capabilities
For each executable file, the analyzer extracts source text from file_cache and runs _detect_capabilities(). This function applies regular-expression patterns defined in _CAPABILITY_PATTERNS (lines 40-78) to identify capability categories including shell, network, file_read, file_write, env, and mcp.
The result is a mapping of path → set(capability) and a union of all capabilities (all_caps). The detection loop starts at line 24 of the analyzer file.
Step 3: Normalize Declared Permissions
The _map_permissions_to_categories() function converts the manifest's permissions list to lowercase and maps entries to standardized capability categories via _PERM_TO_CAPABILITY (lines 92-113). The _has_wildcard() helper identifies blanket entries matching _WILDCARD_PERMS such as "*", "all", "full", or "any".
Step 4: Apply LP Rules
The final stage evaluates four security rules against the normalized data. Each rule generates Finding objects (defined in src/skillspector/models.py) with severity ratings, confidence scores, and remediation advice.
The Four LP Security Rules
The analyzer implements four specific validation rules to identify different classes of permission mismatches.
LP2: Wildcard Permission Detection
When the permissions list contains wildcard values (_WILDCARD_PERMS), the analyzer emits a MEDIUM severity finding with 0.90 confidence. This indicates that blanket permissions disable least-privilege enforcement entirely, granting the skill unrestricted access regardless of actual needs.
LP3: Missing Permission Declarations
If permissions is None or empty but the code analysis detected capabilities (all_caps is non-empty), the analyzer generates a MEDIUM finding with 0.70 confidence. This rule catches skills that have implicit capabilities but no explicit security contract.
LP1: Under-Declared Capabilities
For non-wildcard permission lists, the analyzer checks each detected capability in all_caps against declared permissions. Any capability not covered by the manifest triggers an LP1 finding marked HIGH severity.
Confidence varies by context: 0.75 for standard files, reduced to 0.55 for test files (identified by _is_test_file()). The detection logic resides in the loop starting at line 66.
LP4: Over-Declared Permissions
When declared permissions exist but map to capabilities not present in all_caps, the analyzer emits LP4 findings. Each extraneous permission generates a LOW severity finding with 0.65 confidence. The validation loop begins at line 112.
Confidence Scoring and Severity Levels
The analyzer uses hard-coded confidence values per rule, clamped to the [0, 1] range by the _clamp() helper function. This ensures consistent scoring across different analysis runs while accounting for contextual factors like test file detection.
Severity distribution:
- HIGH: LP1 (under-declared capabilities represent immediate security risks)
- MEDIUM: LP2 (wildcards) and LP3 (missing declarations)
- LOW: LP4 (over-declared permissions indicate potential confusion but not immediate risk)
Source Code Architecture
The implementation spans several key files within the NVIDIA/SkillSpector repository:
| File | Role |
|---|---|
src/skillspector/nodes/analyzers/mcp_least_privilege.py |
Core analyzer containing node(), detection logic, and LP rules |
src/skillspector/models.py |
Finding data class for structured reporting |
src/skillspector/state.py |
SkillspectorState dictionary passed between pipeline nodes |
tests/test_mcp_least_privilege.py |
Test suite validating each LP rule |
tests/fixtures/mcp_underdeclared_skill/ |
Fixture for under-declaration test cases |
tests/fixtures/mcp_overprivileged_skill/ |
Fixture for over-declaration test cases |
Practical Usage Examples
Running Analysis on a Skill Directory
from skillspector import graph
result = graph.invoke({
"input_path": "./my_skill/",
"output_format": "json",
"use_llm": False,
})
# Filter for least-privilege findings
for f in result["filtered_findings"]:
if f["rule_id"].startswith("LP"):
print(f"[{f['severity']}] {f['rule_id']}: {f['message']} "
f"(confidence={f['confidence']:.2f})")
Unit Testing with Minimal State
state = {
"manifest": {
"name": "demo-skill",
"permissions": ["read"],
"triggers": []
},
"file_cache": {
"scripts/agent.py": """
import httpx, subprocess
httpx.get('https://example.com')
subprocess.run(['ls'])
"""
},
"component_metadata": [
{
"path": "scripts/agent.py",
"type": "python",
"executable": True,
"lines": 4,
"size_bytes": 120
}
],
"has_executable_scripts": True,
"components": ["scripts/agent.py"],
}
from skillspector.nodes.analyzers import mcp_least_privilege
findings = mcp_least_privilege.node(state)["findings"]
# Returns LP1 findings for missing 'network' and 'shell' permissions
Summary
- MCP least privilege analysis cross-references
SKILL.mdmanifests against actual code capabilities to enforce security boundaries. - The analyzer implements four distinct rules (LP1-LP4) covering under-declaration, wildcards, missing permissions, and over-declaration.
- Severity levels range from HIGH (LP1) to LOW (LP4), with confidence scores adjusted for test files and rule types.
- Implementation resides in
src/skillspector/nodes/analyzers/mcp_least_privilege.py, utilizing regex patterns and mapping dictionaries to categorize capabilities. - The framework skips analysis for non-executable skills or those lacking manifests, preventing false positives.
Frequently Asked Questions
What triggers an LP1 finding versus an LP4 finding?
An LP1 finding triggers when code uses capabilities not declared in the manifest (under-declaration), while LP4 triggers when the manifest declares permissions for capabilities the code never uses (over-declaration). LP1 carries HIGH severity because missing permissions may indicate hidden malicious behavior, whereas LP4 is LOW severity since excess permissions are less immediately dangerous.
How does the analyzer distinguish between production and test files?
The helper function _is_test_file() checks file paths to identify test scripts. When LP1 findings occur only in test files, confidence drops from 0.75 to 0.55, reflecting the lower security risk of test-only capabilities.
Can the analyzer detect capabilities in non-Python files?
Yes. The _CAPABILITY_PATTERNS regex patterns apply to any executable file present in file_cache, regardless of language. The component_metadata list determines which files are scanned based on their executable flag and path information.
What happens if a skill uses wildcard permissions?
The analyzer detects wildcards via _WILDCARD_PERMS (matching "*", "all", "full", or "any") and emits an LP2 finding with MEDIUM severity and 0.90 confidence. This flags the violation of least-privilege principles without checking individual capabilities, as wildcards grant unrestricted access.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →