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.

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

Installation

AshCredo requires Credo to already be installed in your project.

If your project uses Igniter, a single command will add the dependency and register the plugin in your .credo.exs:

mix igniter.install ash_credo --only dev,test

This keeps ash_credo scoped to the same :dev/:test environments as credo. The installer also sets runtime: false.

Manual

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

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

Then fetch the dependency and register the plugin in your .credo.exs:

mix deps.get
# .credo.exs
%{
  configs: [
    %{
      name: "default",
      plugins: [{AshCredo, []}]
    }
  ]
}

Running

mix credo

Checks

CheckCategoryPriorityDefaultDescription
AuthorizeFalseWarningHighNoFlags literal authorize?: false in Ash calls, action DSL, and (by default) any other call site
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
UseCodeInterfaceRefactorNormalNoFlags Ash.* calls where both resource and action are literals — use a code interface function instead

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, []},
    {AshCredo.Check.Refactor.UseCodeInterface, []}
  ]
}

Configurable parameters

The following checks accept custom parameters:

CheckParameterDefaultDescription
Warning.AuthorizeFalseinclude_non_ash_callstrueWhen false, only checks Ash API calls and action DSL definitions
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 test, mix lint)
  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.