# How the SkillSpector Two-Stage Detection Pipeline Works: Static Analysis + LLM Meta-Analysis

> Discover how SkillSpector's two-stage detection pipeline enhances vulnerability analysis by merging static analysis with LLM meta-analysis for accurate results and actionable remediation.

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

---

**SkillSpector's two-stage detection pipeline combines high-recall static analysis with LLM-driven meta-analysis to filter false positives and enrich confirmed vulnerabilities with context-aware remediation steps.**

NVIDIA's SkillSpector is an open-source security scanner for AI skill bundles that implements a novel two-stage detection pipeline to balance performance with accuracy. The architecture separates coarse-grained detection from precision filtering, using LangGraph to orchestrate the workflow across distinct static and AI-powered phases. This design minimizes expensive LLM calls while maintaining deep contextual analysis of potential security issues.

## Stage 1: Static Analysis for High-Recall Detection

The first phase employs a collection of **per-file analyzers** that scan source code, configuration files, and other artifacts within the skill bundle. These analyzers operate in parallel across the entire codebase to maximize coverage and recall.

In [`src/skillspector/nodes/analyzers/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/__init__.py), the system defines `ANALYZER_NODE_IDS` and `ANALYZER_NODES`, which map to specific detection implementations:

- **YARA rule matching** ([`static_yara.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_yara.py)) – Pattern-based detection for known malicious signatures
- **Semantic intent analysis** ([`semantic_developer_intent.py`](https://github.com/NVIDIA/SkillSpector/blob/main/semantic_developer_intent.py)) – Behavioral AST analysis to identify suspicious coding patterns
- **Taint-tracking engines** – Data-flow analysis to trace untrusted inputs through the codebase

Each analyzer emits **raw findings** containing rule IDs, file locations, severity scores, and metadata. These findings represent a superset of potential issues—designed for high recall rather than precision. The results are stored as `Finding` objects in the shared state defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py).

## Stage 2: LLM Meta-Analysis for Precision Filtering

The second phase introduces the **Meta-Analyzer** ([`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py)), which transforms raw static findings into validated, actionable security intelligence. Rather than analyzing individual findings in isolation, the meta-analyzer performs **per-file LLM calls** that consider the complete context of the file alongside all detected issues.

For each file that generated at least one finding, the Meta-Analyzer:

1. Constructs a structured prompt containing file metadata, source content, and the complete list of raw findings
2. Invokes the configured LLM (e.g., Gemini 1.5 Pro) through the `LLMMetaAnalyzer` class
3. Receives a structured `MetaAnalyzerResult` containing filtered true positives

This stage **enriches** confirmed vulnerabilities with confidence scores, intent classification, impact assessment, and human-readable remediation guidance. The base LLM functionality, including batch handling and token-budget logic, is implemented in [`src/skillspector/llm_analyzer_base.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/llm_analyzer_base.py).

## LangGraph Workflow Architecture

The two-stage detection pipeline is implemented as a directed graph in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) using LangGraph's `StateGraph`. The workflow connects five primary node types:

```python
from langgraph.graph import StateGraph, START, END
from skillspector.state import SkillspectorState

workflow = StateGraph(SkillspectorState)

# Core pipeline nodes

workflow.add_node("resolve_input", resolve_input)
workflow.add_node("build_context", build_context)
workflow.add_node("meta_analyzer", meta_analyzer)
workflow.add_node("report", report)

# Dynamic analyzer nodes

for analyzer_id in ANALYZER_NODE_IDS:
    workflow.add_node(analyzer_id, ANALYZER_NODES[analyzer_id])

# Execution flow

workflow.add_edge(START, "resolve_input")
workflow.add_edge("resolve_input", "build_context")
for analyzer_id in ANALYZER_NODE_IDS:
    workflow.add_edge("build_context", analyzer_id)
    workflow.add_edge(analyzer_id, "meta_analyzer")
workflow.add_edge("meta_analyzer", "report")
workflow.add_edge("report", END)

```

The execution sequence follows this path:
- **Step 0**: `resolve_input` parses command-line arguments or ZIP bundles, storing manifests and file caches
- **Step 1**: `build_context` loads every file into the state for analyzer access
- **Step 2**: All static analyzers run in parallel against the file context
- **Step 3**: `meta_analyzer` processes files with findings through the LLM validation layer
- **Step 4**: `report` generates SARIF/JSON outputs or console summaries

## Implementation Details and Key Files

The state management system in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) maintains the `SkillspectorState` object, which tracks findings, file caches, and the bundle manifest throughout the pipeline.

You can invoke the two-stage detection pipeline programmatically:

```python
from skillspector.graph import graph
from skillspector.state import SkillspectorState

state = SkillspectorState()
state["bundle_path"] = "./my_skill.zip"
state["use_llm"] = True
state["model_config"] = {"meta_analyzer": "gemini-1.5-pro"}

# Execute the compiled graph

final_state = graph.invoke(state)
print(final_state["report"])

```

Or via the CLI:

```bash
skillspector scan ./my_skill.zip --model meta_analyzer=gemini-1.5-pro

```

The architecture deliberately batches LLM calls at the **file level** rather than the **finding level**, significantly reducing API costs while maintaining the reasoning capabilities necessary for accurate vulnerability classification.

## Summary

- **Stage 1** uses multiple static analyzers (YARA, AST-based, taint-tracking) to generate high-recall raw findings across all files in `src/skillspector/nodes/analyzers/`
- **Stage 2** leverages the `meta_analyzer` node to perform per-file LLM validation, filtering false positives and enriching true vulnerabilities with remediation steps
- The **LangGraph architecture** in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) orchestrates the pipeline through `resolve_input` → `build_context` → static analyzers → `meta_analyzer` → `report`
- **Cost optimization** is achieved by limiting LLM calls to one per file with findings, rather than per individual detection

## Frequently Asked Questions

### How does SkillSpector minimize LLM API costs while maintaining accuracy?

SkillSpector batches LLM invocations at the file level rather than analyzing each finding separately. In [`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py), the Meta-Analyzer aggregates all raw findings from a single file into one prompt, generating a single `MetaAnalyzerResult`. This approach typically reduces LLM calls from dozens (per-finding) to one (per-file), while the LLM's reasoning capabilities still filter false positives and enrich confirmed issues with context.

### What types of static analyzers run in the first stage?

The static analysis stage runs a diverse suite of detectors defined in `src/skillspector/nodes/analyzers/`, including YARA pattern matching ([`static_yara.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_yara.py)), semantic developer intent analysis ([`semantic_developer_intent.py`](https://github.com/NVIDIA/SkillSpector/blob/main/semantic_developer_intent.py)), and taint-tracking engines. These analyzers are registered in the `ANALYZER_NODES` dictionary in [`__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/__init__.py) and execute in parallel during the LangGraph workflow.

### Can the two-stage detection pipeline run without LLM meta-analysis?

Yes. The `use_llm` flag in the `SkillspectorState` allows execution to skip the meta-analysis stage. In this mode, the system produces raw static findings without LLM validation, though this results in higher false-positive rates. The pipeline remains functional as the `report` node processes findings from the state regardless of whether they underwent meta-analysis enrichment.

### What output format does the meta-analyzer produce?

The Meta-Analyzer generates structured `MetaAnalyzerResult` objects containing validated vulnerabilities with confidence scores, intent classifications (e.g., malicious, suspicious, benign), impact assessments, and human-readable remediation steps. These enriched findings are then converted to SARIF or JSON format in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py) for integration with security dashboards and CI/CD pipelines.