Skip to main content

Regal

Regal is a linter and language server for Rego, making your Rego magnificent, and you the ruler of rules!

With its extensive set of linter rules, documentation and editor integrations, Regal is the perfect companion for policy development, whether you're an experienced Rego developer or just starting out.

illustration of a viking representing the Regal logo
regal
adj : of notable excellence or magnificence : splendid
-- Merriam-Webster

Trusted by Rego developers at these organizations:

logo for miro
logo for cisa
logo for ibm
logo for atlassian
logo for microsoft
logo for boeing
logo for google
logo for elastic

Goals

  • Deliver an outstanding policy development experience by providing the best possible tools for that purpose
  • Identify common mistakes, bugs and inefficiencies in Rego policies, and suggest better approaches
  • Provide advice on best practices, coding style, and tooling
  • Allow users, teams and organizations to enforce custom rules on their policy code

What People Say About Regal

I really like that at each release of Regal I learn something new! Of all the linters I'm exposed to, Regal is probably the most instructive one.

— Leonardo Taccari, NetBSD

Reviewing the Regal rules documentation. Pure gold.

— Dima Korolev, Miro

Such an awesome project!

— Shawn McGuire, Atlassian

I am really impressed with Regal. It has helped me write more expressive and deterministic Rego.

— Jimmy Ray, Boeing

See the adopters file for more Regal users.

Getting Started

Download Regal

MacOS and Linux

brew install styrainc/packages/regal
Other Installation Options

Please see Packages for a list of package repositories which distribute Regal.

Manual installation commands:

MacOS (Apple Silicon)

curl -L -o regal "https://github.com/StyraInc/regal/releases/latest/download/regal_Darwin_arm64"

MacOS (x86_64)

curl -L -o regal "https://github.com/StyraInc/regal/releases/latest/download/regal_Darwin_x86_64"

Linux (x86_64)

curl -L -o regal "https://github.com/StyraInc/regal/releases/latest/download/regal_Linux_x86_64"
chmod +x regal

Windows

curl.exe -L -o regal.exe "https://github.com/StyraInc/regal/releases/latest/download/regal_Windows_x86_64.exe"

Docker

docker pull ghcr.io/styrainc/regal:latest

See all versions, and checksum files, at the Regal releases page, and published Docker images at the packages page.

Try it out!

First, author some Rego!

policy/authz.rego

package authz

import rego.v1

default allow = false

allow if {
isEmployee
"developer" in input.user.roles
}

isEmployee if regex.match("@acmecorp\\.com$", input.user.email)

Next, run regal lint pointed at one or more files or directories to have them linted.

regal lint policy/
Rule:         	non-raw-regex-pattern
Description: Use raw strings for regex patterns
Category: idiomatic
Location: policy/authz.rego:12:27
Text: isEmployee if regex.match("@acmecorp\\.com$", input.user.email)
Documentation: https://docs.styra.com/regal/rules/idiomatic/non-raw-regex-pattern

Rule: use-assignment-operator
Description: Prefer := over = for assignment
Category: style
Location: policy/authz.rego:5:1
Text: default allow = false
Documentation: https://docs.styra.com/regal/rules/style/use-assignment-operator

Rule: prefer-snake-case
Description: Prefer snake_case for names
Category: style
Location: policy/authz.rego:12:1
Text: isEmployee if regex.match("@acmecorp\\.com$", input.user.email)
Documentation: https://docs.styra.com/regal/rules/style/prefer-snake-case

1 file linted. 3 violations found.

Note If you're running Regal on an existing policy library, you may want to disable the style category initially, as it will likely generate a lot of violations. You can do this by passing the --disable-category style flag to regal lint.

Using Regal in Your Editor

Linting from the command line is a great way to get started with Regal, and even for some experienced developers the preferred way to work with the linter. However, not only is Regal a linter, but a full-fledged development companion for Rego development!

Integrating Regal in your favorite editor means you'll get immediate feedback from the linter as you work on your policies. More than that, it'll unlock a whole new set of features that leverage Regal's language server, like context-aware completion suggestions, informative tooltips on hover, or go-to-definition.

Elevate your policy development experience with Regal in VS Code, Zed, Neovim, Helix and other editors!

To learn more about the features provided by the Regal language server, see the Language Server page.

Using Regal in Your Build Pipeline

To ensure Regal's rules are enforced consistently in your project or organization, we've made it easy to run Regal as part of your builds. See the docs on Using Regal in your build pipeline to learn more about how to set up Regal to lint your policies on every commit or pull request.

Rules

