Skip to content

Index

๐Ÿค– AI-Generated Content

This documentation was generated with AI assistance and is still being audited. Some, or potentially a lot, of this information may be inaccurate. Learn more.

provide.testkit.quality.security

Security analysis integration for provide-testkit.

Provides comprehensive security scanning using multiple security tools: - Bandit: Python SAST (static application security testing) - pip-audit: Dependency vulnerability scanning (PyPI advisories) - Safety: Dependency vulnerability scanning (PyUp database) - GitLeaks: Secret detection (fast, pattern-based) - TruffleHog: Deep secret detection (entropy analysis, verification) - Semgrep: Pattern-based SAST with custom rules

Features: - Multiple vulnerability scanners for comprehensive coverage - Dependency vulnerability scanning - Secret/credential detection - Security issue reporting and classification - Integration with quality gates - Artifact management for CI/CD

Usage

Basic security scanning (Bandit)

def test_with_security(security_scanner): result = security_scanner.scan(path) assert result.passed

Dependency vulnerability scanning

from provide.testkit.quality.security import PipAuditScanner scanner = PipAuditScanner() result = scanner.analyze(path)

Secret detection

from provide.testkit.quality.security import GitLeaksScanner scanner = GitLeaksScanner() result = scanner.analyze(path)

Classes

GitLeaksScanner

GitLeaksScanner(config: dict[str, Any] | None = None)

Secret detection scanner using GitLeaks.

Scans codebases for hardcoded secrets, API keys, passwords, and other sensitive information using pattern matching.

Note: GitLeaks is a Go binary and must be installed separately. Install via: brew install gitleaks (macOS) or download from GitHub releases.

Initialize GitLeaks scanner.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Scanner configuration options. If "config_file" is not specified, will auto-detect .provide/security/gitleaks.toml if it exists.

None
Source code in provide/testkit/quality/security/gitleaks_scanner.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize GitLeaks scanner.

    Args:
        config: Scanner configuration options. If "config_file" is not specified,
                will auto-detect .provide/security/gitleaks.toml if it exists.
    """
    if not GITLEAKS_AVAILABLE:
        raise QualityToolError(
            "GitLeaks not available. Install with: brew install gitleaks (macOS) "
            "or download from https://github.com/gitleaks/gitleaks/releases",
            tool="gitleaks",
        )

    self.config = config or {}
    self.artifact_dir: Path | None = None

    # Auto-detect config file if not explicitly specified
    if "config_file" not in self.config:
        default_config = self._get_default_config_path()
        if default_config:
            self.config["config_file"] = default_config
Functions
analyze
analyze(path: Path, **kwargs: Any) -> QualityResult

Run GitLeaks analysis on the given path.

Parameters:

Name Type Description Default
path Path

Path to scan for secrets

required
**kwargs Any

Additional options including artifact_dir

{}

Returns:

Type Description
QualityResult

QualityResult with secret detection data

Source code in provide/testkit/quality/security/gitleaks_scanner.py
def analyze(self, path: Path, **kwargs: Any) -> QualityResult:
    """Run GitLeaks analysis on the given path.

    Args:
        path: Path to scan for secrets
        **kwargs: Additional options including artifact_dir

    Returns:
        QualityResult with secret detection data
    """
    self.artifact_dir = kwargs.get("artifact_dir", Path(".provide/output/security"))
    start_time = time.time()

    try:
        result = self._run_gitleaks_scan(path)
        result.execution_time = time.time() - start_time
        self._generate_artifacts(result)
        return result

    except Exception as e:
        return QualityResult(
            tool="gitleaks",
            passed=False,
            details={"error": str(e), "error_type": type(e).__name__},
            execution_time=time.time() - start_time,
        )
report
report(
    result: QualityResult, format: str = "terminal"
) -> str

Generate report from QualityResult.

Source code in provide/testkit/quality/security/gitleaks_scanner.py
def report(self, result: QualityResult, format: str = "terminal") -> str:
    """Generate report from QualityResult."""
    if format == "terminal":
        return self._generate_text_report(result)
    elif format == "json":
        return json.dumps(
            {
                "tool": result.tool,
                "passed": result.passed,
                "score": result.score,
                "details": result.details,
            },
            indent=2,
        )
    else:
        return str(result.details)

PipAuditScanner

PipAuditScanner(config: dict[str, Any] | None = None)

Dependency vulnerability scanner using pip-audit.

Scans Python dependencies for known security vulnerabilities using the PyPI security advisory database.

Note: pip-audit does not support config files. All configuration must be passed via CLI flags or the config dict. See wrknv.toml for CLI usage.

Initialize pip-audit scanner.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Scanner configuration options. Supported keys: - strict: Enable strict mode - local: Only scan locally installed packages - skip_editable: Skip editable installations - timeout: Command timeout in seconds

None
Source code in provide/testkit/quality/security/pip_audit_scanner.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize pip-audit scanner.

    Args:
        config: Scanner configuration options. Supported keys:
                - strict: Enable strict mode
                - local: Only scan locally installed packages
                - skip_editable: Skip editable installations
                - timeout: Command timeout in seconds
    """
    if not PIP_AUDIT_AVAILABLE:
        raise QualityToolError(
            "pip-audit not available. Install with: pip install pip-audit",
            tool="pip-audit",
        )

    self.config = config or {}
    self.artifact_dir: Path | None = None
