OeditusCredo

OeditusCredo

Custom Credo checks for detecting common Elixir/Phoenix anti-patterns, mistakes, and CWE Top 25 security vulnerabilities.

Overview

OeditusCredo provides 40 comprehensive custom Credo checks that detect common mistakes, risky code, and security vulnerabilities in Elixir and Phoenix projects:

Error Handling Anti-patterns

Database & Performance Issues

LiveView & Concurrency Issues

Readability

Code Quality & Maintainability

Refactoring Suggestions

Telemetry & Observability

Security - Injection (CWE-89, CWE-78, CWE-94, CWE-79)

Security - Authentication & Authorization (CWE-306, CWE-862, CWE-863, CWE-639)

Security - Data Protection (CWE-200, CWE-798, CWE-502)

Security - Input & File Handling (CWE-20, CWE-22, CWE-434)

Security - Web (CWE-352, CWE-918)

Security - Race Conditions (CWE-367)

Important Note

All these checks are somewhat opinionated and might produce false positives. If a warning does not apply to your specific case, you can suppress it with # credo:disable-for-next-line or any other Credo config comment directive.

Installation

As a Project Dependency

Add oeditus_credo to your list of dependencies in mix.exs:

def deps do
[
{:oeditus_credo, "~> 0.1.0", only: [:dev, :test], runtime: false}
]
end

Standalone Installation (No Dependency Required)

You can also use OeditusCredo without adding it to your project dependencies:

# Install as a Hex archive (recommended for development)
mix archive.install hex oeditus_credo
# Or download and use the escript executable (best for CI/CD)
curl -L https://github.com/Oeditus/oeditus_credo/releases/latest/download/oeditus_credo -o oeditus_credo
chmod +x oeditus_credo

See STANDALONE.md for detailed standalone usage instructions.

Usage

With Standalone Installation

If you installed OeditusCredo as an archive or escript:

mix oeditus_credo # Run with all checks enabled
mix oeditus_credo --strict # Fail on any issues
mix oeditus_credo lib/my_app # Analyze specific directory

With Project Dependency

Add the checks to your .credo.exs configuration:

%{
configs: [
%{
name: "default",
plugins: [],
requires: [],
checks: %{
enabled: [
# ... existing checks ...
# Error Handling
{OeditusCredo.Check.Warning.MissingErrorHandling, []},
{OeditusCredo.Check.Warning.SilentErrorCase, []},
{OeditusCredo.Check.Warning.SwallowingException, []},
# Database & Performance
{OeditusCredo.Check.Warning.InefficientFilter, []},
{OeditusCredo.Check.Warning.NPlusOneQuery, []},
{OeditusCredo.Check.Warning.MissingPreload, []},
# LiveView & Concurrency
{OeditusCredo.Check.Warning.UnmanagedTask, []},
{OeditusCredo.Check.Warning.SyncOverAsync, []},
{OeditusCredo.Check.Warning.MissingHandleAsync, []},
{OeditusCredo.Check.Warning.MissingThrottle, []},
{OeditusCredo.Check.Warning.InlineJavascript, []},
# Readability
{OeditusCredo.Check.Readability.UnnecessaryInterpolatingSigil, []},
# Code Quality
{OeditusCredo.Check.Warning.DirectStructUpdate, []},
{OeditusCredo.Check.Warning.CallbackHell, [max_nesting: 2]},
{OeditusCredo.Check.Warning.BlockingInPlug, []},
{OeditusCredo.Check.Warning.UnsafeMapAccess, []},
# Refactoring Suggestions
{OeditusCredo.Check.Refactoring.SuggestFSM, []},
# Telemetry & Observability
{OeditusCredo.Check.Warning.MissingTelemetryInObanWorker, []},
{OeditusCredo.Check.Warning.MissingTelemetryInLiveViewMount, []},
{OeditusCredo.Check.Warning.TelemetryInRecursiveFunction, []},
{OeditusCredo.Check.Warning.MissingTelemetryInAuthPlug, []},
{OeditusCredo.Check.Warning.MissingTelemetryForExternalHttp, []},
# Security - Injection
{OeditusCredo.Check.Security.SQLInjection, []},
{OeditusCredo.Check.Security.OSCommandInjection, []},
{OeditusCredo.Check.Security.CodeInjection, []},
{OeditusCredo.Check.Security.XSSVulnerability, []},
# Security - Auth
{OeditusCredo.Check.Security.MissingAuthentication, []},
{OeditusCredo.Check.Security.MissingAuthorization, []},
{OeditusCredo.Check.Security.IncorrectAuthorization, []},
{OeditusCredo.Check.Security.InsecureDirectObjectReference, []},
# Security - Data Protection
{OeditusCredo.Check.Security.SensitiveDataExposure, []},
{OeditusCredo.Check.Security.HardcodedCredentials, []},
{OeditusCredo.Check.Security.UnsafeDeserialization, []},
# Security - Input & File Handling
{OeditusCredo.Check.Security.ImproperInputValidation, []},
{OeditusCredo.Check.Security.PathTraversal, []},
{OeditusCredo.Check.Security.UnrestrictedFileUpload, []},
# Security - Web
{OeditusCredo.Check.Security.MissingCSRFProtection, []},
{OeditusCredo.Check.Security.SSRFVulnerability, []},
# Security - Race Conditions
{OeditusCredo.Check.Security.TOCTOU, []}
]
}
]
]
}