Regal comes with a set of built-in rules, grouped by category.

  • bugs: Common mistakes, potential bugs and inefficiencies in Rego policies.
  • custom: Custom, rules where enforcement can be adjusted to match your preferences.
  • idiomatic: Suggestions for more idiomatic constructs.
  • imports: Best practices for imports.
  • style: Rego Style Guide rules.
  • testing: Rules for testing and development.

The following rules are currently available:

CategoryTitleDescription
bugsannotation-without-metadataAnnotation without metadata
bugsargument-always-wildcardArgument is always a wildcard
bugsconstant-conditionConstant condition
bugsdeprecated-builtinAvoid using deprecated built-in functions
bugsduplicate-ruleDuplicate rule
bugsif-empty-objectEmpty object following if
bugsif-object-literalObject literal following if
bugsimpossible-notImpossible not condition
bugsinconsistent-argsInconsistently named function arguments
bugsinternal-entrypointEntrypoint can't be marked internal
bugsinvalid-metadata-attributeInvalid attribute in metadata annotation
bugsleaked-internal-referenceOutside reference to internal rule or function
bugsnot-equals-in-loopUse of != in loop
bugsredundant-existence-checkRedundant existence check
bugsrule-assigns-defaultRule assigned its default value
bugsrule-named-ifRule named "if"
bugsrule-shadows-builtinRule name shadows built-in
bugssprintf-arguments-mismatchMismatch in sprintf arguments count
bugstop-level-iterationIteration in top-level assignment
bugsunassigned-return-valueNon-boolean return value unassigned
bugsunused-output-variableUnused output variable
bugsvar-shadows-builtinVariable name shadows built-in
bugszero-arity-functionAvoid functions without args
customforbidden-function-callForbidden function call
custommissing-metadataPackage or rule missing metadata
customnaming-conventionNaming convention violation
customone-liner-ruleRule body could be made a one-liner
customprefer-value-in-headPrefer value in rule head
idiomaticambiguous-scopeAmbiguous metadata scope
idiomaticboolean-assignmentPrefer if over boolean assignment
idiomaticcustom-has-key-constructCustom function may be replaced by in and object.keys
idiomaticcustom-in-constructCustom function may be replaced by in keyword
idiomaticdirectory-package-mismatchDirectory structure should mirror package
idiomaticequals-pattern-matchingPrefer pattern matching in function arguments
idiomaticno-defined-entrypointMissing entrypoint annotation
idiomaticnon-raw-regex-patternUse raw strings for regex patterns
idiomaticprefer-set-or-object-rulePrefer set or object rule over comprehension
idiomaticuse-containsUse the contains keyword
idiomaticuse-ifUse the if keyword
idiomaticuse-in-operatorUse in to check for membership
idiomaticuse-some-for-output-varsUse some to declare output variables
idiomaticuse-strings-countUse strings.count where possible
importsavoid-importing-inputAvoid importing input
importscircular-importCircular import
importsignored-importReference ignores import
importsimplicit-future-keywordsUse explicit future keyword imports
importsimport-after-ruleImport declared after rule
importsimport-shadows-builtinImport shadows built-in namespace
importsimport-shadows-importImport shadows another import
importsprefer-package-importsPrefer importing packages over rules
importsredundant-aliasRedundant alias
importsredundant-data-importRedundant import of data
importsunresolved-importUnresolved import
importsuse-rego-v1Use import rego.v1
performancedefer-assignmentAssignment can be deferred
performancewalk-no-pathCall to walk can be optimized
performancewith-outside-test-contextwith used outside test context
styleavoid-get-and-list-prefixAvoid get_ and list_ prefix for rules and functions
stylechained-rule-bodyAvoid chaining rule bodies
stylecomprehension-term-assignmentAssignment can be moved to comprehension term
styledefault-over-elsePrefer default assignment over fallback else
styledefault-over-notPrefer default assignment over negated condition
styledetached-metadataDetached metadata annotation
styledouble-negativeAvoid double negatives
styleexternal-referenceExternal reference in function
stylefile-lengthMax file length exceeded
stylefunction-arg-returnFunction argument used for return value
styleline-lengthLine too long
stylemessy-ruleMessy incremental rule
styleno-whitespace-commentComment should start with whitespace
styleopa-fmtFile should be formatted with opa fmt
stylepointless-reassignmentPointless reassignment of variable
styleprefer-snake-casePrefer snake_case for names
styleprefer-some-in-iterationPrefer some .. in for iteration
stylerule-lengthMax rule length exceeded
stylerule-name-repeats-packageRule name repeats package
styletodo-commentAvoid TODO comments
styletrailing-default-ruleDefault rule should be declared first
styleunconditional-assignmentUnconditional assignment in rule body
styleunnecessary-someUnnecessary use of some
styleuse-assignment-operatorPrefer := over = for assignment
styleyoda-conditionYoda condition
testingdubious-print-sprintfDubious use of print and sprintf
testingfile-missing-test-suffixFiles containing tests should have a _test.rego suffix
testingidentically-named-testsMultiple tests with same name
testingmetasyntactic-variableMetasyntactic variable name
testingprint-or-trace-callCall to print or trace function
testingtest-outside-test-packageTest outside of test package
testingtodo-testTODO test encountered

