Search documentation of Ragex

# Ragex.Analysis.BusinessLogic (Ragex v0.4.0)

View Source

Business logic analysis using Metastatic analyzers.

Provides unified access to 33 language-agnostic business logic analyzers that detect common anti-patterns, security vulnerabilities, and issues across multiple languages.

## Semantic Analysis with OpKind

Many analyzers leverage Metastatic's OpKind semantic metadata system for accurate detection. OpKind tags function calls with their semantic meaning:

- Domain: `:db`, `:http`, `:auth`, `:cache`, `:queue`, `:file`, `:external_api`
- Operation: `:retrieve`, `:create`, `:update`, `:delete`, `:query`, etc.
- Framework: `:ecto`, `:django`, `:activerecord`, `:requests`, etc.

## Location Information

Note: Business logic analyzers operate at the MetaAST (M2) abstraction level, which intentionally abstracts away language-specific details like line numbers. This is what makes them language-agnostic. As a result, precise line/column location information is typically not available. Issues will include file paths and function context when available.

## Analyzers

### Tier 1: Pure MetaAST (Language-Agnostic)

- CallbackHell - Detects deeply nested conditionals (M2.1 Core)
- MissingErrorHandling - Pattern matching without error case (M2.2 Extended)
- SilentErrorCase - Conditionals with only success path (M2.1 Core)
- SwallowingException - Exception handling without logging (M2.2 Extended)
- HardcodedValue - Hardcoded URLs/IPs in literals (M2.1 Core)
- NPlusOneQuery - DB queries in collection operations (M2.2 Extended)
- InefficientFilter - Fetch-all then filter pattern (M2.2 Extended)
- UnmanagedTask - Unsupervised async operations (M2.2 Extended)
- TelemetryInRecursiveFunction - Metrics in recursive functions (M2.1 Core)

### Tier 2: Function Name Heuristics

- MissingTelemetryForExternalHttp - HTTP calls without telemetry
- SyncOverAsync - Blocking operations in async contexts
- DirectStructUpdate - Struct updates bypassing validation
- MissingHandleAsync - Unmonitored async operations

### Tier 3: Naming Conventions

- BlockingInPlug - Blocking I/O in middleware
- MissingTelemetryInAuthPlug - Auth checks without audit logging
- MissingTelemetryInLiveviewMount - Component lifecycle without metrics
- MissingTelemetryInObanWorker - Background jobs without telemetry

### Tier 4: Content Analysis

- MissingPreload - Database queries without eager loading
- InlineJavascript - Inline scripts in strings (XSS risk)
- MissingThrottle - Expensive operations without rate limiting

### Tier 5: Security (CWE-based)

- SQLInjection - SQL query string concatenation (CWE-89)
- XSSVulnerability - Cross-site scripting risks (CWE-79)
- SSRFVulnerability - Server-side request forgery (CWE-918)
- PathTraversal - Directory traversal attacks (CWE-22)
- InsecureDirectObjectReference - IDOR vulnerabilities (CWE-639)
- MissingAuthentication - Unprotected endpoints (CWE-306)
- MissingAuthorization - Missing access control (CWE-862)
- IncorrectAuthorization - Flawed access control (CWE-863)
- MissingCSRFProtection - CSRF vulnerabilities (CWE-352)
- SensitiveDataExposure - Data leaks in logs/responses (CWE-200)
- UnrestrictedFileUpload - Unsafe file uploads (CWE-434)
- ImproperInputValidation - Input validation issues (CWE-20)

### Tier 6: Race Conditions

- TOCTOU - Time-of-check-time-of-use vulnerabilities (CWE-367)

## Usage

    alias Ragex.Analysis.BusinessLogic

    # Analyze single file
    {:ok, result} = BusinessLogic.analyze_file("lib/my_module.ex")

    # Check for issues
    result.has_issues?     # => true/false
    result.total_issues    # => 5
    result.critical_count  # => 1

    # Analyze directory
    {:ok, results} = BusinessLogic.analyze_directory("lib/")

    # Run specific analyzers
    {:ok, result} = BusinessLogic.analyze_file("lib/my_module.ex",
      analyzers: [:callback_hell, :missing_error_handling])

    # Filter by severity
    {:ok, results} = BusinessLogic.analyze_directory("lib/",
      min_severity: :high)

    # Generate report
    report = BusinessLogic.audit_report(results)

