Scanner Plugins
Scanner plugins perform security scans on files and generate findings. They are the core of ASH's security scanning functionality.
For detailed visual diagrams of scanner plugin architecture and workflow, see Scanner Plugin Diagrams.
Scanner Plugin Interface
Scanner plugins must implement the ScannerPluginBase
interface:
from automated_security_helper.base.scanner_plugin import ScannerPluginBase, ScannerPluginConfigBase
from automated_security_helper.plugins.decorators import ash_scanner_plugin
@ash_scanner_plugin
class MyScanner(ScannerPluginBase):
"""My custom scanner implementation"""
def scan(self, target, target_type, global_ignore_paths=None, config=None):
"""Implement your scanning logic here"""
# Your code here
Scanner Plugin Configuration
Define a configuration class for your scanner:
from pydantic import Field
class MyScannerConfig(ScannerPluginConfigBase):
name: str = "my-scanner"
enabled: bool = True
class Options:
severity_threshold: str = Field(default="MEDIUM", description="Minimum severity level")
include_tests: bool = Field(default=False, description="Include test files")
Scanner Plugin Example
Here's a complete example of a custom scanner plugin:
import json
import subprocess
from pathlib import Path
from typing import List, Literal
from pydantic import Field
from automated_security_helper.base.scanner_plugin import ScannerPluginBase, ScannerPluginConfigBase
from automated_security_helper.plugins.decorators import ash_scanner_plugin
from automated_security_helper.models.scan_results_container import ScanResultsContainer
class CustomScannerConfig(ScannerPluginConfigBase):
"""Configuration for CustomScanner"""
name: str = "custom-scanner"
enabled: bool = True
class Options:
tool_path: str = Field(default="custom-tool", description="Path to the scanning tool")
severity_threshold: str = Field(default="MEDIUM", description="Minimum severity level")
@ash_scanner_plugin
class CustomScanner(ScannerPluginBase):
"""Custom scanner implementation"""
def model_post_init(self, context):
"""Initialize the scanner with configuration"""
if self.config is None:
self.config = CustomScannerConfig()
self.command = "custom-tool"
# Enable UV tool execution if your tool should be managed via UV
# self.use_uv_tool = True
self.tool_type = "SAST" # or "IAC", "SCA", "SECRETS", etc.
super().model_post_init(context)
def scan(self, target: Path, target_type: Literal["source", "converted"],
global_ignore_paths: List = None, config=None):
"""Scan the target using a custom tool"""
if config is None:
config = self.config
# Create results container
container = ScanResultsContainer()
try:
# Run the external tool
cmd = [config.options.tool_path, "--scan", str(target),
"--severity", config.options.severity_threshold]
result = self._run_subprocess(
cmd,
stdout_preference="return",
stderr_preference="write"
)
# Parse the output
if result.stdout:
findings = json.loads(result.stdout)
# Create SARIF report
sarif_report = {
"version": "2.1.0",
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"runs": [
{
"tool": {
"driver": {
"name": config.name,
"version": "1.0.0"
}
},
"results": []
}
]
}
# Convert findings to SARIF format
for finding in findings:
sarif_report["runs"][0]["results"].append({
"ruleId": finding["id"],
"level": finding["severity"].lower(),
"message": {
"text": finding["message"]
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": finding["file"]
},
"region": {
"startLine": finding["line"]
}
}
}
]
})
# Write SARIF report
sarif_path = self.results_dir / f"{config.name}.sarif"
with open(sarif_path, "w") as f:
json.dump(sarif_report, f, indent=2)
container.sarif_report = sarif_report
except Exception as e:
container.add_error(f"Error running scanner: {str(e)}")
return container
UV Tool Integration
ASH v3 supports UV tool integration for scanner plugins that use CLI tools. This provides automatic tool isolation and management without requiring users to manually install tools.
Enabling UV Tool Integration
To enable UV tool integration in your scanner plugin:
@ash_scanner_plugin
class MyScanner(ScannerPluginBase):
def model_post_init(self, context):
if self.config is None:
self.config = MyScannerConfig()
self.command = "my-tool"
self.use_uv_tool = True # Enable UV tool execution
self.tool_type = "SAST"
# Get tool version via UV
self.tool_version = self._get_uv_tool_version("my-tool")
super().model_post_init(context)
def validate_plugin_dependencies(self) -> bool:
"""Validate scanner dependencies"""
# UV tool availability is automatically validated
if not self._validate_uv_tool_availability():
return False
# For UV tool-based scanners, dependencies are satisfied if UV is available
if self.use_uv_tool:
self.dependencies_satisfied = True
return True
return super().validate_plugin_dependencies()
Benefits of UV Tool Integration
- Automatic Tool Management: Tools are downloaded and managed automatically
- Isolation: Tools run in isolated environments without affecting project dependencies
- Version Control: Consistent tool versions across environments
- Fallback Support: Automatic fallback to direct execution if UV is unavailable
Built-in UV Tool Scanners
The following built-in scanners use UV tool integration: - Checkov: Infrastructure-as-Code scanning - Semgrep: Static Application Security Testing
Scanner Plugin Best Practices
- Generate SARIF Reports: SARIF is the standard format for security findings
- Handle Errors Gracefully: Use try/except blocks to handle errors
- Respect Global Ignore Paths: Skip files that are in the global ignore paths
- Use Subprocess Utilities: Use the provided
_run_subprocess
method for running external commands - Add Metadata: Add useful metadata to the results container
- UV Tool Integration: For CLI tools that can be managed via UV, set
use_uv_tool = True
in yourmodel_post_init
method to enable automatic tool isolation and management
Scanner Plugin Configuration in ASH
Configure your scanner in the ASH configuration file:
# .ash/.ash.yaml
scanners:
custom-scanner:
enabled: true
options:
tool_path: /path/to/custom-tool
severity_threshold: HIGH
Testing Scanner Plugins
Create unit tests for your scanner:
import pytest
from pathlib import Path
from automated_security_helper.base.plugin_context import PluginContext
from my_ash_plugins.scanners import CustomScanner
def test_custom_scanner():
# Create a plugin context
context = PluginContext(
source_dir=Path("test_data"),
output_dir=Path("test_output")
)
# Create scanner instance
scanner = CustomScanner(context=context)
# Run the scanner
results = scanner.scan(Path("test_data/sample.py"), "source")
# Assert results
assert results is not None
assert results.sarif_report is not None
assert len(results.sarif_report["runs"][0]["results"]) > 0