ESLint v9 flat config and Semgrep custom rules let teams ban nondeterministic APIs, unsafe I/O, and prompt-time secrets in code. Full breakdown.
Why AI agents need custom lint rules
AI agents write code that looks correct while still introducing risky defaults: random IDs, wall-clock timestamps, unconstrained file access, and secrets pulled from prompts or environment variables. Human review catches some of these, but agents produce volume that review alone cannot cover. Custom lint rules turn team policy into machine-checkable constraints that run on every draft, not only on merge.
The goal is not to block useful generation. It is to ban patterns that make agent output nondeterministic, hard to test, or unsafe when the same prompt is re-run. ESLint with the flat config format and Semgrep custom rules complement each other: one guards JavaScript and TypeScript at the AST level; the other catches cross-language patterns and security-shaped anti-patterns that style linters miss.
ESLint flat config for agent-safe JavaScript
Flat config makes project-wide rules explicit and composable. For agent workflows, define a dedicated config layer that applies to generated paths, agent tool wrappers, and any module that builds prompts. Prefer fail-closed defaults: if a pattern is banned for agents, it should error, not warn, so the agent loop (or CI) cannot “succeed” while still emitting the bad call.
High-value bans for agent code include nondeterministic APIs such as unconstrained random number generation and direct clock reads in pure logic, network or filesystem calls outside approved adapters, and string interpolation of secrets into prompt templates. Encode allowed alternatives as shared helpers so the linter can allowlist a small surface (for example, a seeded RNG helper or a secrets vault accessor) while rejecting ad hoc usage. Document each rule’s intent in the rule message so the agent can self-correct without a human explaining the policy every time.
Semgrep for I/O, secrets, and prompt surfaces
Semgrep shines when the risk is a call graph pattern rather than a single library name. Write rules that flag raw file writes under agent-controlled paths, shell execution with concatenated strings, and HTTP clients that accept arbitrary URLs from model output. Pair those with rules that detect prompt-time secrets: API keys, tokens, or private keys appearing in template literals, chat message construction, or logs that dump full request bodies.
Keep rules concrete and local. Match the exact APIs your stack uses, then expand only when false negatives show up. Version and review Semgrep rules like application code: small diffs, examples of good and bad snippets in the rule metadata, and a path filter so product UI code is not forced into the same constraints as agent runtimes. Run Semgrep in the same gate as ESLint so both layers must pass before generated patches land.
Cheat sheet: what to ban first
- Nondeterminism — unseeded random, live clocks, unordered map iteration in goldens or replayable agent steps; require seeds, fixed clocks, or sorted keys.
- Unsafe I/O — direct filesystem, process, or network calls outside a thin allowlisted adapter; ban path traversal-friendly string concat into open/exec APIs.
- Prompt-time secrets — credentials in prompt strings, system messages, or tool args; force vault references or redacted placeholders.
- Unscoped tool power — open-ended shell, SQL, or cloud SDK usage from agent-authored modules without policy wrappers.
Start with a short allowlist of approved modules, wire ESLint and Semgrep into the agent’s pre-commit or post-edit check, and treat every new false positive as a rule fix rather than a permanent disable. Custom linter rules do not replace design reviews, but they make “no nondeterminism, no free I/O, no secrets in prompts” enforceable every time an agent touches the tree.