Functions
analyze
analyze(path: Path, **kwargs: Any) -> QualityResult

Run pip-audit analysis on the given path.

Parameters:

Name Type Description Default
path Path

Path to analyze (directory with requirements or pyproject.toml)

required
**kwargs Any

Additional options including artifact_dir

{}

Returns:

Type Description
QualityResult

QualityResult with vulnerability analysis data

Source code in provide/testkit/quality/security/pip_audit_scanner.py
def analyze(self, path: Path, **kwargs: Any) -> QualityResult:
    """Run pip-audit analysis on the given path.

    Args:
        path: Path to analyze (directory with requirements or pyproject.toml)
        **kwargs: Additional options including artifact_dir

    Returns:
        QualityResult with vulnerability analysis data
    """
    self.artifact_dir = kwargs.get("artifact_dir", Path(".provide/output/security"))
    start_time = time.time()

    try:
        result = self._run_pip_audit(path)
        result.execution_time = time.time() - start_time
        self._generate_artifacts(result)
        return result

    except Exception as e:
        return QualityResult(
            tool="pip-audit",
            passed=False,
            details={"error": str(e), "error_type": type(e).__name__},
            execution_time=time.time() - start_time,
        )
report
report(
    result: QualityResult, format: str = "terminal"
) -> str

Generate report from QualityResult.

Source code in provide/testkit/quality/security/pip_audit_scanner.py
def report(self, result: QualityResult, format: str = "terminal") -> str:
    """Generate report from QualityResult."""
    if format == "terminal":
        return self._generate_text_report(result)
    elif format == "json":
        return json.dumps(
            {
                "tool": result.tool,
                "passed": result.passed,
                "score": result.score,
                "details": result.details,
            },
            indent=2,
        )
    else:
        return str(result.details)

SafetyScanner

SafetyScanner(config: dict[str, Any] | None = None)

Dependency vulnerability scanner using Safety.

Scans Python dependencies against the PyUp Safety database for known security vulnerabilities.

Initialize safety scanner.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Scanner configuration options. If "policy_file" is not specified, will auto-detect .provide/security/safety-policy.yml if it exists.

