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 singleSKILL.mdfiles into a working path. It stores the resolved path inskill_pathand tracks any temporary directories intemp_dir_for_cleanupfor automatic removal. -
build_context(src/skillspector/nodes/build_context.py): Traverses the skill directory to populatecomponents,file_cache, andast_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 appendsFindingobjects tostate["findings"]. -
meta_analyzer(src/skillspector/nodes/meta_analyzer.py): Optionally invokes an LLM provider (OpenAI, Anthropic, or NVIDIAnv_build) to refine findings, filter false positives, and enrich explanations. This node respects theuse_llmboolean flag in your state. -
report(src/skillspector/nodes/report.py): Computes the risk score, determines severity, and formats the output according to youroutput_formatsetting (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.mdmanifest 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_PROVIDERto select betweenopenai,anthropic, ornv_build - Provide provider-specific API keys (e.g.,
OPENAI_API_KEY) as required by the client implementations insrc/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 outputjson: Machine-parseable structured datamarkdown: Documentation-friendly formatted reportsarif: 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 fromsrc/skillspector/__init__.py) to access the full LangGraph workflow defined insrc/skillspector/graph.py. - Provide a state dictionary with
input_path,output_format, anduse_llmkeys; the system handles heterogeneous inputs and temporary file cleanup automatically viasrc/skillspector/nodes/resolve_input.py. - Access structured results via
risk_score,filtered_findings, andreport_bodykeys in the returned state dictionary processed bysrc/skillspector/nodes/report.py. - Configure LLM providers using the
SKILLSPECTOR_PROVIDERenvironment variable and provider-specific API keys when enabling themeta_analyzernode.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →