Implementation:Protectai Modelscan ScanBase
| Knowledge Sources | |
|---|---|
| Domains | ML_Security, Software_Architecture |
| Last Updated | 2026-02-14 12:00 GMT |
Overview
Concrete tool for defining scanner plugins and their result containers, provided by the modelscan scanners module.
Description
ScanBase is the abstract base class that all scanner plugins must extend. It defines the contract: scan() receives a Model and returns either None (format not handled) or a ScanResults containing issues, errors, and skipped entries. ScanResults is the return type dataclass. The label_results() helper stamps the scanner's fully-qualified name onto all issue details for provenance tracking.
Built-in scanners extending ScanBase include:
- PickleUnsafeOpScan — Pickle bytecode analysis
- PyTorchUnsafeOpScan — PyTorch checkpoint scanning
- NumpyUnsafeOpScan — NumPy .npy file scanning
- H5LambdaDetectScan — Keras H5 Lambda layer detection
- KerasLambdaDetectScan — Keras .keras format scanning
- SavedModelLambdaDetectScan — TF SavedModel Keras metadata scanning
- SavedModelTensorflowOpScan — TF SavedModel graph operation scanning
Usage
Use ScanBase when implementing a custom scanner plugin for a new model format. Use ScanResults when building the return value from a scanner's scan() method.
Code Reference
Source Location
- Repository: modelscan
- File: modelscan/scanners/scan.py
- Lines: L10-65
Signature
class ScanResults:
issues: List[Issue]
errors: List[ErrorBase]
skipped: List[ModelScanSkipped]
def __init__(
self,
issues: List[Issue],
errors: List[ErrorBase],
skipped: List[ModelScanSkipped],
) -> None:
"""Container for scanner output."""
class ScanBase(metaclass=abc.ABCMeta):
def __init__(self, settings: Dict[str, Any]) -> None:
"""
Args:
settings: Full settings dict. Scanner-specific config
accessible via settings["scanners"][self.full_name()].
"""
@staticmethod
@abc.abstractmethod
def name() -> str:
"""Return short scanner name (e.g., 'PickleUnsafeOpScan')."""
@staticmethod
@abc.abstractmethod
def full_name() -> str:
"""Return fully-qualified class path (e.g., 'modelscan.scanners.PickleUnsafeOpScan')."""
@abc.abstractmethod
def scan(self, model: Model) -> Optional[ScanResults]:
"""
Scan a model file.
Returns:
None if this scanner doesn't handle the model's format.
ScanResults with issues/errors/skipped if handled.
"""
def handle_binary_dependencies(
self, settings: Optional[Dict[str, Any]] = None
) -> Optional[str]:
"""Check if required binary dependencies are installed."""
def label_results(self, results: ScanResults) -> ScanResults:
"""Stamp scanner full_name on all issue details."""
Import
from modelscan.scanners.scan import ScanBase, ScanResults
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| settings | Dict[str, Any] | Yes | Full settings dictionary. Scanner accesses its own config via settings["scanners"][self.full_name()] |
| model | Model | Yes (for scan()) | Model object wrapping a file path and byte stream. Check model.get_context("formats") for format compatibility. |
Outputs
| Name | Type | Description |
|---|---|---|
| scan() returns | Optional[ScanResults] | None if scanner doesn't handle this format. ScanResults otherwise, containing: issues (List[Issue]), errors (List[ErrorBase]), skipped (List[ModelScanSkipped]) |
| name() | str | Short scanner name for display |
| full_name() | str | Fully-qualified class path for identification and settings lookup |
Usage Examples
Implementing a Custom Scanner
from modelscan.scanners.scan import ScanBase, ScanResults
from modelscan.model import Model
from modelscan.issues import Issue, IssueCode, IssueSeverity, OperatorIssueDetails
from typing import Optional, Dict, Any
class MyCustomScan(ScanBase):
@staticmethod
def name() -> str:
return "MyCustomScan"
@staticmethod
def full_name() -> str:
return "mypackage.scanners.MyCustomScan"
def scan(self, model: Model) -> Optional[ScanResults]:
# Check if this scanner handles the model's format
formats = model.get_context("formats") or []
if not any(f.value == "my_format" for f in formats):
return None # Not our format
# Perform analysis
issues = []
data = model.get_stream().read()
if b"dangerous_pattern" in data:
issues.append(
Issue(
code=IssueCode.UNSAFE_OPERATOR,
severity=IssueSeverity.HIGH,
details=OperatorIssueDetails(
module="my_module",
operator="dangerous_op",
severity=IssueSeverity.HIGH,
source=model.get_source(),
),
)
)
results = ScanResults(issues=issues, errors=[], skipped=[])
return self.label_results(results)
Registering a Custom Scanner in Settings
import copy
from modelscan.settings import DEFAULT_SETTINGS
from modelscan.modelscan import ModelScan
settings = copy.deepcopy(DEFAULT_SETTINGS)
settings["scanners"]["mypackage.scanners.MyCustomScan"] = {
"enabled": True,
"supported_extensions": [".myformat"],
}
scanner = ModelScan(settings=settings)
results = scanner.scan("/path/to/model.myformat")
Related Pages
Implements Principle
Requires Environment
- Environment:Protectai_Modelscan_Python_Core_Runtime
- Environment:Protectai_Modelscan_TensorFlow_Optional
- Environment:Protectai_Modelscan_H5py_Optional