Hex.pm HexDocs License: MIT

Unofficial static code analysis checks for the Ash Framework, built as a Credo plugin.

AshCredo detects common anti-patterns, security pitfalls, and missing best practices in your Ash resources and domains by analysing unexpanded source AST.

[!WARNING] This project is experimental and might break frequently.

Installation

AshCredo requires Credo to already be installed in your project.

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

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

Then fetch the dependency:

mix deps.get

Setup

With Igniter

If your project uses Igniter, you can set up AshCredo automatically:

mix ash_credo.install

This will create or update your .credo.exs to include the AshCredo plugin.

Manual

Register the plugin in your .credo.exs configuration:

%{
  configs: [
    %{
      name: "default",
      plugins: [{AshCredo, []}]
    }
  ]
}

Run Credo as usual:

mix credo

Note: Only MissingChangeWrapper is enabled by default. All other checks are opt-in — enable them individually in your .credo.exs (see Configuration).

Checks

CheckCategoryPriorityDefaultDescription
AuthorizeFalseWarningHighNoFlags authorize?: false in Ash API calls — use system actors with bypass policies instead
AuthorizerWithoutPoliciesWarningHighNoDetects resources with Ash.Policy.Authorizer but no policies defined
EmptyDomainWarningNormalNoFlags domains with no resources registered
MissingChangeWrapperWarningHighYesFlags builtin change functions (manage_relationship, set_attribute, ...) used without change wrapper in actions
MissingDomainWarningNormalNoEnsures non-embedded resources set the domain: option
MissingPrimaryKeyWarningHighNoEnsures resources with data layers have a primary key
NoActionsWarningNormalNoFlags resources with data layers but no actions defined
OverlyPermissivePolicyWarningHighNoFlags unscoped authorize_if always() policies
PinnedTimeInExpressionWarningHighNoFlags ^Date.utc_today() / ^DateTime.utc_now() in Ash expressions (frozen at compile time)
SensitiveAttributeExposedWarningHighNoFlags sensitive attributes (password, token, secret, ...) not marked sensitive?: true
SensitiveFieldInAcceptWarningHighNoFlags privilege-escalation fields (is_admin, permissions, ...) in accept lists
WildcardAcceptOnActionWarningHighNoDetects accept :* on create/update actions (mass-assignment risk)
MissingCodeInterfaceDesignLowNoSuggests adding a code_interface for resources with actions
MissingIdentityDesignNormalNoSuggests identities for attributes like email, username, slug
MissingPrimaryActionDesignNormalNoFlags missing primary?: true when multiple actions of the same type exist
MissingTimestampsDesignNormalNoSuggests adding timestamps() to persisted resources
ActionMissingDescriptionReadabilityLowNoFlags actions without a description
BelongsToMissingAllowNilReadabilityNormalNoFlags belongs_to without explicit allow_nil?
LargeResourceRefactorLowNoFlags resource files exceeding 400 lines

Configuration

Only MissingChangeWrapper is enabled by default. Enable additional checks by adding them to the extra section of your .credo.exs:

%{
  configs: [
    %{
      name: "default",
      plugins: [{AshCredo, []}],
      checks: %{
        extra: [
          # Enable checks
          {AshCredo.Check.Warning.AuthorizeFalse, []},
          {AshCredo.Check.Warning.SensitiveFieldInAccept, []},
          {AshCredo.Check.Warning.WildcardAcceptOnAction, []},

          # Enable with custom parameters
          {AshCredo.Check.Refactor.LargeResource, [max_lines: 250]},
          {AshCredo.Check.Warning.SensitiveAttributeExposed, [
            sensitive_names: ~w(password token secret api_key)a
          ]},
          {AshCredo.Check.Design.MissingIdentity, [
            identity_candidates: ~w(email username slug)a
          ]}
        ]
      }
    }
  ]
}

To enable all checks at once:

checks: %{
  extra: [
    {AshCredo.Check.Warning.AuthorizeFalse, []},
    {AshCredo.Check.Warning.AuthorizerWithoutPolicies, []},
    {AshCredo.Check.Warning.EmptyDomain, []},
    {AshCredo.Check.Warning.MissingDomain, []},
    {AshCredo.Check.Warning.MissingPrimaryKey, []},
    {AshCredo.Check.Warning.NoActions, []},
    {AshCredo.Check.Warning.OverlyPermissivePolicy, []},
    {AshCredo.Check.Warning.PinnedTimeInExpression, []},
    {AshCredo.Check.Warning.SensitiveAttributeExposed, []},
    {AshCredo.Check.Warning.SensitiveFieldInAccept, []},
    {AshCredo.Check.Warning.WildcardAcceptOnAction, []},
    {AshCredo.Check.Design.MissingCodeInterface, []},
    {AshCredo.Check.Design.MissingIdentity, []},
    {AshCredo.Check.Design.MissingPrimaryAction, []},
    {AshCredo.Check.Design.MissingTimestamps, []},
    {AshCredo.Check.Readability.ActionMissingDescription, []},
    {AshCredo.Check.Readability.BelongsToMissingAllowNil, []},
    {AshCredo.Check.Refactor.LargeResource, []}
  ]
}

Configurable parameters

The following checks accept custom parameters:

CheckParameterDefaultDescription
Design.MissingIdentityidentity_candidates~w(email username slug handle phone)aAttribute names to suggest adding identities for
Refactor.LargeResourcemax_lines400Maximum line count before triggering
Warning.SensitiveAttributeExposedsensitive_names~w(password hashed_password password_hash token secret api_key private_key ssn)aAttribute names to flag when not marked sensitive?: true
Warning.SensitiveFieldInAcceptdangerous_fields~w(is_admin admin permissions api_key secret_key)aField names to flag when found in accept lists

Contributing

  1. Fork the repository
  2. Create your feature branch (git switch -c my-new-check)
  3. Apply formatting and make sure tests and lints pass (mix format, mix credo, mix test)
  4. Commit your changes
  5. Open a pull request — PR titles must follow the Conventional Commits format (e.g. feat: add check for XY, fix: handle XY edge case)

License

MIT - see LICENSE for details.