# How MCP Least Privilege Detection Analyzes Code Capabilities in NVIDIA SkillSpector

> Learn how MCP least privilege detection analyzes code capabilities in NVIDIA SkillSpector. It compares declared permissions against actual code using regex for accurate LP1-LP4 findings. Enhance your security posture.

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

---

**The MCP least-privilege analyzer in NVIDIA SkillSpector compares declared permissions in [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) manifests against actual code capabilities detected via regex patterns, generating LP1-LP4 findings to identify under-declared, over-declared, wildcard, or missing permissions.**

The **MCP least privilege detection** system in NVIDIA SkillSpector provides automated static analysis to ensure Model Context Protocol (MCP) skills follow security best practices. This analyzer bridges the gap between what a skill claims to need in its manifest and what it actually executes in code. By scanning executable files for capability patterns and cross-referencing them against declared permissions, SkillSpector helps developers maintain the principle of least privilege.

## How the MCP Least Privilege Analyzer Works

The analyzer processes skills through a static analysis 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).

### Core Analysis Flow

The `node(state: SkillspectorState)` function orchestrates the validation process. It first loads the skill's manifest and file cache, then normalizes the `permissions` field to handle cases where permissions are absent or empty. If the skill contains only documentation files or lacks a manifest, the analyzer returns early with an empty finding list.

For each executable file in the component metadata, the analyzer extracts capabilities using `_detect_capabilities`, which applies regex patterns against the source text. Simultaneously, declared permissions are mapped to capability categories via `_map_permissions_to_categories`. The system then compares these two sets to generate specific findings.

### Detecting Code Capabilities

The `_CAPABILITY_PATTERNS` dictionary contains case-insensitive regex patterns that identify six distinct capability categories: **shell**, **network**, **file_read**, **file_write**, **env**, and **mcp**.

For example, the network capability detection uses patterns matching common HTTP libraries:

```python
"network": [
    r"\bhttpx\b",
    r"\brequests\b",
    r"\burllib\b",
    r"\baiohttp\b",
    r"socket\.connect",
    r"fetch\(",
    r"XMLHttpRequest",
],

```

