README
View Source
OeditusCredo
Custom Credo checks for detecting common Elixir/Phoenix anti-patterns, mistakes, and CWE Top 25 security vulnerabilities.
Overview
OeditusCredo provides 36 comprehensive custom Credo checks that detect common mistakes and security vulnerabilities in Elixir and Phoenix projects:
Error Handling Anti-patterns
- MissingErrorHandling - Detects
{:ok, x} =pattern without error handling - SilentErrorCase - Detects case statements missing error branches
- SwallowingException - Detects try/rescue blocks without logging or re-raising
Database & Performance Issues
- InefficientFilter - Detects
Repo.allfollowed by Enum filtering - NPlusOneQuery - Detects potential N+1 queries (Enum.map with Repo calls)
- MissingPreload - Detects Ecto queries without proper preloading
LiveView & Concurrency Issues
- UnmanagedTask - Detects unsupervised
Task.asynccalls - SyncOverAsync - Detects blocking operations in LiveView/GenServer callbacks
- MissingHandleAsync - Detects blocking in handle_event without async pattern
- MissingThrottle - Detects form inputs without phx-debounce/throttle
- InlineJavascript - Detects inline JS handlers instead of phx-* bindings
Code Quality & Maintainability
- DirectStructUpdate - Detects direct struct updates instead of changesets
- CallbackHell - Detects deeply nested case statements (suggests
with) - BlockingInPlug - Detects blocking operations in Plug functions
Telemetry & Observability
- MissingTelemetryInObanWorker - Detects Oban workers without telemetry instrumentation
- MissingTelemetryInLiveViewMount - Detects LiveView mount/3 without telemetry events
- TelemetryInRecursiveFunction - Detects telemetry inside recursive functions (anti-pattern)
- MissingTelemetryInAuthPlug - Detects auth/authz plugs without telemetry
- MissingTelemetryForExternalHttp - Detects HTTP client calls without telemetry wrapper
Security - Injection (CWE-89, CWE-78, CWE-94, CWE-79)
- SQLInjection - Detects string interpolation/concatenation in Ecto queries
- OSCommandInjection - Detects user input passed to System.cmd/os:cmd
- CodeInjection - Detects dynamic code execution via Code.eval_string
- XSSVulnerability - Detects raw/1 with user input in templates
Security - Authentication & Authorization (CWE-306, CWE-862, CWE-863, CWE-639)
- MissingAuthentication - Detects controllers/routers without authentication plugs
- MissingAuthorization - Detects Phoenix actions without authorization checks
- IncorrectAuthorization - Detects role checks using negation/!= patterns
- InsecureDirectObjectReference - Detects direct DB lookups from user params without ownership checks
Security - Data Protection (CWE-200, CWE-798, CWE-502)
- SensitiveDataExposure - Detects sensitive fields in JSON responses and inspect output
- HardcodedCredentials - Detects hardcoded passwords, API keys, tokens, and secrets
- UnsafeDeserialization - Detects :erlang.binary_to_term without the :safe option
Security - Input & File Handling (CWE-20, CWE-22, CWE-434)
- ImproperInputValidation - Detects missing validation of external input
- PathTraversal - Detects user input in file paths without sanitization
- UnrestrictedFileUpload - Detects file uploads without content-type validation
Security - Web (CWE-352, CWE-918)
- MissingCSRFProtection - Detects API pipelines without CSRF protection
- SSRFVulnerability - Detects HTTP requests with user-controlled URLs
Security - Race Conditions (CWE-367)
- TOCTOU - Detects time-of-check/time-of-use patterns (File.exists? then File.read)
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}
]
endStandalone 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, []},
# Code Quality
{OeditusCredo.Check.Warning.DirectStructUpdate, []},
{OeditusCredo.Check.Warning.CallbackHell, [max_nesting: 2]},
{OeditusCredo.Check.Warning.BlockingInPlug, []},
# 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
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:
false-- Disable a check entirely. When a check tuple usesfalseinstead of a keyword list, the check is skipped and produces no issues.# Disable a check {OeditusCredo.Check.Warning.NPlusOneQuery, false}exit_status(integer()) -- Override the exit status contributed by issues from this check. By default, all checks in the:warningcategory contribute exit status16. Settingexit_status: 0means the check still runs and reports issues, but they will not cause a non-zero exit code.# Run the check but don't fail CI on its issues {OeditusCredo.Check.Warning.NPlusOneQuery, exit_status: 0} # Custom exit status {OeditusCredo.Check.Security.SQLInjection, exit_status: 2}priority-- Override the base priority for the check (:low,:normal,:high,:higher, or:ignore).files-- Restrict which files the check runs on:{OeditusCredo.Check.Security.SQLInjection, files: %{included: ["lib/my_app/repo.ex"]}}
These parameters can be combined with any check-specific parameters.
Common Check-Specific Parameters
Every OeditusCredo check additionally accepts:
exclude_test_files(boolean(), default:false) -- When set totrue, files ending in_test.exsor located under a/test/directory are skipped.
Code Quality
- CallbackHell:
max_nesting-- Maximum allowed case nesting depth (default:2) - DirectStructUpdate:
extra_struct_patterns-- Additional regex strings for struct-like variable names (default:[]) - BlockingInPlug:
extra_blocking_modules-- Additional module atoms to treat as blocking (default:[])
LiveView & Concurrency
- SyncOverAsync:
extra_blocking_modules-- Additional blocking module atoms (default:[]);callback_functions-- Callback names to check (default:[:handle_event, :handle_call, :handle_info, :handle_cast, :handle_continue]) - MissingHandleAsync:
extra_blocking_modules-- Additional blocking module atoms (default:[])
Telemetry & Observability
- MissingTelemetryForExternalHttp:
extra_http_modules-- Additional{module_parts, [functions]}tuples (default:[]) - MissingTelemetryInAuthPlug:
extra_auth_plug_names-- Additional auth plug name substrings (default:[])
Security -- Injection
- CodeInjection:
extra_dangerous_functions-- AdditionalCode.*function atoms to flag (default:[])
Security -- Auth
- MissingAuthentication:
sensitive_actions-- Controller actions requiring auth (default:[:index, :show, :create, :new, :update, :edit, :delete, :destroy]) - MissingAuthorization:
extra_auth_indicators-- Additional authorization indicator substrings (default:[]) - IncorrectAuthorization:
extra_auth_indicators-- Additional authorization indicator substrings (default:[]) - InsecureDirectObjectReference:
extra_ownership_indicators-- Additional ownership/auth indicator substrings (default:[])
Security -- Data Protection
- SensitiveDataExposure:
extra_sensitive_terms-- Additional sensitive field substrings (default:[]) - HardcodedCredentials:
extra_credential_terms-- Additional credential name substrings (default:[])
Security -- Web
- SSRFVulnerability:
extra_http_modules-- Additional HTTP module atom lists, e.g.[[:MyHTTP]](default:[])
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 36 checks. Run tests with:
mix test
Current test coverage: 92 tests covering all checks, including security vulnerability detection and telemetry instrumentation.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License. See LICENSE for details.