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

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. 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 for convenient direct import.

All data flows through SkillspectorState, a TypedDict defined in 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): Normalizes local directories, zip files, git URLs, or single 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): 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): 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): 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:

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, the library automatically handles:

  • Local filesystem directories
  • Zip file archives
  • Remote git URLs
  • Single 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:

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:

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:

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) to access the full LangGraph workflow defined in 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.
  • Access structured results via risk_score, filtered_findings, and report_body keys in the returned state dictionary processed by 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. 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 automatically normalizes local directories, zip files, git URLs, and single 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →