# How SkillSpector's Two-Stage Detection Pipeline Works: Static Analysis to LLM Validation

> Discover how SkillSpector's two-stage pipeline uses static analysis and LLM validation to detect vulnerabilities, filter false positives, and provide remediation steps. Learn more about NVIDIA/SkillSpector.

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

---

**SkillSpector processes skill bundles through a static-analysis stage that generates coarse vulnerability findings, followed by an LLM-meta-analysis stage that filters false positives and enriches confirmed threats with contextual remediation steps.**

The NVIDIA SkillSpector repository implements a sophisticated two-stage detection pipeline designed to balance comprehensive security coverage with high-precision vulnerability validation. This architecture casts a wide net using traditional static analysis tools, then applies Large Language Model reasoning to eliminate false positives and add contextual security insights. The entire workflow is orchestrated through a LangGraph state machine defined in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) that processes skill bundles from input resolution to final SARIF reporting.

## 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 to identify potential security issues. These analyzers run in parallel through the `ANALYZER_NODES` mapping and emit **raw findings** containing rule IDs, file locations, severity levels, and initial metadata.

According to the source code in `src/skillspector/nodes/analyzers/`, the static analysis suite includes multiple detection strategies:

- **Pattern-based detection** via YARA rules in [`static_yara.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_yara.py)
- **Semantic intent analysis** in [`semantic_developer_intent.py`](https://github.com/NVIDIA/SkillSpector/blob/main/semantic_developer_intent.py) that evaluates developer purpose
- **Behavioral AST analysis** identifying dangerous code constructs
- **Taint-tracking** for data flow vulnerabilities

Each analyzer stores results as `Finding` objects in the shared state, producing a coarse list with high recall that captures every potential vulnerability regardless of false positive likelihood.

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

The second phase implements a **Meta-Analyzer** that executes per-file LLM calls to transform raw findings into validated, enriched security intelligence. Defined in [`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py), this component builds structured prompts containing file metadata, source content, and the static analysis findings, then invokes the `LLMMetaAnalyzer` class from [`src/skillspector/llm_analyzer_base.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/llm_analyzer_base.py).

For each file containing at least one static finding, the LLM returns a structured `MetaAnalyzerResult` that indicates:

- **Validation**: Which findings represent true vulnerabilities versus false positives
- **Confidence scoring**: Numerical certainty ratings for each confirmed issue
- **Intent classification**: Developer purpose and security context
- **Impact assessment**: Severity and exploitability analysis
- **Remediation guidance**: Human-readable explanations and fix instructions

This two-stage design minimizes expensive LLM calls by batching analysis at the **per-file level** rather than per-finding, while adding a precision layer that contextually validates the high-recall static results.

## LangGraph Workflow Orchestration

SkillSpector wires these stages together using a LangGraph `StateGraph` defined in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py). The workflow processes data through five sequential node types:

1. **`resolve_input`** ([`src/skillspector/nodes/resolve_input.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/resolve_input.py)): Parses command-line arguments and zip bundles, storing the manifest and file cache
2. **`build_context`** ([`src/skillspector/nodes/build_context.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/build_context.py)): Loads every file from the bundle into state for analyzer access
3. **Static analyzers** (`ANALYZER_NODES` from [`src/skillspector/nodes/analyzers/__init__.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/analyzers/__init__.py)): Runs detection suite in parallel
4. **`meta_analyzer`** ([`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py)): Executes LLM filtering and enrichment
5. **`report`** ([`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py)): Generates SARIF/JSON output or console summaries

The graph implementation connects these nodes through conditional edges:

```python
workflow = StateGraph(SkillspectorState)

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)

# Register every static analyzer node

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

# Wiring: resolve → build → each analyzer → meta → report

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 `SkillspectorState` class defined in [`src/skillspector/state.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/state.py) maintains the shared data structure containing the file cache, manifest, raw findings, and final report throughout graph execution.

## Running the Pipeline

Execute the complete two-stage pipeline using the SkillSpector CLI:

```bash

# Scan a skill bundle using the full pipeline

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

```

Alternatively, invoke the LangGraph workflow 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"])

```

## Summary

- **Static analyzers** in `src/skillspector/nodes/analyzers/` provide high-recall detection by scanning every file for potential vulnerabilities using rule-based and semantic methods.
- The **meta-analyzer** in [`src/skillspector/nodes/meta_analyzer.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/meta_analyzer.py) provides high-precision filtering by validating findings through per-file LLM calls that add confidence scores and remediation guidance.
- **LangGraph orchestration** in [`src/skillspector/graph.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/graph.py) connects input resolution, context building, parallel static analysis, LLM enrichment, and reporting into a deterministic state machine.
- The two-stage architecture optimizes LLM costs by limiting calls to one per file rather than one per finding, while maintaining comprehensive security coverage.

## Frequently Asked Questions

### What makes SkillSpector's two-stage detection pipeline more effective than single-stage analysis?

Single-stage scanners must trade off between recall and precision. SkillSpector's pipeline uses fast, deterministic static analysis to achieve high recall without missing potential issues, then applies LLM reasoning only to files with findings to filter false positives. This separation allows the system to be simultaneously comprehensive and precise, adding contextual security insights that static rules cannot determine alone.

### How does the meta-analyzer determine which static findings are true vulnerabilities?

The meta-analyzer builds per-file prompts containing the source code, file metadata, and raw static findings, then submits these to the `LLMMetaAnalyzer` class. The LLM evaluates developer intent, code context, and security implications to return a structured `MetaAnalyzerResult` indicating which findings constitute genuine vulnerabilities along with confidence scores and categorized intent.

### Why does the pipeline process files individually rather than scanning the entire bundle at once?

Processing per-file enables parallel execution of static analyzers and granular LLM calls. This design allows the graph to route findings from specific analyzers ([`static_yara.py`](https://github.com/NVIDIA/SkillSpector/blob/main/static_yara.py), [`semantic_developer_intent.py`](https://github.com/NVIDIA/SkillSpector/blob/main/semantic_developer_intent.py)) to the meta-analyzer only for files containing potential issues. It also prevents token limit exhaustion that would occur when submitting large codebases to LLMs in a single request.

### What output formats does the two-stage pipeline generate?

The reporting node in [`src/skillspector/nodes/report.py`](https://github.com/NVIDIA/SkillSpector/blob/main/src/skillspector/nodes/report.py) converts filtered and enriched findings into **SARIF** (Static Analysis Results Interchange Format) for integration with security platforms, **JSON** for programmatic processing, or concise console summaries for human review. The enriched data includes LLM-generated explanations, remediation steps, and confidence metrics alongside traditional vulnerability metadata.