SkillSpector Architecture: Inside NVIDIA's LangGraph Security Analysis Pipeline

SkillSpector implements a deterministic, state-machine workflow using LangGraph to process skills through sequential nodes—input resolution, context building, static analysis, LLM meta-analysis, and reporting—ensuring consistent and extensible security scanning.

The SkillSpector architecture powers NVIDIA's open-source security scanner for AI skills (directory structures, zip archives, URLs, or standalone SKILL.md files). Built on LangGraph, the system orchestrates a shared state across specialized nodes, enabling both high-performance static analysis and optional semantic evaluation via LLM providers like OpenAI, Anthropic, and NVIDIA Build.

Core Architectural Components

The codebase organizes functionality into distinct layers, each handling a specific phase of the security analysis lifecycle.

Entry Point and Workflow Orchestration

The CLI entry point in src/skillspector/cli.py parses command-line arguments and constructs the workflow graph. It delegates execution to the compiled LangGraph instance defined in src/skillspector/graph.py, which declares the StateGraph using the SkillspectorState TypedDict schema from src/skillspector/state.py. This state object holds mutable shared data including the input path, file caches, AST caches, component metadata, findings, and final risk scores.

Input Handling and Context Building

The pipeline begins with two foundational nodes:

  • resolve_input (src/skillspector/nodes/resolve_input.py): Normalizes user input by handling git clones, zip extractions, local path validation, and URL downloads, storing the resolved path in skill_path.
  • build_context (src/skillspector/nodes/build_context.py): traverses the skill tree to populate file_cache and ast_cache, extracts manifest files, and records component metadata necessary for downstream analysis.

Static and LLM-Based Analysis Nodes

The analysis layer comprises 20+ pure-Python analyzers located in src/skillspector/nodes/analyzers/*.py. These modules detect specific patterns including prompt injection, supply-chain risks, YARA matches, AST behavioral analysis, and MCP protocol violations. Each analyzer returns an AnalyzerNodeResponse containing Finding instances defined in src/skillspector/models.py.

Following static analysis, the meta_analyzer node (src/skillspector/nodes/meta_analyzer.py) optionally invokes LLM providers to re-evaluate findings, filter false positives, and generate human-readable explanations. Provider implementations reside in src/skillspector/providers/, while src/skillspector/llm_analyzer_base.py supplies common prompt-building and response-parsing logic.

Reporting and Data Models

The final report node (src/skillspector/nodes/report.py) consumes the completed state—including risk_score, risk_severity, risk_recommendation, and SARIF-compatible structures from src/skillspector/sarif_models.py—to generate outputs in terminal, JSON, Markdown, or SARIF formats.

Execution Flow in the StateGraph

The workflow graph defined in src/skillspector/graph.py executes deterministically through six sequential stages:

  1. START → resolve_input: Ingest and normalize the skill source.
  2. resolve_input → build_context: Gather files, build AST caches, and extract component metadata.
  3. build_context → analyzers: Execute all static-pattern analyzers in parallel; each appends findings to the shared state.
  4. analyzers → meta_analyzer: Perform optional LLM semantic analysis on accumulated findings.
  5. meta_analyzer → report: Calculate risk scores and generate formatted output.
  6. report → END: Return the final SkillspectorState to the CLI or API caller.

All nodes function as pure functions accepting the current SkillspectorState and returning updated state fragments. The graph compiles once via create_graph() and reuses the compiled instance for every scan, ensuring thread safety and deterministic ordering.

Practical Implementation Examples

Using the Python API Directly

You can invoke the compiled workflow programmatically using the same graph the CLI runs:

from skillspector.graph import graph

result = graph.invoke(
    {
        "input_path": "/path/to/skill",
        "output_format": "json",
        "use_llm": True,
    }
)

print("Risk score:", result["risk_score"])
print("Recommendation:", result["risk_recommendation"])
for finding in result["filtered_findings"]:
    print(f"[{finding.severity}] {finding.id}: {finding.message}")

Command-Line Execution

Typical CLI usage supports multiple input formats and output options:


# Scan local directory with full analysis

skillspector scan ./my-skill/

# Scan git repository, output SARIF for CI integration

skillspector scan https://github.com/user/bad-skill.git \
    --format sarif --output report.sarif

# Static-only scan without LLM costs

skillspector scan ./my-skill/ --no-llm

Extending with Custom Analyzers

Add a new security check by creating src/skillspector/nodes/analyzers/static_my_pattern.py:

from skillspector.models import Finding
from skillspector.state import SkillspectorState, AnalyzerNodeResponse

def node(state: SkillspectorState) -> AnalyzerNodeResponse:
    findings = []
    for path, code in state["file_cache"].items():
        if "dangerous()" in code:
            findings.append(
                Finding(
                    id="MY01",
                    category="custom",
                    severity="HIGH",
                    message="Usage of dangerous() detected",
                    location={"file": path, "start_line": 1},
                )
            )
    return {"findings": findings}

Register the analyzer in src/skillspector/nodes/analyzers/__init__.py by adding it to ANALYZER_NODE_IDS and ANALYZER_NODES. The graph automatically includes it in the parallel execution phase following build_context.

Key Source Files Reference

Summary

  • SkillSpector uses LangGraph to orchestrate a deterministic, stateful pipeline for security analysis of AI skills.
  • The architecture separates concerns into input resolution, context building, parallel static analysis, LLM meta-analysis, and reporting nodes.
  • All nodes in src/skillspector/nodes/ operate on a shared SkillspectorState defined in src/skillspector/state.py, ensuring type safety and consistency.
  • The system supports 20+ static analyzers in src/skillspector/nodes/analyzers/ that run in parallel, followed by optional LLM evaluation via src/skillspector/nodes/meta_analyzer.py.
  • Extending the tool requires only adding a pure function analyzer and registering it in the analyzer __init__.py module.

Frequently Asked Questions

What makes LangGraph suitable for SkillSpector's security scanning workflow?

LangGraph provides deterministic state management and explicit node dependencies, which are critical for security auditing. According to the NVIDIA/SkillSpector source code, the graph compiles once via create_graph() in src/skillspector/graph.py and reuses the compiled instance, ensuring that every scan follows the exact same execution path (START → resolve_input → build_context → analyzers → meta_analyzer → report → END) regardless of input source.

Can SkillSpector analyze skills without LLM API access?

Yes. The architecture treats LLM analysis as an optional layer controlled by the use_llm state parameter. Setting --no-llm in the CLI or use_llm: False in the API bypasses the meta_analyzer node entirely, executing only the static analyzers in src/skillspector/nodes/analyzers/*.py. This enables fast, cost-free scans suitable for CI/CD pipelines.

How does SkillSpector handle different input formats like URLs and zip files?

The resolve_input node in src/skillspector/nodes/resolve_input.py normalizes all inputs—local directories, git repository URLs, zip archives, or single SKILL.md files—into a consistent directory structure stored in skill_path. This abstraction allows downstream nodes to operate uniformly on the file cache without concerning themselves with the original input format.

What is the performance impact of running 20+ analyzers in parallel?

The graph executes all static analyzers concurrently as parallel branches from the build_context node, merging results back into the shared state before the sequential meta_analyzer phase. Because each analyzer in src/skillspector/nodes/analyzers/ is a pure function operating on the immutable file cache, the parallel execution scales efficiently with available CPU cores without blocking the main thread.

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 →