# SkillSpector Architecture: Inside NVIDIA's LangGraph Security Analysis Pipeline

> Explore SkillSpector architecture, NVIDIA's LangGraph security pipeline. Discover its state-machine workflow for consistent, extensible skill analysis from input to reporting.

- Repository: [NVIDIA Corporation/SkillSpector](https://github.com/NVIDIA/SkillSpector)
- Tags: architecture
- Published: 2026-06-23

---

**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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py), which declares the `StateGraph` using the `SkillspectorState` **TypedDict** schema from [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py).

Following static analysis, the **`meta_analyzer`** node ([`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/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:

```python
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:

```bash

# 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`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/static_my_pattern.py):

```python
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`](https://github.com/NVIDIA/SkillSpector/blob/main/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

- **[`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py)**: Compiles the LangGraph `StateGraph` and wires node dependencies.
- **[`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py)**: Defines the `SkillspectorState` TypedDict for shared mutable state.
- **[`src/skillspector/nodes/resolve_input.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/resolve_input.py)**: Handles source normalization for directories, zips, and URLs.
- **[`src/skillspector/nodes/build_context.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/build_context.py)**: Builds file and AST caches, extracts metadata.
- **[`src/skillspector/nodes/analyzers/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/__init__.py)**: Registry for all analyzer nodes (static and LLM-based).
- **[`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py)**: Implements LLM-based semantic re-evaluation of findings.
- **[`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py)**: Generates final risk scores and formatted reports.
- **`src/skillspector/providers/`**: LLM backend implementations (OpenAI, Anthropic, NVIDIA Build).
- **[`src/skillspector/models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/models.py) & [`sarif_models.py`](https://github.com/NVIDIA/SkillSpector/blob/main/sarif_models.py)**: Core data structures (`Finding`, `Severity`, `Location`) and SARIF output types.
- **[`src/skillspector/cli.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/cli.py)**: Command-line interface and workflow entry point.

## 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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py).
- Extending the tool requires only adding a pure function analyzer and registering it in the analyzer [`__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/__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`](https://github.com/NVIDIA/SkillSpector/blob/main/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`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/resolve_input.py) normalizes all inputs—local directories, git repository URLs, zip archives, or single [`SKILL.md`](https://github.com/NVIDIA/SkillSpector/blob/main/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.