Safeguard
Developer Tools•

How do you write a custom ESLint rule for a security check?

Write a custom ESLint rule in five steps: pick one pattern, inspect the AST, add visitor code, report with a message, and test it before you enable it.

Safeguard Security Team
4 min read

To write a custom ESLint rule, export an object with a meta block and a create function, return visitor methods for the syntax node types you care about, call context.report when a node matches, and test with RuleTester. Start with one narrow pattern, such as a banned call, not a broad class of bugs.

What a custom rule is

A custom rule is a small program that walks the abstract syntax tree (AST) of your code and reports nodes that match a pattern you define. ESLint's documentation says you might create one if the core rules do not cover your use case. Teams usually write them for house conventions and for internal APIs that are unsafe to call in certain ways, such as a wrapper around a shell command.

The five steps

  1. Choose one pattern. Write it as a sentence: "Flag calls to runShell where the first argument is not a string literal." If you cannot say it in one sentence, split it.
  2. Look at the AST. ESLint's docs point to the Code Explorer, which shows the tree for any snippet. Find the node type, for example CallExpression.
  3. Write the rule file. Export meta (type, docs, messages, schema) and a create function.
  4. Report with a messageId. Messages keyed by id keep wording in one place and make tests stable.
  5. Test it. Use RuleTester with at least one valid and one invalid case.

A minimal shape looks like this:

module.exports = {
  meta: {
    type: "problem",
    docs: { description: "Disallow non-literal input to runShell" },
    messages: { nonLiteral: "Pass a fixed string to runShell, not a variable." },
    schema: []
  },
  create(context) {
    return {
      CallExpression(node) {
        if (node.callee.name !== "runShell") return;
        const arg = node.arguments[0];
        if (!arg || arg.type !== "Literal") {
          context.report({ node, messageId: "nonLiteral" });
        }
      }
    };
  }
};

This is an illustration of the structure, not a complete injection detector. It will miss aliased imports and template literals with no placeholders, which is a reminder that a rule only knows what you teach it.

Choosing rule type

ESLint defines three types. A "problem" rule flags code that will cause an error or confusing behavior. A "suggestion" rule flags something that could be done better. A "layout" rule is about whitespace and formatting. Most security checks are "problem" rules, though a style preference for a safer helper is often a "suggestion".

Common mistakes

  • Extending a core rule. ESLint warns that core rules are not part of the public API and that building on them is fragile. If you want something similar, copy the rule file into your project.
  • Forgetting the schema. The schema is mandatory when a rule has options, so invalid configuration is rejected early.
  • Marking a rule fixable without a fixer. The fixable property is required for rules that produce fixes, or ESLint throws an error when the rule tries to.
  • Matching on names only. Matching "runShell" by identifier name misses renamed imports. Decide whether that gap is acceptable and write it down.

Try it yourself

Click through it at your own pace, or open it full screen.

Click a finding and look at how the location, message and code snippet fit together. Your rule's message should give a developer the same three things.

Quick check

Do I need a plugin to use a custom rule?

Not to try one locally, but the ESLint tutorial bundles a rule into a plugin and publishes it so several projects can share it. A plugin is the usual route once more than one repository needs the rule.

Can ESLint rules find every security bug?

No. A rule sees syntax and, at most, limited flow. It is good at banned patterns and weak at questions that need runtime context or business logic.

Safeguard's scanning covers dependencies and SBOMs, and its reachability analysis helps show which findings your code can actually call. You can run a free scan on a repository.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.