How SkillSpector's Two-Stage Detection Pipeline Works: Static Analysis to LLM Validation
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 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 - Semantic intent analysis in
semantic_developer_intent.pythat 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, 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.
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. The workflow processes data through five sequential node types:
resolve_input(src/skillspector/nodes/resolve_input.py): Parses command-line arguments and zip bundles, storing the manifest and file cachebuild_context(src/skillspector/nodes/build_context.py): Loads every file from the bundle into state for analyzer access- Static analyzers (
ANALYZER_NODESfromsrc/skillspector/nodes/analyzers/__init__.py): Runs detection suite in parallel meta_analyzer(src/skillspector/nodes/meta_analyzer.py): Executes LLM filtering and enrichmentreport(src/skillspector/nodes/report.py): Generates SARIF/JSON output or console summaries
The graph implementation connects these nodes through conditional edges:
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 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:
# 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:
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.pyprovides high-precision filtering by validating findings through per-file LLM calls that add confidence scores and remediation guidance. - LangGraph orchestration in
src/skillspector/graph.pyconnects 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, 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →