How to Contribute to SkillSpector: A Developer's Guide to NVIDIA's LangGraph Security Scanner
To contribute to SkillSpector, clone the NVIDIA repository, install development dependencies with make install-dev, and extend the scanning pipeline by creating new analyzer modules in src/skillspector/nodes/analyzers/ and registering them in the analyzer registry.
SkillSpector is a LangGraph-based security scanner for AI-agent skills developed by NVIDIA. The tool resolves skills from Git repositories, ZIP files, or local directories, then executes a directed acyclic graph (DAG) of 22 parallel analyzers to detect vulnerabilities and emit SARIF/JSON/Markdown reports. Contributing to SkillSpector involves extending its modular analyzer suite, improving the graph workflow in src/skillspector/graph.py, or enhancing the risk-scoring logic in the report node.
Setting Up Your Development Environment
Begin by cloning the repository and creating an isolated Python environment. The project prefers uv for virtual environment management, though standard venv works equally well.
# Clone the repository
git clone https://github.com/NVIDIA/SkillSpector.git
cd SkillSpector
# Create and activate virtual environment (uv preferred)
uv venv .venv && source .venv/bin/activate
# Alternative: python3 -m venv .venv && source .venv/bin/activate
# Install development dependencies
make install-dev
All make targets assume an active virtual environment. For detailed build commands and environment variables, consult docs/DEVELOPMENT.md.
Understanding the SkillSpector Architecture
SkillSpector processes skills through a state-driven DAG defined in src/skillspector/graph.py. The workflow passes a SkillspectorState (a TypedDict defined in src/skillspector/state.py) through five distinct stages:
- Input resolution –
src/skillspector/input_handler.pyclones Git repos, extracts ZIPs, and normalizes inputs into local directories. - Context building –
src/skillspector/nodes/build_context.pywalks the directory, caches file contents, builds ASTs, and flags executable scripts. - Parallel analysis – 22 analyzer nodes (static regex/YARA, AST-based behavioral checks, CVE lookups, and MCP checks) each emit
Findingobjects defined insrc/skillspector/models.py. - Meta-analysis –
src/skillspector/nodes/meta_analyzer.pyoptionally filters false positives using an LLM provider configured insrc/skillspector/providers/. - Reporting –
src/skillspector/nodes/report.pyapplies baseline suppression, computes a numeric risk score, and renders the final output.
All state transitions use LangGraph’s reducer pattern, ensuring pure functions without side effects. This architecture makes the pipeline trivial to unit test and extend.
Adding a New Security Analyzer
The most common contribution is adding a new static analyzer to detect specific vulnerability patterns. Analyzers reside in src/skillspector/nodes/analyzers/ and follow a standardized interface.
Creating the Analyzer Module
Create a new Python file in the analyzers directory implementing the analyze function signature:
# src/skillspector/nodes/analyzers/static_patterns_secret_leak.py
from .common import AnalyzerFinding, Location, Severity
from .pattern_defaults import CATEGORY, EXPLANATION, REMEDIATION
def analyze(content: str, file_path: str, file_type: str) -> list[AnalyzerFinding]:
findings: list[AnalyzerFinding] = []
# Detect hard-coded AWS access key patterns
if "AKIA" in content:
lines = content.splitlines()
for line_no, line in enumerate(lines, 1):
if "AKIA" in line:
findings.append(
AnalyzerFinding(
location=Location(file=file_path, line=line_no),
severity=Severity.HIGH,
rule_id="SEC1",
message="Hard-coded AWS access key detected",
category=CATEGORY,
explanation=EXPLANATION,
remediation=REMEDIATION,
)
)
break
return findings
The function receives the file content, absolute path, and detected MIME type, then returns a list of AnalyzerFinding objects containing the location, severity, and remediation guidance.
Registering Your Analyzer
Make the new analyzer available to the graph by updating the registry in src/skillspector/nodes/analyzers/__init__.py:
from . import static_patterns_secret_leak
ANALYZER_NODE_IDS.append("static_secret_leak")
ANALYZER_NODES["static_secret_leak"] = static_runner.run_static_patterns(
static_patterns_secret_leak.analyze
)
The ANALYZER_NODE_IDS list controls execution order, while the ANALYZER_NODES dictionary maps the node ID to the LangGraph node function.
Writing Unit Tests
Create a corresponding test file under tests/nodes/analyzers/:
# tests/nodes/analyzers/test_static_secret_leak.py
from skillspector.nodes.analyzers import static_patterns_secret_leak
def test_detects_aws_key():
content = "aws_access_key_id = 'AKIAIOSFODNN7EXAMPLE'"
findings = static_patterns_secret_leak.analyze(content, "test.py", "text/x-python")
assert len(findings) == 1
assert findings[0].rule_id == "SEC1"
assert findings[0].severity.name == "HIGH"
Run the specific test with pytest tests/nodes/analyzers/test_static_secret_leak.py or execute the full suite with make test.
Testing Your Contributions Locally
Verify your changes using the CLI or the interactive LangGraph development server.
Scan a local skill with your new analyzer:
# Run with static analysis only (fastest iteration)
skillspector scan ./examples/my-skill/ --no-llm
# Generate structured JSON output
skillspector scan ./examples/my-skill/ --format json -o report.json
Launch LangGraph Studio for interactive debugging:
make langgraph-dev
This starts a local HTTP server and opens LangGraph Studio, where you can step through individual nodes, inspect the SkillspectorState at each stage, and invoke the graph with custom inputs such as:
{
"input_path": "/absolute/path/to/skill",
"output_format": "json",
"use_llm": false
}
Documentation and Pull Request Guidelines
Before submitting, ensure your contribution meets the repository's quality standards:
- Update documentation – Add an entry to
docs/DEVELOPMENT.mddescribing the analyzer's detection logic and any configuration flags. UpdateREADME.mdif you introduce new CLI options. - Code quality – Run the linting and formatting tools:
make lint && make format. - Full test suite – Execute
make testto verify no regressions in the risk-score gating model.
Submit your contribution by forking the repository, creating a feature branch (feature/my-new-analyzer), and opening a pull request against main. Reviewers will verify that new rules correctly categorize findings and do not introduce false positives that could skew the final risk calculation.
Summary
- Setup – Clone NVIDIA/SkillSpector, create a virtual environment, and run
make install-devto install dependencies. - Architecture – SkillSpector uses a LangGraph DAG with 22 analyzer nodes processing a
SkillspectorStatethrough resolution, context building, analysis, and reporting stages. - Development – Add new detectors by implementing the
analyzefunction insrc/skillspector/nodes/analyzers/, then register them in__init__.pyand write unit tests undertests/nodes/analyzers/. - Testing – Use
make langgraph-devfor interactive debugging andmake testfor regression testing; use--no-llmfor rapid static analysis iteration. - Submission – Follow the guidelines in
CONTRIBUTING.md, update relevant documentation, and ensure CI checks pass before requesting review.
Frequently Asked Questions
What programming language is SkillSpector written in?
SkillSpector is implemented in Python 3 using the LangGraph framework for orchestrating the security scanning workflow. The codebase leverages Pydantic models in src/skillspector/models.py for structured data validation and uses standard AST libraries for code analysis.
Do I need an LLM API key to contribute to SkillSpector?
No. While SkillSpector supports LLM-based meta-analysis via providers in src/skillspector/providers/, you can develop and test static analyzers without API keys by using the --no-llm flag. This mode runs only the static pattern matchers, AST detectors, and YARA rules, making it ideal for rapid development cycles.
How does the risk score calculation work?
The risk score is computed in src/skillspector/nodes/report.py based on the aggregated severity of findings after baseline suppression. Each Finding object carries a severity level (LOW, MEDIUM, HIGH, CRITICAL) that maps to numeric weights; the final score determines the severity rating and recommendation shown in SARIF or Markdown outputs.
Can I contribute bug fixes or documentation without adding new analyzers?
Yes. Contributions to improve error handling in src/skillspector/input_handler.py, enhance the reducer pattern logic in the state management, clarify documentation in docs/DEVELOPMENT.md, or fix edge cases in existing analyzers are welcome. All pull requests undergo the same risk-score gating review process to ensure code quality.
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 →