Home Posts Custom Linter Rules for AI Agents [Cheat Sheet 2026]
Developer Reference

Custom Linter Rules for AI Agents [Cheat Sheet 2026]

Custom Linter Rules for AI Agents [Cheat Sheet 2026]
Dillip Chowdary
Dillip Chowdary
Tech Entrepreneur & Innovator · April 30, 2026 · 12 min read

Bottom Line

If agent code can reach randomness, wall-clock time, network, shell, or secrets without an explicit policy, encode that as a lint failure. Use ESLint for fast AST-local guardrails and Semgrep to spread the same policy across repos and languages.

Key Takeaways

  • Use ESLint for AST-local bans and Semgrep for reusable cross-repo security patterns.
  • In ESLint v9, flat config lives in eslint.config.js and rule tests use RuleTester.
  • Semgrep custom rules require id, message, severity, languages, and a pattern key.
  • Ban randomness, wall-clock time, unrestricted network, shell exec, and prompt-time secrets by default.

Custom linter rules are one of the simplest ways to make AI-agent code auditable. Instead of trusting prompt discipline or PR reviews, you can encode hard bans on nondeterministic APIs, uncontrolled network access, shell execution, and secret leakage directly into CI. This cheat sheet uses ESLint v9 for JavaScript rule authoring and Semgrep for cross-repo pattern enforcement, with commands, config templates, UI helpers, and testing shortcuts you can lift into your own toolchain.

  • Use ESLint for AST-local bans and Semgrep for reusable cross-repo security patterns.
  • In ESLint v9, flat config lives in eslint.config.js and rule tests use RuleTester.
  • Semgrep custom rules require id, message, severity, languages, and a pattern key.
  • Ban randomness, wall-clock time, unrestricted network, shell exec, and prompt-time secrets by default.

What To Enforce

Bottom Line

If an agent path can touch randomness, time, network, shell, or secrets without an explicit allowlist, treat it as a build-breaking lint violation.

Security Guardrails

  • Ban direct child_process execution from agent orchestration code unless a wrapper module is approved.
  • Reject raw fetch() or SDK calls unless the hostname or client is allowlisted.
  • Flag prompt construction that interpolates environment secrets, tokens, or credential-like constants.
  • Disallow filesystem writes outside designated scratch paths for autonomous tasks.

Determinism Guardrails

  • Reject Math.random() and similar entropy sources in planning, routing, and tool-selection paths.
  • Reject Date.now() and new Date() where replayable execution matters; require injected clocks instead.
  • Ban implicit process state such as unscoped process.env access inside core decision logic.
  • Require stable wrappers for retries, backoff, and concurrency so behavior is testable under fixtures.
Watch out: A custom rule is strongest when it bans capabilities at the boundary, not when it tries to infer intent from prompt text alone.

Commands By Purpose

Author And Run Local Rules

npx eslint .
npx eslint . --fix
npx eslint --inspect-config src/agent.js

Run A Shared Semgrep Rule Pack

semgrep scan --config semgrep/rules .
semgrep scan --config p/javascript --config semgrep/rules .
semgrep scan --config semgrep/rules --metrics=off .

Test Rules Before CI

node tests/no-nondeterminism.test.js
semgrep --test --config semgrep/rules/ semgrep/tests/
semgrep --validate --config semgrep/rules/no-secrets.yaml

Export Findings For Review Tooling

npx eslint . -f json -o reports/eslint.json
semgrep scan --config semgrep/rules --json --output reports/semgrep.json .
semgrep scan --config semgrep/rules --sarif-output reports/semgrep.sarif .
  • Use --fix only for deterministic rewrites such as replacing banned helpers with approved wrappers.
  • Use --inspect-config to confirm the exact rule set applied to a target file in ESLint v9.
  • Use --validate and --test to keep a growing Semgrep rule library sane.

Configuration

ESLint v9 Flat Config

ESLint custom rules are documented in the official custom rules guide, and plugin wiring for flat config is covered in the plugin configuration docs.

const { defineConfig } = require("eslint/config");
const agentRules = require("./eslint-plugin-agent-guardrails");

module.exports = defineConfig([
  {
    files: ["src/agents/**/*.js"],
    plugins: { agent: agentRules },
    rules: {
      "agent/no-nondeterminism": "error",
      "agent/no-raw-network": ["error", { allowModules: ["./http-client"] }],
      "agent/no-secrets-in-prompts": "error"
    }
  }
]);

Minimal ESLint Rule Skeleton

module.exports = {
  meta: {
    type: "problem",
    docs: { description: "Disallow nondeterministic APIs in agent paths" },
    schema: [],
    messages: {
      noRandom: "Use an injected deterministic source instead of {{name}}."
    }
  },
  create(context) {
    return {
      CallExpression(node) {
        const callee = context.sourceCode.getText(node.callee);
        if (callee === "Math.random") {
          context.report({
            node,
            messageId: "noRandom",
            data: { name: callee }
          });
        }
      }
    };
  }
};

Semgrep Rule Pack

Semgrep rule structure and operators are documented in the official rule syntax and pattern syntax references.

