# How MCP Least Privilege Detection Works in NVIDIA SkillSpector

> Discover how MCP least privilege detection in NVIDIA SkillSpector works. It compares declared vs. actual permissions to find under-declared, over-declared, wildcard, or missing privileges.

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

---

**MCP least privilege detection works by comparing declared permissions in a skill's [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) manifest against actual capabilities extracted from source code using regex patterns, emitting four rule-based findings (LP1-LP4) that identify under-declared, over-declared, wildcard, or missing permissions.**

The **MCP least privilege detection** mechanism in NVIDIA SkillSpector ensures that Model Context Protocol (MCP) skills operate with minimal required permissions. Implemented as the `mcp_least_privilege` analyzer node, this static analysis tool bridges the gap between manifest declarations and real code behavior without requiring LLM calls.

## How the MCP Least Privilege Analyzer Works

The analyzer operates as a stateless pipeline node that consumes pre-built context from upstream components. It systematically cross-references declared permissions against extracted code capabilities to identify security gaps.

### Required State and Inputs

The analyzer requires three specific state keys supplied by earlier pipeline nodes (such as *build_context*):

- **`manifest`**: Parsed front-matter of [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) containing the `permissions` list
- **`file_cache`**: Dictionary mapping file paths to their string contents
- **`component_metadata`**: List of dictionaries with per-file metadata including `path` and `executable` flags

These inputs are read directly in [`src/skillspector/nodes/analyzers/mcp_least_privilege.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/mcp_least_privilege.py) with no LLM inference performed during analysis.

### Capability Detection via Regex Patterns

The `_detect_capabilities(content)` function scans executable files once using regular expressions to identify six capability categories:

- **shell**: `subprocess`, `Popen`, `curl`, `chmod`
- **network**: `httpx`, `requests`, `urllib`, `socket\.connect`
- **file_read**: `open(..., "r")`, `.read_text()`, `os\.listdir`
- **file_write**: `open(..., "w")`, `.write_text()`, `shutil\.copy`
- **env**: `os\.environ`, `os\.getenv`, `process\.env`
- **mcp**: `create_session`, `MCPClient`, `mcp\.client`

Test files (matching `test_*.py` or `*_test.py`) are tracked separately. Capabilities appearing only in test files receive a confidence score of **0.55** versus **0.75** for production code.

### Permission-to-Category Mapping

Declared permissions are normalized to lowercase and mapped to capability categories via `_map_permissions_to_categories(permissions)`. The internal `_PERM_TO_CAPABILITY` table maps keywords like `bash`, `shell`, or `terminal` to the **shell** category, while `network`, `http`, or `fetch` map to **network**.

Wildcard entries (`"*", "all", "full", "any"`) are detected by `_has_wildcard()` in [`src/skillspector/nodes/analyzers/mcp_least_privilege.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/mcp_least_privilege.py) and trigger the LP2 rule.

### The Four Detection Rules (LP1-LP4)

The `node(state)` function implements four distinct findings through systematic evaluation:

1. **LP1 – Underdeclared Capability**: Emitted when code contains capabilities not covered by declared permissions. Confidence is **0.75** for production code or **0.55** if the capability appears only in test files.

2. **LP2 – Wildcard Permission**: Triggered when any declared permission is a wildcard entry. This indicates potential over-privilege.

3. **LP3 – Missing Permission Declaration**: Fired when no permissions are declared in the manifest but capabilities are detected in the code.

4. **LP4 – Overdeclared Permission**: Emitted when a declared permission maps to a capability completely absent from the source code. Fixed confidence of **0.65**.

## Rule Interaction and Suppression Logic

The analyzer implements suppression logic to prevent redundant findings according to the NVIDIA SkillSpector design specification:

- **Wildcard + under-declared capabilities**: LP2 is emitted; LP1 is suppressed
- **No permissions declared + capabilities detected**: LP3 is emitted; LP1 and LP4 are suppressed
- **Normal permission list with gaps**: LP1 and/or LP4 are emitted individually
- **Documentation-only skills**: No analysis performed; no findings emitted

This interaction matrix is documented in [`docs/B.3.1-mcp-least-privilege.md`](https://github.com/NVIDIA/SkillSpector/blob/main/docs/B.3.1-mcp-least-privilege.md).

## Implementation Examples in SkillSpector

To invoke the analyzer directly on a minimal state:

```python
from skillspector.nodes.analyzers import mcp_least_privilege

state = {
    "manifest": {"permissions": ["read"], "triggers": []},
    "file_cache": {"script.py": "import httpx\nhttpx.get('https://example.com')"},
    "component_metadata": [
        {"path": "script.py", "type": "python", "executable": True, "lines": 2, "size_bytes": 50}
    ],
    "has_executable_scripts": True,
    "components": ["script.py"],
}

result = mcp_least_privilege.node(state)
for finding in result["findings"]:
    print(f"{finding.rule_id}: {finding.message}")

```

To detect wildcard permissions (LP2):

```python
state = {
    "manifest": {"permissions": ["*"]},
    "file_cache": {"agent.py": "print('hello')"},
    "component_metadata": [{"path": "agent.py", "executable": True}],
    "has_executable_scripts": True,
    "components": ["agent.py"],
}

findings = mcp_least_privilege.node(state)["findings"]
lp2 = next(f for f in findings if f.rule_id == "LP2")
print(lp2.message)  # Output: Permission list contains a wildcard entry...

```

## Summary

- **MCP least privilege detection** compares [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) manifests against actual code capabilities using static regex analysis in [`src/skillspector/nodes/analyzers/mcp_least_privilege.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/mcp_least_privilege.py)
- The analyzer categorizes code into six capability types: **shell**, **network**, **file_read**, **file_write**, **env**, and **mcp**
- Four rules (LP1-LP4) identify under-declared, wildcard, missing, and over-declared permissions with specific confidence scoring
- Test-only capabilities receive reduced confidence scores (0.55 vs 0.75) to reflect lower risk profiles
- Rule suppression logic prevents redundant findings when wildcards or missing declarations are present

## Frequently Asked Questions

### What triggers an LP1 finding in MCP least privilege detection?

An LP1 (Underdeclared Capability) finding is triggered when `_detect_capabilities()` identifies a capability category in executable code that is not covered by the corresponding permission declaration in the manifest. The finding receives a confidence score of **0.75** for capabilities found in production code, or **0.55** if the capability appears only in test files.

### How does the analyzer handle test files differently?

The analyzer tracks capabilities found in files matching `test_*.py` or `*_test.py` patterns separately from production code. When a capability appears exclusively in test files, the confidence score for LP1 findings is reduced to **0.55** compared to **0.75** for normal files, reflecting the lower risk of test-only code paths.

### What is the difference between LP2 and LP3 findings?

LP2 (Wildcard Permission) is emitted when the manifest contains wildcard entries like `"*"`, `"all"`, `"full"`, or `"any"`, indicating potentially over-privileged declarations. LP3 (Missing Permission Declaration) is emitted when the manifest contains no permissions at all but capabilities are detected in the code. When LP3 is triggered, LP1 and LP4 findings are suppressed to avoid redundancy.

### Where is the confidence scoring logic defined?

The confidence scoring logic is implemented in [`src/skillspector/nodes/analyzers/mcp_least_privilege.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/mcp_least_privilege.py) within the `node()` function. The analyzer assigns **0.75** confidence to LP1 findings in normal files, **0.55** to LP1 findings for test-only capabilities, and **0.65** to LP4 (Overdeclared Permission) findings. These values are hardcoded constants in the rule evaluation logic.