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

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, the system defines ANALYZER_NODE_IDS and ANALYZER_NODES, which map to specific detection implementations:

  • YARA rule matching (static_yara.py) – Pattern-based detection for known malicious signatures
  • Semantic intent analysis (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.

Stage 2: LLM Meta-Analysis for Precision Filtering

The second phase introduces the Meta-Analyzer (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.

LangGraph Workflow Architecture

The two-stage detection pipeline is implemented as a directed graph in src/skillspector/graph.py using LangGraph's StateGraph. The workflow connects five primary node types:

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

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:

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 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, 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), semantic developer intent analysis (semantic_developer_intent.py), and taint-tracking engines. These analyzers are registered in the ANALYZER_NODES dictionary in __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 for integration with security dashboards and CI/CD pipelines.

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 →