rules:
  - id: agent-no-randomness
    message: Avoid nondeterministic randomness in agent execution paths.
    severity: HIGH
    languages: [javascript, typescript]
    pattern-either:
      - pattern: Math.random(...)
      - pattern: crypto.randomUUID(...)

  - id: agent-no-direct-fetch
    message: Route outbound calls through the approved client wrapper.
    severity: HIGH
    languages: [javascript, typescript]
    pattern: fetch(...)
  • For Semgrep, every rule needs id, message, severity, languages, and one of pattern, patterns, pattern-either, or pattern-regex.
  • For fixture files, scrub prompts and sample credentials before committing them; TechBytes' Data Masking Tool is a simple fit for that workflow.

Live Search Filter

A cheat sheet gets dramatically more useful when readers can filter commands by tool, risk domain, or task. The pattern below keeps the UI static-site friendly and works with a sticky ToC.

<input id='ruleFilter' type='search' placeholder='Filter commands, rules, or flags' />
<div id='ruleCards'>
  <article data-tags='eslint deterministic random rule tester'>ESLint: ban randomness</article>
  <article data-tags='semgrep secrets prompt security'>Semgrep: prompt secret rule</article>
  <article data-tags='network fetch allowlist security'>Network allowlist checks</article>
</div>

<script>
  const input = document.getElementById('ruleFilter');
  const cards = [...document.querySelectorAll('#ruleCards [data-tags]')];

  input.addEventListener('input', (event) => {
    const query = event.target.value.trim().toLowerCase();
    cards.forEach((card) => {
      const haystack = card.dataset.tags.toLowerCase();
      card.hidden = query !== '' && !haystack.includes(query);
    });
  });
</script>
  • Keep the search index in data-tags so you can match commands, flags, and concepts without extra parsing.
  • Use native hidden toggling to avoid layout thrash and framework overhead.
  • Pre-seed tags with terms developers actually search for: random, fetch, secrets, RuleTester, --validate.

Keyboard Shortcuts

If you publish an internal lint-rule catalog, add lightweight keyboard affordances. They matter more than animation when engineers are scanning during PR review or incident cleanup.

Shortcut Action Why It Helps
/ Focus live filter Fastest path to a specific rule or flag.
Esc Clear filter Resets the full command catalog instantly.
j Next result Keeps navigation keyboard-first in long references.
k Previous result Pairs naturally with j for scanning.
c Copy current block Ideal for command snippets and rule templates.
g t Jump to ToC Useful once the page grows beyond one screen.

Advanced Usage

Test ESLint Rules With Stable Defaults

The ESLint v9 migration notes matter here: RuleTester now uses flat-config defaults unless you override them.

const { RuleTester } = require("eslint");
const rule = require("../rules/no-nondeterminism");

const tester = new RuleTester({
  languageOptions: {
    ecmaVersion: "latest",
    sourceType: "module"
  }
});

tester.run("no-nondeterminism", rule, {
  valid: [{ code: "const rng = seededRandom(seed);" }],
  invalid: [
    {
      code: "const x = Math.random();",
      errors: [{ messageId: "noRandom" }]
    }
  ]
});

Reduce False Positives Without Weakening Policy

  • Prefer explicit wrapper allowlists over broad exceptions like “ignore test files.”
  • In Semgrep, use pattern-not to carve out approved variants rather than deleting the whole rule.
  • In ESLint, define meta.schema and, when needed, defaultOptions so teams cannot misconfigure the rule silently.

Version And Publish Rule Packs Carefully

  • Package ESLint rules as a plugin once more than one repo needs them.
  • Use Semgrep min-version and max-version when a rule depends on newer syntax support.
  • Export JSON or SARIF in CI so findings are diffable, reviewable, and measurable over time.
Pro tip: The best agent-security rule packs are small and opinionated. Start with five hard bans, wire them into tests, and expand only after you have clean remediation paths.

Frequently Asked Questions

How do I write a custom ESLint rule for AI-agent code? +
Create a rule module with meta and create(context), then report banned AST patterns with context.report(). In ESLint v9, wire it into eslint.config.js as a plugin and test it with RuleTester.
When should I use Semgrep instead of ESLint for agent security rules? +
Use ESLint when the problem is local to JavaScript syntax and you want fast editor feedback. Use Semgrep when the same policy needs to span multiple repos or languages, or when you want a shared rule pack for CI enforcement.
What are the first APIs to ban for deterministic AI agents? +
Start with Math.random(), Date.now(), direct fetch(), raw shell execution, and secret-bearing prompt interpolation. Those five categories cover the most common ways agent runs become non-replayable or unsafe.
How do I test Semgrep custom rules before shipping them? +
Use semgrep --test --config RULES_DIR TARGETS_DIR with annotated fixtures such as ruleid: and ok:. Run semgrep --validate --config rule.yaml as well to catch malformed or incomplete rule definitions early.

Get Engineering Deep-Dives in Your Inbox

Weekly breakdowns of architecture, security, and developer tooling — no fluff.

Found this useful? Share it.