## Summary

### Types

analysis_result()
directory_result()
issue()
location()

### Functions

analyze_directory(path, opts \\ [])

Analyzes all files in a directory for business logic issues.

analyze_file(path, opts \\ [])

Analyzes a single file for business logic issues.

audit_report(results)

Generates a comprehensive business logic audit report.

available_analyzers()

Returns the list of available business logic analyzers.

recommendation(analyzer)

Returns the recommendation for a specific analyzer.

## Types

# analysis_result()

    @type analysis_result() :: %{
      file: String.t(),
      language: atom(),
      issues: [issue()],
      has_issues?: boolean(),
      total_issues: non_neg_integer(),
      critical_count: non_neg_integer(),
      high_count: non_neg_integer(),
      medium_count: non_neg_integer(),
      low_count: non_neg_integer(),
      info_count: non_neg_integer(),
      by_analyzer: %{required(atom()) => non_neg_integer()},
      timestamp: DateTime.t()
    }

# directory_result()

    @type directory_result() :: %{
      total_files: non_neg_integer(),
      files_with_issues: non_neg_integer(),
      total_issues: non_neg_integer(),
      by_severity: %{required(atom()) => non_neg_integer()},
      by_analyzer: %{required(atom()) => non_neg_integer()},
      results: [analysis_result()],
      summary: String.t()
    }

# issue()

    @type issue() :: %{
      analyzer: atom(),
      category: atom(),
      severity: :critical | :high | :medium | :low | :info,
      description: String.t(),
      suggestion: String.t() | nil,
      context: map(),
      location: location() | nil
    }

# location()

    @type location() :: %{
      line: non_neg_integer() | nil,
      column: non_neg_integer() | nil,
      function: String.t() | nil
    }

## Functions

# analyze_directory(path, opts \\ [])

    @spec analyze_directory(
      String.t(),
      keyword()
    ) :: {:ok, directory_result()} | {:error, term()}

Analyzes all files in a directory for business logic issues.

## Options

- `:recursive` - Recursively analyze subdirectories (default: true)
- `:parallel` - Use parallel processing (default: true)
- `:max_concurrency` - Maximum concurrent analyses (default: System.schedulers_online())
- Plus all options from `analyze_file/2`

## Examples

    {:ok, results} = BusinessLogic.analyze_directory("lib/")
    total_issues = results.total_issues

# analyze_file(path, opts \\ [])

    @spec analyze_file(
      String.t(),
      keyword()
    ) :: {:ok, analysis_result()} | {:error, term()}

Analyzes a single file for business logic issues.

## Options

- `:analyzers` - List of analyzer names to run (default: all)
- `:language` - Explicit language (default: auto-detect)
- `:min_severity` - Minimum severity to report (default: :info)
- `:config` - Configuration map for analyzers

## Examples

    {:ok, result} = BusinessLogic.analyze_file("lib/my_module.ex")
    result.has_issues?  # => false

    {:ok, result} = BusinessLogic.analyze_file("lib/my_module.ex",
      analyzers: [:callback_hell, :missing_error_handling],
      min_severity: :high)

# audit_report(results)

    @spec audit_report([analysis_result()]) :: map()

Generates a comprehensive business logic audit report.

Returns a formatted map with:

- Summary statistics
- Issues grouped by severity
- Issues grouped by analyzer
- Recommendations

## Examples

    {:ok, results} = BusinessLogic.analyze_directory("lib/")
    report = BusinessLogic.audit_report(results.results)
    IO.puts(report.summary)

# available_analyzers()

    @spec available_analyzers() :: [atom()]

Returns the list of available business logic analyzers.

## Examples

    iex> Ragex.Analysis.BusinessLogic.available_analyzers()
    [:callback_hell, :missing_error_handling, ...]

# recommendation(analyzer)

    @spec recommendation(atom()) :: String.t()

Returns the recommendation for a specific analyzer.

For security analyzers, includes CWE reference numbers.

## Examples

    iex> Ragex.Analysis.BusinessLogic.recommendation(:sql_injection)
    "CWE-89: Use parameterized queries or Ecto's query DSL..."