How to Use the SkillSpector Python API for Programmatic Security Scanning
SkillSpector exposes a compiled LangGraph workflow via skillspector.graph that you invoke with a state dictionary containing skill_path, output_format, and use_llm to return findings, reports, and risk scores.
NVIDIA/SkillSpector provides a programmatic interface built on LangGraph that allows you to embed security scanning directly into Python applications. Instead of shelling out to the command line, you can import the compiled graph object and invoke it with a typed state dictionary. This guide explains how to use the SkillSpector Python API to analyze skills programmatically, based on the actual implementation in the NVIDIA/SkillSpector repository.
Understanding the SkillSpector Architecture
LangGraph Workflow
The core of the SkillSpector Python API is a compiled LangGraph workflow defined in src/skillspector/graph.py. The graph object (exposed via skillspector.__init__) is a standard LangChain runnable that chains together nodes for input handling, context building, static analyzers, meta-analysis, and report generation.
State Schema
All data passing between nodes adheres to SkillspectorState, a TypedDict defined in src/skillspector/state.py. This schema dictates the required and optional fields for scan execution, ensuring type safety across the workflow.
CLI Reference Implementation
The command-line interface in src/skillspector/cli.py serves as a thin wrapper that demonstrates the programmatic pattern. It prepares the initial state dictionary and calls graph.invoke(), making it the definitive reference for embedding the scanner in your own code.
Importing the Graph and Building State
To execute a scan programmatically, import the compiled graph from skillspector.graph and construct a state dictionary matching the SkillspectorState schema. Required keys include skill_path (local path or remote URL), output_format (terminal, json, markdown, or sarif), and use_llm (boolean).
from pathlib import Path
from skillspector.graph import graph
# Build the minimal state required for a scan
state = {
"skill_path": str(Path("./my_skill").resolve()),
"output_format": "terminal",
"use_llm": True,
# Optional: specify custom YARA rules directory
# "yara_rules_dir": "/path/to/custom/rules",
}
# Optional: add tracing configuration for observability
trace_cfg = {
"run_name": "my-skill-scan",
"tags": ["skillspector", "ci-cd"],
"metadata": {"skill_path": state["skill_path"]},
}
# Execute the workflow
result = graph.invoke(state, config=trace_cfg)
# Access results
print(result.get("report_body", ""))
for finding in result.get("findings", []):
print(f"- [{finding.severity}] {finding.title}")
The graph.invoke(state, config=...) method call mirrors the CLI implementation in src/skillspector/cli.py, executing the full pipeline and returning a plain dictionary containing the final fields.
Configuring LLM Providers
The SkillSpector Python API supports multiple LLM providers through environment variables or explicit state configuration. The provider protocol is defined in src/skillspector/providers/base.py, with resolution handled by the internal providers.registry.
Set the provider and model via environment variables before invocation:
import os
from skillspector.graph import graph
os.environ["SKILLSPECTOR_PROVIDER"] = "openai"
os.environ["SKILLSPECTOR_MODEL"] = "gpt-4o-mini"
state = {
"skill_path": "https://github.com/example/malicious-skill",
"output_format": "json",
"use_llm": True,
}
result = graph.invoke(state)
print(result["report_body"])
Alternatively, you can pass a model_config entry in the state dictionary to override environment settings for specific invocations.
Running Static-Only Scans
You can disable LLM-based analysis for faster execution or air-gapped environments by setting use_llm: False. This skips the meta_analyzer and all semantic analyzers, leaving only static YARA and pattern checks.
from skillspector.graph import graph
state = {
"skill_path": "./some_skill",
"output_format": "sarif",
"use_llm": False, # Disable LLM nodes
}
result = graph.invoke(state)
# SARIF JSON is available directly in the result
sarif_output = result["sarif_report"]
print(sarif_output)
This programmatic behavior mirrors the --no-llm CLI flag handled in src/skillspector/cli.py.
Accessing Scan Results
The dictionary returned by graph.invoke() contains structured data defined in src/skillspector/models.py. Key output fields include:
findings– A list ofFindingobjects containing analyzer details, location information, severity levels, and remediation suggestions.report_body– Human-readable report text formatted according to youroutput_formatspecification.sarif_report– Static Analysis Results Interchange Format (SARIF) JSON for integration with security dashboards.risk_score– Aggregated numeric risk metric calculated by the meta-analysis node.
Because the graph is a standard LangChain runnable, you can also pass a custom RunnableConfig for tracing, metadata logging, or callback handling during invocation.
Summary
- Import the compiled
graphfromskillspector.graphto access the LangGraph workflow constructed insrc/skillspector/graph.py. - Construct a state dictionary matching
SkillspectorStatewith required keys:skill_path,output_format, anduse_llm. - Invoke the scan via
graph.invoke(state, config=...)to execute the full pipeline from input handling to reporting. - Configure LLM providers via
SKILLSPECTOR_PROVIDERandSKILLSPECTOR_MODELenvironment variables, or pass credentials through the state’smodel_configfield. - Access structured results via
findings,sarif_report, andrisk_scorefields in the returned dictionary, with data models defined insrc/skillspector/models.py.
Frequently Asked Questions
What is the difference between create_graph and the graph object?
create_graph is a factory function exposed in skillspector.__init__ that constructs a fresh StateGraph instance, while graph is the pre-compiled, ready-to-use runnable that the package exports for immediate invocation. Use graph for standard programmatic access; use create_graph only if you need to modify the node topology before compilation.
How do I configure authentication for private LLM providers?
Authentication is handled via standard provider-specific environment variables (e.g., OPENAI_API_KEY, ANTHROPIC_API_KEY) or by passing a model_config dictionary in the state with credential fields. The provider protocol in src/skillspector/providers/base.py defines the interface for credential resolution, allowing the registry to instantiate the correct ChatModelProvider with your credentials.
Can I run SkillSpector without an LLM for faster static analysis?
Yes. Set use_llm: False in your state dictionary before calling graph.invoke(). This bypasses the LLM-dependent meta-analyzer and semantic analyzers, executing only the static YARA and pattern-matching nodes. This is equivalent to the --no-llm CLI flag and significantly reduces execution time.
What format does the findings field return?
The findings field contains a list of Finding objects as defined in src/skillspector/models.py. Each object includes attributes such as analyzer (string identifying the detection rule), location (file path and line numbers), severity (categorical risk level), title (short description), and description (detailed explanation with remediation guidance). These objects are JSON-serializable and can be directly ingested by security orchestration platforms.
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 →