Then run:

mix credo

Change Risk Anti-Patterns (CRAP) score

ChangeRiskAntiPatterns is opt-in and disabled by default because it needs test-coverage data that Credo cannot produce on its own. It combines each function's cyclomatic complexity with its test coverage:

CRAP = complexity^2 * (1 - coverage/100)^3 + complexity

A fully covered function scores its complexity; a complex, untested function scores much higher. The default maximum is 30 (the historical CRAP convention).

IMPORTANT: This check only works when run after generating persisted coverage data. It reads cover/default.coverdata, which is produced by --export-coverage:

mix test --cover --export-coverage default
mix credo

Plain mix test --cover prints a coverage report but does not leave an importable coverage file, so it is not sufficient. When no coverage data is found, the check does nothing by default (so it never breaks a mix credo run launched without coverage). Set require_coverage: true to turn missing coverage into a reported issue instead (useful in CI).

Enable it in .credo.exs:

{OeditusCredo.Check.Refactoring.ChangeRiskAntiPatterns, []}

Parameters: max_score (default 30), coverdata (default "cover/default.coverdata"), exclude_test_files (default true), require_coverage (default false).

Configuration Options

All checks support configuration parameters. Pass them in .credo.exs:

General (Credo-standard) Parameters

Every check accepts the following general parameters provided by Credo:

These parameters can be combined with any check-specific parameters.

Common Check-Specific Parameters

Every OeditusCredo check additionally accepts:

Code Quality

Refactoring Suggestions

LiveView & Concurrency

Telemetry & Observability

Security -- Injection

Security -- Auth

Security -- Data Protection

Security -- Web

Example

# Customise check-specific params
{OeditusCredo.Check.Warning.CallbackHell, [max_nesting: 3]},
{OeditusCredo.Check.Warning.SyncOverAsync, [extra_blocking_modules: [:ExternalAPI]]},
{OeditusCredo.Check.Security.CodeInjection, [extra_dangerous_functions: [:compile_string]]},
{OeditusCredo.Check.Security.HardcodedCredentials, [exclude_test_files: true, extra_credential_terms: ["conn_string"]]},
{OeditusCredo.Check.Warning.MissingTelemetryForExternalHttp, [
extra_http_modules: [{[:MyApp, :HTTP], [:get, :post]}]
]},
# Run check as advisory only (won't affect exit code)
{OeditusCredo.Check.Warning.DirectStructUpdate, exit_status: 0},
# Disable a check entirely
{OeditusCredo.Check.Warning.InlineJavascript, false}

Test Coverage

The library includes comprehensive tests for all 40 checks. Run tests with:

mix test

Run mix test to see the current test count and coverage.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License. See LICENSE for details.