How to Contribute New Vulnerability Patterns to SkillSpector: A Complete Developer Guide
To contribute new vulnerability patterns to SkillSpector, create a Python module in src/skillspector/nodes/analyzers/ that exposes an analyze() function returning AnalyzerFinding objects, register it via static_runner.run_static_patterns, add metadata to pattern_defaults.py, and write unit tests following the existing pytest patterns.
SkillSpector is NVIDIA's open-source security scanner for AI skill definitions that detects security-relevant constructs using static-pattern analyzers. Contributing new vulnerability patterns allows you to extend the tool's detection capabilities for emerging security threats. This guide walks through the exact steps required to add custom analyzers to the SkillSpector repository, referencing the actual source files and architectural patterns used in the codebase.
Understanding the Analyzer Architecture
SkillSpector's detection pipeline relies on static-pattern analyzers—modular Python components that scan skill definition files for security anti-patterns. Each analyzer lives in its own module under src/skillspector/nodes/analyzers/ and follows a strict contract.
The core data structure is AnalyzerFinding (defined in src/skillspector/models.py), which encapsulates:
- rule_id: Unique identifier (e.g.,
MY1) - severity: Severity level (e.g.,
Severity.HIGH) - confidence: Float between 0 and 1
- location: File path and line number via
Locationobjects - tags: Category tags derived from
PatternCategory - context: Surrounding code snippet
The static_runner.run_static_patterns function (located in src/skillspector/nodes/analyzers/static_runner.py) orchestrates the scanning process. It iterates over every file in the skill project, infers file types via extensions, and invokes each module's analyze function. The runner automatically converts AnalyzerFinding objects into the generic Finding type used by the graph state.
Because the pipeline discovers analyzers through module imports, no central registry edits are required—simply placing your file in the correct package makes it available for scanning.
Step-by-Step Guide to Contributing New Vulnerability Patterns
Follow these seven steps to add a fully functional vulnerability pattern to SkillSpector.
Step 1: Create the Analyzer Module
Create a new Python file in src/skillspector/nodes/analyzers/ using the naming convention static_patterns_<your_rule>.py. Use static_patterns_excessive_agency.py as a reference template.
Your module must expose an analyze(content, file_path, file_type) function that returns list[AnalyzerFinding]. The function receives the file content as a string, the absolute file path, and the inferred file type (e.g., "markdown", "python").
Step 2: Define Detection Patterns
Define your detection logic using regex patterns or custom parsing logic. Patterns typically follow the tuple format (regex_pattern, confidence), where confidence is a float representing the certainty of the match.
Store patterns in a module-level constant (e.g., MY1_PATTERNS) and iterate through them in your analyze function. Use helper functions from src/skillspector/nodes/analyzers/common.py such as get_context(), get_line_number(), and is_code_example() to enrich findings with location data and context snippets.
Step 3: Implement the Node Function
Add a node(state) function that serves as the entry point for the LangGraph execution pipeline. This function must call static_runner.run_static_patterns(state, [sys.modules[__name__]]) and return an AnalyzerNodeResponse containing the findings.
The sys.modules[__name__] argument passes the current module to the runner, enabling automatic discovery of your analyze function.
Step 4: Register Metadata in pattern_defaults.py
Human-readable reporting requires metadata entries in src/skillspector/nodes/analyzers/pattern_defaults.py. Add entries for your rule ID to the following dictionaries:
DEFAULT_EXPLANATIONS: Description of the vulnerabilityDEFAULT_REMEDIATIONS: Step-by-step fix instructionsRULE_ID_TO_CATEGORY: Mapping to aPatternCategoryenum value (e.g.,PatternCategory.EXCESSIVE_AGENCY,PatternCategory.OUTPUT_HANDLING)PATTERN_NAMES: Display name for the rule
Step 5: Write Unit Tests
Create test cases in tests/unit/test_patterns_new.py following the existing pytest patterns. Use pytest.mark.parametrize to test both positive cases (strings that should trigger the rule) and negative cases (strings that should not).
Import your module directly and call the analyze() function with test content, asserting that the returned AnalyzerFinding objects contain the expected rule IDs.
Step 6: Validate with pytest
Run the full test suite using pytest -q to ensure your changes do not break existing analyzers. All existing tests must pass, and your new tests should succeed before submitting a pull request.
Step 7: Document Your Contribution
Update CONTRIBUTING.md (if present) with a reference to your new analyzer file. Include a brief description of the vulnerability pattern and any special configuration requirements.
Complete Code Example: Minimal Analyzer Implementation
Below is a complete implementation of a custom analyzer module (static_patterns_my_new_rule.py) that detects the dangerous marker "dangerous‑operation‑allowed":
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Static patterns: my custom rule (MY1). Detects any occurrence of the phrase
"dangerous‑operation‑allowed".
"""
from __future__ import annotations
import re
import sys
from skillspector.logging_config import get_logger
from skillspector.models import AnalyzerFinding, Location, Severity
from skillspector.state import AnalyzerNodeResponse, SkillspectorState
from . import static_runner
from .common import get_context, get_line_number, is_code_example
from .pattern_defaults import PatternCategory
logger = get_logger(__name__)
ANALYZER_ID = "static_patterns_my_new_rule"
# Define the pattern list – each entry is (regex, confidence)
MY1_PATTERNS = [(r"dangerous‑operation‑allowed", 0.9)]
def analyze(content: str, file_path: str, file_type: str) -> list[AnalyzerFinding]:
"""Run the MY1 regex over the supplied content."""
findings: list[AnalyzerFinding] = []
def loc(ln: int) -> Location:
return Location(file=file_path, start_line=ln)
def ctx(start: int) -> str:
return get_context(content, start)
tag = [PatternCategory.OUTPUT_HANDLING.value]
for pattern, confidence in MY1_PATTERNS:
for match in re.finditer(pattern, content, re.IGNORECASE):
line_num = get_line_number(content, match.start())
findings.append(
AnalyzerFinding(
rule_id="MY1",
message="Dangerous operation flag detected",
severity=Severity.HIGH,
location=loc(line_num),
confidence=confidence,
tags=tag,
context=ctx(match.start()),
matched_text=match.group(0)[:200],
)
)
return findings
def node(state: SkillspectorState) -> AnalyzerNodeResponse:
"""Execute the MY1 analyzer on the skill state."""
findings = static_runner.run_static_patterns(state, [sys.modules[__name__]])
logger.info("%s: %d findings", ANALYZER_ID, len(findings))
return {"findings": findings}
Adding Metadata and Unit Tests
After implementing the analyzer, populate the metadata tables in src/skillspector/nodes/analyzers/pattern_defaults.py:
# In DEFAULT_EXPLANATIONS
"MY1": "The skill contains a marker that explicitly permits a dangerous operation. Such markers bypass safety checks and can lead to privilege escalation or data loss.",
# In DEFAULT_REMEDIATIONS
"MY1": "Remove the 'dangerous‑operation‑allowed' marker or replace it with an explicit, audited approval step. Require human confirmation before the operation is performed.",
# In RULE_ID_TO_CATEGORY
"MY1": PatternCategory.OUTPUT_HANDLING.value,
# In PATTERN_NAMES
"MY1": "Dangerous Operation Marker",
Write comprehensive unit tests in tests/unit/test_patterns_new.py:
import pytest
from skillspector.nodes.analyzers import static_patterns_my_new_rule as my_mod
def test_my1_detected():
content = "Please enable dangerous‑operation‑allowed for the next step."
findings = my_mod.analyze(content, "SKILL.md", "markdown")
assert any(f.rule_id == "MY1" for f in findings)
def test_my1_not_flagged_on_safe_text():
content = "All operations are safe and reviewed."
findings = my_mod.analyze(content, "SKILL.md", "markdown")
assert not any(f.rule_id == "MY1" for f in findings)
Key Files Reference
When contributing new vulnerability patterns to SkillSpector, you will work with these specific source files:
src/skillspector/nodes/analyzers/static_patterns_excessive_agency.py: Reference implementation showing the canonical structure for static-pattern analyzerssrc/skillspector/nodes/analyzers/static_runner.py: Runner utility that coordinates file iteration and module executionsrc/skillspector/nodes/analyzers/pattern_defaults.py: Metadata tables for explanations, remediations, categories, and display namessrc/skillspector/models.py: Definitions forAnalyzerFinding,Location,Severity, and other data structurestests/unit/test_patterns_new.py: Template for unit testing new patterns
Summary
- Create a new module in
src/skillspector/nodes/analyzers/implementing theanalyze()function that returnslist[AnalyzerFinding] - Define regex patterns as tuples of
(pattern, confidence)and iterate through them to generate findings with properLocationand context data - Add a
node()wrapper that callsstatic_runner.run_static_patterns(state, [sys.modules[__name__]])to integrate with the scanning pipeline - Register metadata in
pattern_defaults.pyfor human-readable reporting, including explanations, remediations, categories, and display names - Write unit tests using pytest to verify pattern detection and avoid false positives
- Validate changes by running
pytest -qto ensure no regressions in existing analyzers
Frequently Asked Questions
What is the minimum code required to create a new vulnerability pattern?
You need a Python module in src/skillspector/nodes/analyzers/ containing an analyze(content, file_path, file_type) function that returns a list of AnalyzerFinding objects, and a node(state) function that calls static_runner.run_static_patterns(state, [sys.modules[__name__]]). You must also add entries for your rule ID in pattern_defaults.py to provide explanations and categories.
How does SkillSpector automatically register new analyzers?
SkillSpector uses Python's module import system for discovery. When you place your analyzer file in the src/skillspector/nodes/analyzers/ package, the static_runner.run_static_patterns function can import and execute it dynamically. No manual registration in a central registry is required—simply ensuring the file is importable and passing sys.modules[__name__] to the runner is sufficient.
What fields are required when creating an AnalyzerFinding object?
According to the AnalyzerFinding model in src/skillspector/models.py, you must provide: rule_id (string), message (string), severity (Severity enum), location (Location object with file path and line number), confidence (float), tags (list of category strings), context (surrounding code snippet), and matched_text (the specific matched string). All fields are used by the reporting UI to display findings.
How do I categorize a new vulnerability pattern?
Choose an appropriate category from the PatternCategory enum (defined in src/skillspector/nodes/analyzers/pattern_defaults.py), such as EXCESSIVE_AGENCY or OUTPUT_HANDLING. Map your rule ID to this category value in the RULE_ID_TO_CATEGORY dictionary in pattern_defaults.py. If no existing category fits, you must extend the PatternCategory enum and update the mapping tables accordingly.
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 →