# How to Use SkillSpector as a Python Library: Programmatic Security Analysis

> Learn to use SkillSpector as a Python library for programmatic security analysis. Import skillspector.graph and call invoke with your input path to run the security pipeline.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: how-to-guide
- Published: 2026-06-16

---

**Import `skillspector.graph` and call `graph.invoke(state)` with a dictionary containing your `input_path` to execute the full LangGraph-based security pipeline without invoking the CLI.**

NVIDIA's SkillSpector is an open-source security scanner for AI skills packaged as a standard Python library (`skillspector`). When you use SkillSpector as a Python library, you embed its compiled LangGraph workflow directly into your applications, CI pipelines, or automation scripts by manipulating a shared state dictionary that flows through analysis nodes.

## Core Architecture and Components

The library centers on a compiled LangGraph object that orchestrates a multi-stage analysis pipeline. Understanding these components helps you configure the state dictionary correctly.

### The Compiled Graph and State Management

At the heart of the system is the compiled graph defined in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py). The `create_graph` function constructs a `StateGraph` that stitches together nodes including `resolve_input`, `build_context`, static analyzers, `meta_analyzer`, and `report`. This compiled graph is re-exported as `skillspector.graph` from [`src/skillspector/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/__init__.py) for convenient direct import.

All data flows through `SkillspectorState`, a TypedDict defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py). This state object carries the input path, file caches, AST representations, discovered findings, and final report data between nodes.

### Analysis Pipeline Nodes

When you invoke `graph.invoke()`, the workflow executes these stages sequentially:

- **`resolve_input`** ([`src/skillspector/nodes/resolve_input.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/resolve_input.py)): Normalizes local directories, zip files, git URLs, or single [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) files into a working path. It stores the resolved path in `skill_path` and tracks any temporary directories in `temp_dir_for_cleanup` for automatic removal.