None
Source code in provide/testkit/quality/security/safety_scanner.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize safety scanner.

    Args:
        config: Scanner configuration options. If "policy_file" is not specified,
                will auto-detect .provide/security/safety-policy.yml if it exists.
    """
    if not SAFETY_AVAILABLE:
        raise QualityToolError(
            "Safety not available. Install with: pip install safety",
            tool="safety",
        )

    self.config = config or {}
    self.artifact_dir: Path | None = None

    # Auto-detect policy file if not explicitly specified
    if "policy_file" not in self.config:
        default_config = self._get_default_config_path()
        if default_config:
            self.config["policy_file"] = default_config
Functions
analyze
analyze(path: Path, **kwargs: Any) -> QualityResult

Run safety analysis on the given path.

Parameters:

Name Type Description Default
path Path

Path to analyze (directory with requirements or pyproject.toml)

required
**kwargs Any

Additional options including artifact_dir

{}

Returns:

Type Description
QualityResult

QualityResult with vulnerability analysis data

Source code in provide/testkit/quality/security/safety_scanner.py
def analyze(self, path: Path, **kwargs: Any) -> QualityResult:
    """Run safety analysis on the given path.

    Args:
        path: Path to analyze (directory with requirements or pyproject.toml)
        **kwargs: Additional options including artifact_dir

    Returns:
        QualityResult with vulnerability analysis data
    """
    self.artifact_dir = kwargs.get("artifact_dir", Path(".provide/output/security"))
    start_time = time.time()

    try:
        result = self._run_safety_scan(path)
        result.execution_time = time.time() - start_time
        self._generate_artifacts(result)
        return result

    except Exception as e:
        return QualityResult(
            tool="safety",
            passed=False,
            details={"error": str(e), "error_type": type(e).__name__},
            execution_time=time.time() - start_time,
        )
report
report(
    result: QualityResult, format: str = "terminal"
) -> str

Generate report from QualityResult.

Source code in provide/testkit/quality/security/safety_scanner.py
def report(self, result: QualityResult, format: str = "terminal") -> str:
    """Generate report from QualityResult."""
    if format == "terminal":
        return self._generate_text_report(result)
    elif format == "json":
        return json.dumps(
            {
                "tool": result.tool,
                "passed": result.passed,
                "score": result.score,
                "details": result.details,
            },
            indent=2,
        )
    else:
        return str(result.details)

SecurityFixture

SecurityFixture(
    config: dict[str, Any] | None = None,
    artifact_dir: Path | None = None,
)

Bases: BaseQualityFixture

Pytest fixture for security scanning integration.

Initialize security fixture.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Security scanner configuration

None
artifact_dir Path | None

Directory for artifacts

None
Source code in provide/testkit/quality/security/fixture.py
def __init__(self, config: dict[str, Any] | None = None, artifact_dir: Path | None = None) -> None:
    """Initialize security fixture.

    Args:
        config: Security scanner configuration
        artifact_dir: Directory for artifacts
    """
    super().__init__(config, artifact_dir)
    self.scanner: SecurityScanner | None = None
Functions
generate_report
generate_report(format: str = 'terminal') -> str

Generate security report.

Parameters:

Name Type Description Default
format str

Report format (terminal, json)

'terminal'

Returns:

Type Description
str

Formatted report

Source code in provide/testkit/quality/security/fixture.py
def generate_report(self, format: str = "terminal") -> str:
    """Generate security report.

    Args:
        format: Report format (terminal, json)

    Returns:
        Formatted report
    """
    if not self.scanner:
        return "No security scanner available"

    results = self.get_results_by_tool()
    if "security" not in results:
        return "No security results available"

    return self.scanner.report(results["security"], format)
scan
scan(path: Path) -> dict[str, Any]

Perform security scan.

Parameters:

Name Type Description Default
path Path

Path to scan

required

Returns:

Type Description
dict[str, Any]

Security scan results

Source code in provide/testkit/quality/security/fixture.py
def scan(self, path: Path) -> dict[str, Any]:
    """Perform security scan.

    Args:
        path: Path to scan

    Returns:
        Security scan results
    """
    self.ensure_setup()
    if not self.scanner:
        return {"error": "Scanner not available"}

    result = self.scanner.analyze(path, artifact_dir=self.artifact_dir)
    self.add_result(result)
    return {
        "passed": result.passed,
        "score": result.score,
        "issues": result.details.get("total_issues", 0),
        "details": result.details,
    }
setup
setup() -> None

Setup security scanning.

Source code in provide/testkit/quality/security/fixture.py
def setup(self) -> None:
    """Setup security scanning."""
    if not BANDIT_AVAILABLE:
        pytest.skip("Bandit not available")

    try:
        self.scanner = SecurityScanner(self.config)
    except Exception as e:
        pytest.skip(f"Failed to initialize security scanner: {e}")
teardown
teardown() -> None

Cleanup security scanner.

Source code in provide/testkit/quality/security/fixture.py
def teardown(self) -> None:
    """Cleanup security scanner."""
    # No cleanup needed for security scanner
    pass

SecurityScanner

SecurityScanner(config: dict[str, Any] | None = None)

Security vulnerability scanner using bandit and other tools.

Provides high-level interface for security analysis with automatic artifact management and integration with the quality framework.

Configuration

max_high_severity: Maximum allowed high severity issues (default: 0) max_medium_severity: Maximum allowed medium severity issues (default: 5) min_score: Minimum security score to pass (default: 80.0) verbosity: Output verbosity ('quiet', 'normal', 'verbose') exclude: List of file patterns to exclude from scanning

Initialize security scanner.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Security scanner configuration options

None

Raises:

Type Description
QualityToolError

If bandit is not available

Source code in provide/testkit/quality/security/scanner.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize security scanner.

    Args:
        config: Security scanner configuration options

    Raises:
        QualityToolError: If bandit is not available
    """
    if not BANDIT_AVAILABLE:
        raise QualityToolError("Bandit not available. Install with: pip install bandit", tool="security")

    self.config = config or {}
    self.artifact_dir: Path | None = None
    self.verbosity: VerbosityLevel = self.config.get("verbosity", "normal")

    # Configure logging based on verbosity
    self._configure_logging()