These patterns are defined in [`mcp_least_privilege.py`](https://github.com/NVIDIA/SkillSpector/blob/main/mcp_least_privilege.py) and applied to every executable file's raw text to build a map of detected capabilities per file path.

### Mapping Permissions to Categories

The `_PERM_TO_CAPABILITY` dictionary translates common permission strings from the manifest into standardized capability categories. For instance, the permission `"bash"` maps to the `"shell"` category, while `"network"` maps directly to `"network"`. This normalization enables consistent comparison between declared intents and actual code behavior.

## The Four Least Privilege Findings (LP1-LP4)

The analyzer generates standardized findings based on NIST-style least privilege checks, each with specific severity and confidence levels.

### LP1: Under-Declared Capabilities

**LP1** identifies when code implements capabilities not covered by the manifest. If a capability is detected in the source code but its category is absent from the declared permissions (and no wildcard is present), the analyzer emits an LP1 finding with **HIGH** severity and **0.75** confidence. When the capability appears only in test files, confidence drops to **0.55**.

### LP2: Wildcard Permissions

**LP2** triggers when the `_has_wildcard` function detects overly broad permissions. If any permission value matches entries in `_WILDCARD_PERMS` (`*`, `all`, `full`, or `any`), the analyzer reports a **MEDIUM** severity finding with confidence **≥0.90**, as these grant blanket access beyond the principle of least privilege.

### LP3: Missing Permissions with Active Capabilities

**LP3** occurs when the manifest lacks a `permissions` field entirely (or it is empty) while the code contains executable capabilities. This generates a **MEDIUM** severity finding with confidence **≥0.70**, flagging skills that operate without explicit permission declarations.

### LP4: Over-Declared Permissions

**LP4** detects the opposite problem: permissions declared in the manifest that have no corresponding implementation in the code. For each declared permission category with no detected capability, the analyzer emits a **LOW** severity finding with confidence **≥0.65**, suggesting potential over-privileging.

## Implementation Details in SkillSpector

The findings are constructed as instances of `skillspector.models.Finding`, defined in [`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py). Each finding includes `rule_id`, `message`, `severity`, `confidence`, `category`, and `tags` fields. Confidence values are clamped to the `[0,1]` range using the `_clamp` helper function.

Key constants in the analyzer include:
- `_WILDCARD_PERMS`: `{"*", "all", "full", "any"}` (lines 38-39)
- `_CAPABILITY_PATTERNS`: Regex mappings for the six capability categories (lines 40-60)
- `_PERM_TO_CAPABILITY`: Permission string normalization mapping (lines 92-113)

## Practical Code Examples

### Running the Analyzer Locally

To execute the least privilege detection on a local skill directory:

```python
import yaml
from pathlib import Path
from skillspector.nodes.analyzers import mcp_least_privilege

def build_state_from_dir(root: Path) -> dict:
    # Minimal subset of what `build_context` does – parses SKILL.md, builds caches.

    file_cache = {p.relative_to(root).as_posix(): p.read_text(encoding="utf-8")
                  for p in root.rglob("*") if p.is_file()}
    component_metadata = []
    exec_ext = {".py", ".sh", ".bash", ".zsh", ".js", ".ts", ".rb", ".go", ".rs", ".pl"}
    for p in root.rglob("*"):
        if not p.is_file():
            continue
        rel = p.relative_to(root).as_posix()
        suffix = p.suffix.lower()
        component_metadata.append({
            "path": rel,
            "type": suffix.lstrip("."),
            "executable": suffix in exec_ext,
            "lines": len(file_cache[rel].splitlines()),
            "size_bytes": p.stat().st_size,
        })
    # Parse manifest front‑matter (same logic as test helper)

    skill_md = file_cache.get("SKILL.md", "")
    manifest = {}
    if skill_md.startswith("---"):
        end = skill_md.find("\n---", 3)
        if end != -1:
            manifest = yaml.safe_load(skill_md[3:end])
    # Normalise permissions field to match analyzer expectations

    if isinstance(manifest.get("permissions"), list):
        manifest["permissions"] = [str(p) for p in manifest["permissions"]]
    else:
        manifest["permissions"] = None
    return {
        "manifest": manifest,
        "file_cache": file_cache,
        "component_metadata": component_metadata,
        "has_executable_scripts": any(m["executable"] for m in component_metadata),
        "components": list(file_cache.keys()),
    }

# Example usage

skill_dir = Path("/path/to/your/skill")
state = build_state_from_dir(skill_dir)
result = mcp_least_privilege.node(state)

for f in result["findings"]:
    print(f"{f.rule_id}: {f.message} (severity={f.severity}, confidence={f.confidence:.2f})")

```

### Interpreting Output

Typical analyzer output reveals specific privilege violations:

```text
LP2: Permission list contains a wildcard entry ('*', 'all', 'full', or 'any'), granting blanket access... (severity=MEDIUM, confidence=0.92)
LP1: Code capability 'network' detected in scripts/agent.py but not covered by declared permissions. (severity=HIGH, confidence=0.75)
LP4: Permission 'bash' is declared but no corresponding code capability (shell) was detected. (severity=LOW, confidence=0.65)

```

### Unit Test State Construction

For testing purposes, you can construct a minimal state object:

```python
state = {
    "manifest": {"name": "demo", "permissions": ["read"], "triggers": []},
    "file_cache": {
        "scripts/agent.py": "import httpx\nimport subprocess\nhttpx.get('http://example.com')\nsubprocess.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"],
}
findings = mcp_least_privilege.node(state)["findings"]
assert any(f.rule_id == "LP1" for f in findings)

```

## Summary

- The **MCP least privilege analyzer** in [`src/skillspector/nodes/analyzers/mcp_least_privilege.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/mcp_least_privilege.py) performs static analysis comparing [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) manifests against actual code capabilities.
- It detects six capability categories (**shell**, **network**, **file_read**, **file_write**, **env**, **mcp**) using regex patterns defined in `_CAPABILITY_PATTERNS`.
- Four finding types (LP1-LP4) identify under-declared capabilities, wildcard permissions, missing permissions, and over-declared permissions respectively.
- Each finding includes severity levels (HIGH for LP1, MEDIUM for LP2/LP3, LOW for LP4) and confidence scores clamped between 0 and 1.
- The analyzer skips documentation-only skills and normalizes permission strings via `_PERM_TO_CAPABILITY` for consistent comparison.

## Frequently Asked Questions

### What is MCP least privilege detection in SkillSpector?

MCP least privilege detection is a static analysis feature in NVIDIA SkillSpector that validates whether Model Context Protocol skills declare only the permissions they actually use. It compares the `permissions` list in [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) against capabilities detected in executable code using regex patterns, generating findings when discrepancies exist.

### How does SkillSpector detect capabilities in code?

SkillSpector uses the `_detect_capabilities` function in [`mcp_least_privilege.py`](https://github.com/NVIDIA/SkillSpector/blob/main/mcp_least_privilege.py) to scan executable files for regex patterns that match imports and function calls associated with six categories: shell execution, network requests, file operations, environment access, and MCP interactions. These patterns are defined in the `_CAPABILITY_PATTERNS` dictionary.

### What do the LP1-LP4 finding codes mean?

LP1 indicates under-declared capabilities (code uses permissions not listed in the manifest), LP2 flags wildcard permissions like `*` or `all`, LP3 identifies missing permission declarations when capabilities exist, and LP4 marks over-declared permissions (listed in manifest but unused in code). Each has distinct severity and confidence levels according to the NIST-style least privilege specification.

### Which file types does the MCP least privilege analyzer examine?

The analyzer examines executable files based on extensions defined in the component metadata, including `.py`, `.sh`, `.bash`, `.zsh`, `.js`, `.ts`, `.rb`, `.go`, `.rs`, and `.pl`. It reads the raw text of these files and applies regex patterns to detect capabilities, while skipping non-executable documentation files.