By default, all rules except for those in the custom category are currently enabled.

Aggregate Rules

Most Regal rules will use data only from a single file at a time, with no consideration for other files. A few rules however require data from multiple files, and will therefore collect, or aggregate, data from all files provided for linting. These rules are called aggregate rules, and will only be run when there is more than one file to lint, such as when linting a directory or a whole policy repository. One example of such a rule is the prefer-package-imports rule, which will aggregate package names and imports from all provided policies in order to determine if any imports are pointing to rules or functions rather than packages. You normally won't need to care about this distinction other than being aware of the fact that some linter rules won't be run when linting a single file.

If you'd like to see more rules, please open an issue for your feature request, or better yet, submit a PR! See the custom rules page for more information on how to develop your own rules, for yourself or for inclusion in Regal.

Custom Rules

The custom category is a special one, as the rules in this category allow you to enforce rules that are specific to your project, team or organization. This typically includes things like naming conventions, where you might want to ensure that, for example, all package names adhere to an organizational standard, like having a prefix matching the organization name.

Since these rules require configuration provided by the user, or are more opinionated than other rules, they are disabled by default. In order to enable them, see the configuration options available for each rule for how to configure them according to your requirements.

For more advanced requirements, see the guide on writing custom rules in Rego.

Configuration

A custom configuration file may be used to override the default configuration options provided by Regal. The most common use case for this is to change the severity level of a rule. These three levels are available:

  • ignore — disable the rule entirely
  • warning — report the violation without changing the exit code of the lint command
  • error — report the violation and have the lint command exit with a non-zero exit code (default)

Additionally, some rules may have configuration options of their own. See the documentation page for a rule to learn more about it.

.regal/config.yaml

rules:
style:
todo-comment:
# don't report on todo comments
level: ignore
line-length:
# custom rule configuration
max-line-length: 100
# warn on too long lines, but don't fail
level: warning
opa-fmt:
# not needed as error is the default, but
# being explicit won't hurt
level: error
# files can be ignored for any individual rule
# in this example, test files are ignored
ignore:
files:
- "*_test.rego"
custom:
# custom rule configuration
naming-convention:
level: error
conventions:
# ensure all package names start with "acmecorp" or "system"
- pattern: '^acmecorp\.[a-z_\.]+$|^system\.[a-z_\.]+$'
targets:
- package

capabilities:
from:
# optionally configure Regal to target a specific version of OPA
# this will disable rules that has dependencies to e.g. built-in
# functions or features not supported by the given version
#
# if not provided, Regal will use the capabilities of the latest
# version of OPA available at the time of the Regal release
engine: opa
version: v0.58.0

ignore:
# files can be excluded from all lint rules according to glob-patterns
files:
- file1.rego
- "*_tmp.rego"

project:
roots:
# declares the 'main' and 'lib/jwt' directories as project roots
- main
- lib/jwt

Regal will automatically search for a configuration file (.regal/config.yaml) in the current directory, and if not found, traverse the parent directories either until either one is found, or the top of the directory hierarchy is reached. If no configuration file is found, Regal will use the default configuration.

A custom configuration may be also be provided using the --config-file/-c option for regal lint, which when provided will be used to override the default configuration.

Ignoring Rules

If one of Regal's rules doesn't align with your team's preferences, don't worry! Regal is not meant to be the law, and some rules may not make sense for your project, or parts of it. Regal provides several different methods to ignore rules with varying precedence. The available methods are (ranked highest to lowest precedence):

In summary, the CLI flags will override any configuration provided in the file, and inline ignore directives for a specific line will override any other method.

It's also possible to ignore messages on a per-file basis. The available methods are (ranked High to Lowest precedence):

Ignoring a Rule In Config

If you want to ignore a rule, set its level to ignore in the configuration file:

rules:
style:
prefer-snake-case:
# At example.com, we use camel case to comply with our naming conventions
level: ignore

Ignoring a Category In Config

If you want to ignore a category of rules, set its default level to ignore in the configuration file:

rules:
style:
default:
level: ignore

Ignoring All Rules In Config

