Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Implementation:Protectai Modelscan ScanBase

From Leeroopedia
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

Uses Heuristic

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment