# How MCP Least Privilege Analysis Detects Permission Mismatches in Skill Definitions

> Learn how MCP least privilege analysis detects permission mismatches in SkillSpector skill definitions by comparing manifest declarations against executable capabilities for enhanced security.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: deep-dive
- Published: 2026-06-17

---

**The MCP least privilege analyzer compares permissions declared in a skill's [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/mcp_least_privilege.py) | Core analyzer containing `node()`, detection logic, and LP rules |
| [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) | `Finding` data class for structured reporting |
| [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) | `SkillspectorState` dictionary passed between pipeline nodes |
| [`tests/test_mcp_least_privilege.py`](https://github.com/NVIDIA/SkillSpector/blob/main/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

```python
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

```python
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.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) manifests 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`](https://github.com/NVIDIA/SkillSpector/blob/main/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.