If you want to ignore all rules, set the default level to ignore in the configuration file:

rules:
default:
level: ignore
# then you can re-enable specific rules or categories
testing:
default:
level: error
style:
opa-fmt:
level: error

Tip: providing a comment on ignored rules is a good way to communicate why the decision was made.

Ignoring a Rule in Some Files

You can use the ignore attribute inside any rule configuration to provide a list of files, or patterns, that should be ignored for that rule:

rules:
style:
line-length:
level: error
ignore:
files:
# ignore line length in test files to accommodate messy test data
- "*_test.rego"
# specific file used only for testing
- "scratch.rego"

Ignoring Files Globally

Note: Ignoring files will disable most language server features for those files. Only formatting will remain available. Ignored files won't be used for completions, linting, or definitions in other files.

If you want to ignore certain files for all rules, you can use the global ignore attribute in your configuration file:

ignore:
files:
- file1.rego
- "*_tmp.rego"

Inline Ignore Directives

If you'd like to ignore a specific violation in a file, you can add an ignore directive above the line in question, or alternatively on the same line to the right of the expression:

package policy

import rego.v1

# regal ignore:prefer-snake-case
camelCase := "yes"

list_users contains user if { # regal ignore:avoid-get-and-list-prefix
some user in data.db.users
# ...
}

The format of an ignore directive is regal ignore:<rule-name>,<rule-name>..., where <rule-name> is the name of the rule to ignore. Multiple rules may be added to the same ignore directive, separated by commas.

Note that at this point in time, Regal only considers the same line or the line following the ignore directive, i.e. it does not apply to entire blocks of code (like rules, functions or even packages). See configuration if you want to ignore certain rules altogether.

Ignoring Rules via CLI Flags

For development and testing, rules or classes of rules may quickly be enabled or disabled using the relevant CLI flags for the regal lint command:

  • --disable-all disables all rules
  • --disable-category disables all rules in a category, overriding --enable-all (may be repeated)
  • --disable disables a specific rule, overriding --enable-all and --enable-category (may be repeated)
  • --enable-all enables all rules
  • --enable-category enables all rules in a category, overriding --disable-all (may be repeated)
  • --enable enables a specific rule, overriding --disable-all and --disable-category (may be repeated)
  • --ignore-files ignores files using glob patterns, overriding ignore in the config file (may be repeated)

Note: all CLI flags override configuration provided in file.

Project Roots

While many projects consider the project's root directory (in editors often referred to as workspace) their "main" directory for policies, some projects may contain code from other languages, policy "subprojects", or multiple bundles. While most of Regal's features works independently of this — linting, for example, doesn't consider where in a workspace policies are located as long as those locations aren't ignored — some features, like automatically fixing violations, benefit from knowing when a project contains multiple roots.

To provide an example, consider the directory-package-mismatch rule, which states that a file declaring a package path like policy.permissions.users should also be located in a directory structure that mirrors that package, i.e. policy/permissions/users. When a violation against this rule is reported, the regal fix command, or its equivalent Code Action in editors, may when invoked remediate the issue by moving the file to the correct location. But where should the policy/permissions/users directory itself reside?

Normally, the answer to that question would be the project, or workspace root. But if the file was found in a subdirectory containing a bundle, the directory naturally belongs under that bundle's root instead. The roots configuration option under the top-level project object allows you to tell Regal where these roots are, and have features like the directory-package-mismatch fixer work as you'd expect.

project:
roots:
- bundle1
- bundle2

The configuration file is not the only way Regal may determine project roots. Other ways include:

  • A directory containing a .manifest file will automatically be registered as a root
  • A directory containing a .regal directory will be registered as a root (this is normally the project root)

If a feature that depends on project roots fails to identify any, it will either fail or fall back on the directory in which the command was run.

Capabilities

By default, Regal will lint your policies using the capabilities of the latest version of OPA known to Regal (i.e. the latest version of OPA at the time Regal was released). Sometimes you might want to tell Regal that some rules aren't applicable to your project (yet!). As an example, if you're running OPA v0.46.0, you likely won't be helped by the custom-has-key rule, as it suggests using the object.keys built-in function introduced in OPA v0.47.0. The opposite could also be true — sometimes new versions of OPA will invalidate rules that applied to older versions. An example of this is the upcoming introduction of import rego.v1, which will make implicit-future-keywords obsolete, as importing rego.v1 automatically imports all "future" functions.

Capabilities help you tell Regal which features to take into account, and rules with dependencies to capabilities not available or not applicable in the given version will be skipped.

If you'd like to target a specific version of OPA, you can include a capabilities section in your configuration, providing either a specific version of an engine (currently only opa supported):