Functions
analyze
analyze(path: Path, **kwargs: Any) -> QualityResult

Run security analysis on the given path.

Parameters:

Name Type Description Default
path Path

Path to analyze

required
**kwargs Any

Additional options: - artifact_dir: Directory for output artifacts - verbosity: Override scanner's verbosity level

{}

Returns:

Type Description
QualityResult

QualityResult with security analysis data

Source code in provide/testkit/quality/security/scanner.py
def analyze(self, path: Path, **kwargs: Any) -> QualityResult:
    """Run security analysis on the given path.

    Args:
        path: Path to analyze
        **kwargs: Additional options:
            - artifact_dir: Directory for output artifacts
            - verbosity: Override scanner's verbosity level

    Returns:
        QualityResult with security analysis data
    """
    self.artifact_dir = kwargs.get("artifact_dir", Path(".provide/output/security"))

    # Allow verbosity override for this specific analysis
    old_verbosity = None
    if "verbosity" in kwargs:
        old_verbosity = self.verbosity
        self.verbosity = kwargs["verbosity"]
        self._configure_logging()

    start_time = time.time()

    try:
        # Run bandit security scan
        result = self._run_bandit_scan(path)
        result.execution_time = time.time() - start_time

        # Generate artifacts
        self._generate_artifacts(result)

        return result

    except Exception as e:
        return QualityResult(
            tool="security",
            passed=False,
            details={"error": str(e), "error_type": type(e).__name__},
            execution_time=time.time() - start_time,
        )
    finally:
        # Restore original verbosity if it was overridden
        if old_verbosity is not None:
            self.verbosity = old_verbosity
            self._configure_logging()
report
report(
    result: QualityResult, format: str = "terminal"
) -> str

Generate report from QualityResult (implements QualityTool protocol).

Parameters:

Name Type Description Default
result QualityResult

Security result

required
format str

Report format

'terminal'

Returns:

Type Description
str

Formatted report

Source code in provide/testkit/quality/security/scanner.py
def report(self, result: QualityResult, format: str = "terminal") -> str:
    """Generate report from QualityResult (implements QualityTool protocol).

    Args:
        result: Security result
        format: Report format

    Returns:
        Formatted report
    """
    if format == "terminal":
        return self._generate_text_report(result)
    elif format == "json":
        return json.dumps(
            {
                "tool": result.tool,
                "passed": result.passed,
                "score": result.score,
                "details": result.details,
            },
            indent=2,
        )
    else:
        return str(result.details)

SemgrepScanner

SemgrepScanner(config: dict[str, Any] | None = None)

Pattern-based static analysis security scanner using Semgrep.

Scans code for security vulnerabilities, bugs, and anti-patterns using customizable pattern rules. Supports many languages including Python, JavaScript, Go, Java, and more.

Initialize Semgrep scanner.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Scanner configuration options. If "config" is not specified, will auto-detect .provide/security/semgrep.yml if it exists.

