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 of Finding objects containing analyzer details, location information, severity levels, and remediation suggestions.
  • report_body – Human-readable report text formatted according to your output_format specification.
  • 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 graph from skillspector.graph to access the LangGraph workflow constructed in src/skillspector/graph.py.
  • Construct a state dictionary matching SkillspectorState with required keys: skill_path, output_format, and use_llm.
  • Invoke the scan via graph.invoke(state, config=...) to execute the full pipeline from input handling to reporting.
  • Configure LLM providers via SKILLSPECTOR_PROVIDER and SKILLSPECTOR_MODEL environment variables, or pass credentials through the state’s model_config field.
  • Access structured results via findings, sarif_report, and risk_score fields in the returned dictionary, with data models defined in src/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:

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 →