capabilities:
from:
engine: opa
version: v0.58.0

You can also choose to import capabilities from a file:

capabilities:
from:
file: build/capabilities.json

You can use plus and minus to add or remove built-in functions from the given set of capabilities:

capabilities:
from:
engine: opa
version: v0.58.0
minus:
builtins:
# exclude rules that depend on the http.send built-in function
- name: http.send
plus:
builtins:
# make Regal aware of a custom "ldap.query" function
- name: ldap.query
type: function
decl:
args:
- type: string
result:
type: object

Loading Capabilities from URLs

Starting with Regal version v0.26.0, Regal can load capabilities from URLs with the http, or https schemes using the capabilities.from.url config key. For example, to load capabilities from https://example.org/capabilities.json, this configuration could be used:

capabilities:
from:
url: https://example.org/capabilities.json

Supported Engines

Regal includes capabilities files for the following engines:

EngineWebsiteDescription
opaOPA websiteOpen Policy Agent
eopaEnterprise OPA websiteStyra Enterprise OPA

Exit Codes

Exit codes are used to indicate the result of the lint command. The --fail-level provided for regal lint may be used to change the exit code behavior, and allows a value of either warning or error (default).

If --fail-level error is supplied, exit code will be zero even if warnings are present:

  • 0: no errors were found
  • 0: one or more warnings were found
  • 3: one or more errors were found

This is the default behavior.

If --fail-level warning is supplied, warnings will result in a non-zero exit code:

  • 0: no errors or warnings were found
  • 2: one or more warnings were found
  • 3: one or more errors were found

Output Formats

The regal lint command allows specifying the output format by using the --format flag. The available output formats are:

  • pretty (default) - Human-readable table-like output where each violation is printed with a detailed explanation
  • compact - Human-readable output where each violation is printed on a single line
  • json - JSON output, suitable for programmatic consumption
  • github - GitHub workflow command output, ideal for use in GitHub Actions. Annotates PRs and creates a job summary from the linter report
  • sarif - SARIF JSON output, for consumption by tools processing code analysis reports
  • junit - JUnit XML output, e.g. for CI servers like GitLab that show these results in a merge request.

OPA Check and Strict Mode

Linting with Regal assumes syntactically correct Rego. If there are errors parsing any files during linting, the process is aborted and any parser errors are logged similarly to OPA. OPA itself provides a "linter" of sorts, via the opa check command and its --strict flag. This checks the provided Rego files not only for syntax errors, but also for OPA strict mode violations.

Note It is recommended to run opa check --strict as part of your policy build process, and address any violations reported there before running Regal. Why both commands? Couldn't the strict mode checks be integrated in Regal? That would certainly be an option. However, most of the strict mode checks will be made default / mandatory as part of a future OPA 1.0 release, at which point they'd be made immediately obsolete as part of Regal. There are a few strict mode checks that likely will remain optional in OPA, and we may choose to integrate them into Regal in the future.

Until then, the recommendation is to run both opa check --strict and regal lint as part of your policy build and test process.

Regal Language Server

In order to support linting directly in editors and IDE's, Regal implements parts of the Language Server Protocol (LSP). With Regal installed and available on your $PATH, editors like VS Code (using the OPA extension) and Zed (using the zed-rego extension) can leverage Regal for diagnostics, i.e. linting, and have the results displayed directly in your editor as you work on your Rego policies. The Regal LSP implementation doesn't stop at linting though — it'll also provide features like tooltips on hover, go to definition, and document symbols helping you easily navigate the Rego code in your workspace.

The Regal language server currently supports the following LSP features:

See the documentation page for the language server for an extensive overview of all features, and their meaning.

See the Editor Support page for information about Regal support in different editors.

Resources

  • Contributing contains information about how to hack on Regal itself

Talks

Regal the Rego Linter, CNCF London meetup, June 2023 Regal the Rego Linter

Blogs and Articles

Status

Regal is currently in beta. End-users should not expect any drastic changes, but any API may change without notice. If you want to embed Regal in another project or product, please reach out!

Roadmap

The Regal project roadmap is subject to change, but these are some of the features we're planning to work on in the near future:

Linter

Language Server

  • Make "Check on save" unnecessary by allowing diagnostics to include compilation errors
  • Add Code Lens to "Evaluate" any rule or package (VS Code only, initially)
  • Implement Signature Help feature

The roadmap is updated when all the current items have been completed.

If there's something you'd like to have added to the roadmap, either open an issue, or reach out in the community Slack!

Community

For questions, discussions and announcements related to Styra products, services and open source projects, please join the Styra community on Slack!