None
Source code in provide/testkit/quality/security/semgrep_scanner.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize Semgrep scanner.

    Args:
        config: Scanner configuration options. If "config" is not specified,
                will auto-detect .provide/security/semgrep.yml if it exists.
    """
    if not SEMGREP_AVAILABLE:
        raise QualityToolError(
            "Semgrep not available. Install with: pip install semgrep",
            tool="semgrep",
        )

    self.config = config or {}
    self.artifact_dir: Path | None = None

    # Auto-detect config file if not explicitly specified
    if "config" not in self.config:
        default_config = self._get_default_config_path()
        if default_config:
            self.config["config"] = [str(default_config)]
Functions
analyze
analyze(path: Path, **kwargs: Any) -> QualityResult

Run Semgrep analysis on the given path.

Parameters:

Name Type Description Default
path Path

Path to scan

required
**kwargs Any

Additional options including artifact_dir

{}

Returns:

Type Description
QualityResult

QualityResult with security analysis data

Source code in provide/testkit/quality/security/semgrep_scanner.py
def analyze(self, path: Path, **kwargs: Any) -> QualityResult:
    """Run Semgrep analysis on the given path.

    Args:
        path: Path to scan
        **kwargs: Additional options including artifact_dir

    Returns:
        QualityResult with security analysis data
    """
    self.artifact_dir = kwargs.get("artifact_dir", Path(".provide/output/security"))
    start_time = time.time()

    try:
        result = self._run_semgrep_scan(path)
        result.execution_time = time.time() - start_time
        self._generate_artifacts(result)
        return result

    except Exception as e:
        return QualityResult(
            tool="semgrep",
            passed=False,
            details={"error": str(e), "error_type": type(e).__name__},
            execution_time=time.time() - start_time,
        )
report
report(
    result: QualityResult, format: str = "terminal"
) -> str

Generate report from QualityResult.

Source code in provide/testkit/quality/security/semgrep_scanner.py
def report(self, result: QualityResult, format: str = "terminal") -> str:
    """Generate report from QualityResult."""
    if format == "terminal":
        return self._generate_text_report(result)
    elif format == "json":
        return json.dumps(
            {
                "tool": result.tool,
                "passed": result.passed,
                "score": result.score,
                "details": result.details,
            },
            indent=2,
        )
    else:
        return str(result.details)

TruffleHogScanner

TruffleHogScanner(config: dict[str, Any] | None = None)

Deep secret detection scanner using TruffleHog.

Scans codebases for secrets using entropy analysis and pattern matching. Can optionally verify if discovered credentials are still active.

Note: TruffleHog is a Go binary and must be installed separately. Install via: brew install trufflehog (macOS) or download from GitHub releases.

Initialize TruffleHog scanner.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Scanner configuration options. If "config_file" is not specified, will auto-detect .provide/security/trufflehog.yml if it exists.

None
Source code in provide/testkit/quality/security/trufflehog_scanner.py
def __init__(self, config: dict[str, Any] | None = None) -> None:
    """Initialize TruffleHog scanner.

    Args:
        config: Scanner configuration options. If "config_file" is not specified,
                will auto-detect .provide/security/trufflehog.yml if it exists.
    """
    if not TRUFFLEHOG_AVAILABLE:
        raise QualityToolError(
            "TruffleHog not available. Install with: brew install trufflehog (macOS) "
            "or download from https://github.com/trufflesecurity/trufflehog/releases",
            tool="trufflehog",
        )

    self.config = config or {}
    self.artifact_dir: Path | None = None

    # Auto-detect config file if not explicitly specified
    if "config_file" not in self.config:
        default_config = self._get_default_config_path()
        if default_config:
            self.config["config_file"] = default_config
Functions
analyze
analyze(path: Path, **kwargs: Any) -> QualityResult

Run TruffleHog analysis on the given path.

Parameters:

Name Type Description Default
path Path

Path to scan for secrets

required
**kwargs Any

Additional options including artifact_dir

{}

Returns:

Type Description
QualityResult

QualityResult with secret detection data

Source code in provide/testkit/quality/security/trufflehog_scanner.py
def analyze(self, path: Path, **kwargs: Any) -> QualityResult:
    """Run TruffleHog analysis on the given path.

    Args:
        path: Path to scan for secrets
        **kwargs: Additional options including artifact_dir

    Returns:
        QualityResult with secret detection data
    """
    self.artifact_dir = kwargs.get("artifact_dir", Path(".provide/output/security"))
    start_time = time.time()

    try:
        result = self._run_trufflehog_scan(path)
        result.execution_time = time.time() - start_time
        self._generate_artifacts(result)
        return result

    except Exception as e:
        return QualityResult(
            tool="trufflehog",
            passed=False,
            details={"error": str(e), "error_type": type(e).__name__},
            execution_time=time.time() - start_time,
        )
report
report(
    result: QualityResult, format: str = "terminal"
) -> str

Generate report from QualityResult.

Source code in provide/testkit/quality/security/trufflehog_scanner.py
def report(self, result: QualityResult, format: str = "terminal") -> str:
    """Generate report from QualityResult."""
    if format == "terminal":
        return self._generate_text_report(result)
    elif format == "json":
        return json.dumps(
            {
                "tool": result.tool,
                "passed": result.passed,
                "score": result.score,
                "details": result.details,
            },
            indent=2,
        )
    else:
        return str(result.details)