- **`build_context`** ([`src/skillspector/nodes/build_context.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/build_context.py)): Traverses the skill directory to populate `components`, `file_cache`, and `ast_cache`. It extracts the skill manifest and flags executable scripts, which factor into the final risk score.

- **Static Analyzers** (`src/skillspector/nodes/analyzers/*`): A suite of 64 pattern detectors (regex, AST, YARA, OSV lookup) execute against the cached files. Each analyzer appends `Finding` objects to `state["findings"]`.

- **`meta_analyzer`** ([`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py)): Optionally invokes an LLM provider (OpenAI, Anthropic, or NVIDIA `nv_build`) to refine findings, filter false positives, and enrich explanations. This node respects the `use_llm` boolean flag in your state.

- **`report`** ([`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py)): Computes the risk score, determines severity, and formats the output according to your `output_format` setting (terminal, JSON, Markdown, or SARIF).

## Basic Integration Pattern

### Importing and Executing the Graph

To use SkillSpector as a Python library, import the pre-compiled graph and invoke it with a partial state dictionary:

```python
from skillspector import graph

# Prepare initial state - only required keys need specification

initial_state = {
    "input_path": "/path/to/your/skill",   # Supports dirs, zips, URLs, or SKILL.md

    "output_format": "json",               # Options: terminal | json | markdown | sarif

    "use_llm": True,                       # Set False for static-only analysis

}

# Execute the full pipeline

result = graph.invoke(initial_state)

# Access structured results

print(f"Risk Score: {result['risk_score']}/100")
print(f"Severity: {result['risk_severity']}")
print(f"Recommendation: {result['risk_recommendation']}")

# Iterate through processed findings

for finding in result["filtered_findings"]:
    print(f"[{finding['severity']}] {finding['rule_id']}: {finding['message']}")

# Access formatted report output

print(result["report_body"])

```

The `graph.invoke()` method accepts any dictionary matching the `SkillspectorState` schema. Missing fields receive sensible defaults, while the returned dictionary contains the complete final state including `filtered_findings` and `report_body`.

## Configuration and Environment Setup

### Input Path Handling

The `input_path` key supports heterogeneous inputs. According to [`src/skillspector/nodes/resolve_input.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/resolve_input.py), the library automatically handles:

- Local filesystem directories
- Zip file archives
- Remote git URLs
- Single [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) manifest files

When processing remote or compressed inputs, the node creates a temporary working directory. The path is stored in `temp_dir_for_cleanup`, and the graph automatically removes this directory after execution completes—no manual cleanup required.

### LLM Provider Configuration

To enable semantic analysis in the `meta_analyzer` node, set `use_llm: True` and configure your provider via environment variables:

- Set `SKILLSPECTOR_PROVIDER` to select between `openai`, `anthropic`, or `nv_build`
- Provide provider-specific API keys (e.g., `OPENAI_API_KEY`) as required by the client implementations in `src/skillspector/providers/`

Each provider module handles credential management and model selection independently.

### Output Format Options

The `output_format` parameter determines how `report_body` is rendered:

- **`terminal`**: Human-readable colored console output
- **`json`**: Machine-parseable structured data
- **`markdown`**: Documentation-friendly formatted report
- **`sarif`**: Static Analysis Results Interchange Format for security tool integration

While `result["report_body"]` contains the formatted string, the raw dictionary structure remains available for programmatic consumption.

## Practical Implementation Examples

### Static-Only Security Scan

For fast analysis without API costs or latency:

```python
from skillspector import graph

state = {
    "input_path": "https://github.com/example/skill-repo",
    "output_format": "terminal",
    "use_llm": False
}

result = graph.invoke(state)
print(result["report_body"])

```

### LLM-Enhanced Analysis with Environment Variables

Load credentials securely while using SkillSpector as a Python library:

```python
import os
from pathlib import Path
from dotenv import load_dotenv
from skillspector import graph

# Load credentials from .env file

load_dotenv(Path(__file__).with_name(".env"))

state = {
    "input_path": "./my-skill/",
    "output_format": "markdown",
    "use_llm": True
}

result = graph.invoke(state)
Path("security_report.md").write_text(result["report_body"])

```

### CI/CD Pipeline Integration

Embed risk scoring into automated quality gates:

```python
from skillspector import graph
import json
import logging

def evaluate_skill_security(path: str) -> dict:
    """Run SkillSpector analysis and return risk metrics."""
    result = graph.invoke({
        "input_path": path,
        "output_format": "json",
        "use_llm": True
    })
    return {
        "score": result["risk_score"],
        "severity": result["risk_severity"],
        "findings": result["filtered_findings"]
    }

if __name__ == "__main__":
    metrics = evaluate_skill_security("./candidate-skill/")
    logging.info("Skill risk: %s (%d/100)", metrics["severity"], metrics["score"])
    
    # Fail build on high risk

    if metrics["score"] > 70:
        raise SystemExit("High risk skill detected")

```

## Summary

- **Import the compiled graph** from `skillspector` (re-exported from [`src/skillspector/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/__init__.py)) to access the full LangGraph workflow defined in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py).
- **Provide a state dictionary** with `input_path`, `output_format`, and `use_llm` keys; the system handles heterogeneous inputs and temporary file cleanup automatically via [`src/skillspector/nodes/resolve_input.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/resolve_input.py).
- **Access structured results** via `risk_score`, `filtered_findings`, and `report_body` keys in the returned state dictionary processed by [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py).
- **Configure LLM providers** using the `SKILLSPECTOR_PROVIDER` environment variable and provider-specific API keys when enabling the `meta_analyzer` node.

## Frequently Asked Questions

### What Python objects does SkillSpector expose for library usage?

SkillSpector exposes the compiled LangGraph object as `skillspector.graph` and the factory function `create_graph` from [`src/skillspector/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/__init__.py). The graph is a pre-compiled `StateGraph` instance ready for immediate invocation via `graph.invoke()`.

### How does SkillSpector handle different input types when used programmatically?

The `resolve_input` node in [`src/skillspector/nodes/resolve_input.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/resolve_input.py) automatically normalizes local directories, zip files, git URLs, and single [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/SKILL.md) files into a standard working path. Temporary directories created for remote or compressed inputs are tracked in `state["temp_dir_for_cleanup"]` and automatically removed after the graph completes execution.

### Can I run SkillSpector without LLM API calls to reduce costs?

Yes. Set `use_llm: False` in your initial state dictionary to skip the `meta_analyzer` node. This executes only the 64 static pattern detectors in `src/skillspector/nodes/analyzers/*`, significantly reducing execution time and eliminating API costs while still detecting security issues through regex, AST, and YARA rules.

### What is the difference between `result["filtered_findings"]` and the raw findings list?

The `filtered_findings` key contains findings that have been processed by the `meta_analyzer` node (when LLM is enabled) to remove false positives and enrich explanations, or the raw static findings if LLM is disabled. The final returned state prioritizes this filtered set for consumption, while the raw list exists only during intermediate graph execution.