Rule catalog

This catalog is read directly from the shipped rule packs and the native analysis registry, and a meta-test (packages/engine/tests/rule_contracts.rs) machine-checks that every id listed below matches what the engine actually loads at runtime, so the catalog cannot silently drift out of sync with the code. Every rule below carries its own inline suppress marker, and every rule and native analysis id can also be turned off per-run — in zzop.config.jsonc via rules: { "<id>": "off" }, or via disabledRules for embedders.

be-db

Rule idSeverityMatcherSuppress markerDetects
update-delete-no-wherecriticalmethod-scanno-where-okupdateMany/deleteMany called with no where: clause anywhere in the enclosing function — a whole-table write.
pagination-no-orderbywarningmethod-scanpagination-okskip/take pagination used with no orderBy anywhere in the enclosing function — page boundaries can shift between requests without a stable sort.
client-per-requestwarningmethod-scanprisma-client-oknew PrismaClient() constructed inside a function that also looks like a request handler — exhausts the DB connection pool under load.
external-call-in-txwarningmethod-scantx-egress-okA network call (fetch/axios/got) in the same function as a $transaction( — extends transaction lock hold time across a network round-trip.
unawaited-writewarningline-scanunawaited-okA DB write (create/update/delete/upsert) on a DB-client-shaped receiver (prisma/db/tx/client/repo/...) whose promise is neither awaited, returned, nor chained — fire-and-forget; a failed write looks identical to a successful one.
unbounded-user-limitwarningline-scanlimit-okA take/limit pagination size read directly from user input (req.query/req.params/req.body) with no upper-bound clamp — unbounded page size, a cheap memory/CPU exhaustion vector.
find-then-create-no-uniquewarningmethod-scanfind-create-okA findFirst/findOne/findUnique read followed by .create( in the same function with no connectOrCreate/upsert/$transaction anywhere — check-then-act race, concurrent requests can create duplicate rows.
float-money-compareinfoline-scanmoney-okA money-named identifier (price/amount/balance/fee/cost) compared with ==/=== against a float literal — IEEE754 rounding makes strict equality on monetary values unreliable.
empty-catch-on-writewarningmethod-scanempty-catch-okA DB write (create/update/delete/upsert/updateMany/deleteMany) in the same function as an empty catch {} — write failure is silently discarded.
multi-write-no-txwarningmethod-scanmulti-write-tx-okA create-family write (create/createMany/insert) and a mutate-family write (update/delete/upsert/...) in the same function with no $transaction(/SQL BEGIN — a failure between the two leaves partial state (co-occurrence heuristic; independent writes suppress with the marker).
non-atomic-counter-updatewarningmethod-scanatomic-counter-okA findUnique/findFirst/findOne/findById read plus a field: value +/- 1 arithmetic update in the same function with no atomic increment:/decrement:/$inc/FOR UPDATE — a read-modify-write counter that loses updates under concurrency.
connection-no-releasewarningmethod-scanconnection-release-okpool.connect(/getConnection(/acquireConnection( with no .release(/.destroy(/.end(, no return conn, and no using/await using declaration in the same function — a connection leak under load (release in a callee still fires; verify before refactoring).

be-reliability

Rule idSeverityMatcherSuppress markerDetects
async-route-no-catchwarningmethod-scanroute-catch-okAsync Express/router handler registered with no try/catch, next(err), or .catch() — an unhandled rejection can crash the process or hang the request.
sync-fs-in-handlerwarningmethod-scansync-io-okSynchronous fs/child_process call alongside request-handler context (req/res/ctx/...) — blocks Node's single event loop for every concurrent request.
await-in-mapwarningmethod-scanmap-async-ok.map(async ...) used without Promise.all/Promise.allSettled — rejections become unhandled, ordering/completion guarantees are lost.
env-nonnull-assertwarningline-scanenv-assert-okprocess.env.X! non-null assertion — defers a missing-config crash from startup to first use.
debug-true-committedwarningline-scandebug-okDebug flag or disabled TLS verification (debug: true, NODE_TLS_REJECT_UNAUTHORIZED=0, rejectUnauthorized: false) committed to source.
promise-all-writeswarningmethod-scanpromise-all-okPromise.all(...) used alongside DB write calls (create/update/delete/upsert) — partial-failure non-atomicity, no rollback for writes that already committed.
json-parse-no-trywarningmethod-scanjson-parse-okJSON.parse(...) called on apparent external input (req/body/params/query/...) with no surrounding try — malformed input throws instead of producing a handled 4xx.
fetch-no-timeoutwarningmethod-scanfetch-timeout-okOutbound HTTP call (fetch/axios/got) on a backend-looking path with no timeout/AbortController visible in the same function — a hung upstream hangs the request indefinitely.
process-exit-in-libwarningmethod-scanprocess-exit-okprocess.exit(...) called inside a function outside scripts//tools//bin/ — skips cleanup and kills the whole server process, not just the current request.
body-limit-missinginfoline-scanbody-limit-okBody parser (express.json/urlencoded/bodyParser.*) configured with no explicit limit — relies on the implicit 100kb default: too small for some endpoints, unexamined for the rest.
console-in-beinfoline-scanconsole-okconsole.* call in backend-path source (api//server//backend//be//routes//controllers//services/) — unstructured, synchronous, not queryable.
interval-no-clearwarningline-scaninterval-oksetInterval(...) with no matching clearInterval(...) anywhere in the file — a leaked timer keeps the process/page alive and re-fires forever.
env-outside-configinfoline-scanenv-access-okprocess.env.X accessed outside a config module (files/dirs named config/env/settings exempt) — scatters env parsing/validation instead of centralizing it.

be-security

Rule idSeverityMatcherSuppress markerDetects
hardcoded-secretwarningline-scansecret-okHardcoded secret-shaped literal (API key/password/token assignment, or a known cloud-key prefix).
mass-assignmentwarningmethod-scanmass-assignment-okreq.body (or a spread of it) passed directly into a database write in the same function — lets a caller set fields the handler never intended to expose.
raw-query-interpolationcriticalline-scanraw-sql-ok$queryRawUnsafe/$executeRawUnsafe called — no parameterization, so any interpolated request-derived string is a SQL injection.
insecure-cookiewarningmethod-scancookie-okA cookie is set (res.cookie/setCookie/cookies.set) with no httpOnly anywhere in the same function body.
cors-wildcardwarningline-scancors-okCORS origin set to * — defeats the same-origin protection CORS exists to provide.
weak-password-hashcriticalline-scanweak-hash-okPassword hashed/compared with MD5/SHA-1, or bcrypt configured at a single-digit cost factor.
api-key-in-urlwarningline-scanurl-key-okA secret-shaped query parameter (api_key/access_token/token/secret) appears in a URL — leaks via proxy/browser/Referer logs.
annotation-sql-concatcriticalline-scanquery-concat-okJPA @Query annotation built via string concatenation — attacker-controlled input spliced into SQL/JPQL.
open-redirectwarningmethod-scanredirect-okredirect(...) called in a function that also reads req.query/req.params/req.body — unchecked request-derived redirect target, a phishing/OAuth-callback token-theft vector.
ssrf-user-urlwarningmethod-scanssrf-okAn outbound HTTP call (fetch/axios/got) made in a function that also reads req.query/req.params/req.body — a request-derived value can steer the server to attacker-chosen hosts (SSRF).
path-traversalwarningmethod-scantraversal-okA filesystem call (fs/fsp.*/readFile/writeFile/createReadStream) reads a path.join(...)-built path in a function that also reads req.params/req.query/req.body — unvalidated .. segments escape the intended directory.
cors-credentials-wildcardwarningline-scancors-cred-okcredentials: true in a file that also configures origin: '*' — the wildcard origin removes the check credentialed CORS relies on, exposing cookies/auth headers cross-origin.
jwt-no-expirywarningmethod-scanjwt-expiry-okjwt.sign(...) called in a function where expiresIn never appears — a token with no expiry, valid forever if it leaks.
weak-token-randomwarningline-scanweak-random-okMath.random() used on the same line as a token/otp/nonce/session-id/secret-shaped identifier — a predictable, non-cryptographic PRNG for a security-sensitive value.
timing-unsafe-compareinfoline-scantiming-okA secret/token/signature/hmac/api-key-shaped identifier compared with ===/!== — short-circuiting equality leaks a timing side-channel.
error-leak-to-clientwarningline-scanerror-leak-okA raw error object sent directly to the client (res.status(5xx).send/json(err), Hono c.json(err)) — stack traces/paths/SQL fragments help an attacker map internals.
secret-env-in-fewarningline-scanfe-env-okA server-only-shaped env var (SECRET/PRIVATE/SERVICE_ROLE/SERVICE_KEY) referenced from frontend code — inlined into the shipped JS bundle, readable via devtools.
localstorage-jwtwarningline-scanls-token-okA token/JWT-shaped value written to localStorage — readable by any script on the page, so one XSS bug anywhere on the origin exfiltrates it.
java-hardcoded-passwordwarningline-scanjava-pwd-okA password-shaped literal hardcoded (direct assignment, or a JDBC getConnection(url, user, password) call) — a credential committed to source, can't be rotated without a code change.
xxe-no-guardcriticalmethod-scanxxe-okDocumentBuilderFactory/SAXParserFactory.newInstance() with no XXE guard (disallow-doctype-decl/FEATURE_SECURE_PROCESSING) — default XML parsing resolves external entities (file read/SSRF/billion laughs).
unsafe-deserializationwarningmethod-scandeser-okObjectInputStream.readObject() called — native Java deserialization of an attacker-controlled byte stream can trigger remote code execution via gadget chains.
java-path-traversalwarningmethod-scanjava-traversal-oknew File(...) constructed in a function that also reads request.getParameter(...) — unvalidated .. segments escape the intended directory.
java-weak-randomwarningline-scanjava-random-oknew Random() used on the same line as a token/session/otp/nonce-shaped identifier — a predictable, non-cryptographic PRNG for a security-sensitive value.
stacktrace-to-responsewarningmethod-scanstacktrace-okAn exception's stack trace/message (printStackTrace()/.getMessage()) produced in a method that also touches the HTTP response — internal class names/paths/SQL fragments can reach the client.
trust-all-tlscriticalline-scantrust-all-okTLS certificate/hostname verification disabled (trust-all X509TrustManager, ALLOW_ALL_HOSTNAME_VERIFIER, or an always-true hostname-verifier lambda) — accepts any certificate for any host, opening a MITM path.
conn-string-credentialscriticalline-scanconn-cred-okConnection-string URL with a password in the userinfo slot (scheme://user:pass@host — redis/postgres/mongodb/amqp/...) committed to source — repo readers own the datastore and git history preserves it; move to env/secret config AND rotate. Scans test paths too (a committed credential is leaked regardless).
private-key-committedcriticalline-scanprivate-key-okA PEM private-key header (-----BEGIN [RSA/EC/DSA/OPENSSH/...] PRIVATE KEY-----) committed to source — the key is compromised the moment the repo is shared; rotate it and move it to a secret store. Scans test paths too.
vendor-token-committedcriticalline-scanvendor-token-okA format-identified LIVE vendor credential (Stripe sk_live_/rk_live_, GitHub ghp_/gho_, Slack xox[bpars]-, Google AIza...) committed to source — rotate immediately; committed means leaked. Test-mode keys (sk_test_) deliberately do not fire. Scans test paths too.
jwt-none-algorithmcriticalline-scanjwt-none-okalgorithm(s): 'none' in a JWT-adjacent file — alg=none turns signature verification off entirely; no legitimate production use.
shell-exec-interpolationcriticalline-scanshell-exec-okexec/execSync whose command string carries a ${...} interpolation or +-concat — a dynamic segment inside a shell line is command injection; use execFile/spawn with an argv array (those APIs deliberately do not fire).
jwt-sign-literal-secretcriticalline-scanjwt-secret-okjwt.sign(payload, '<string literal>') — a positional committed signing secret lets anyone forge tokens (the hardcoded-secret rule needs a key: value shape, so this positional form was uncovered); placeholder-word and interpolation shapes are vetoed. Scans test paths too.
jwt-verify-bypasswarningline-scanjwt-verify-okignoreExpiration: true or verify: false in a JWT-library-adjacent file — token validation partially disabled.
sendfile-from-requestwarningline-scansendfile-oksendFile(/download( handed a req.params/query/body value directly — path traversal via a file-serving API (path-traversal covers the fs+path.join shape; this covers the serving APIs). A path.basename(...)-wrapped arg does not fire.
cors-reflected-origin-credentialswarningline-scancors-reflect-okcredentials: true together with origin: true (reflect-any-origin) or origin: req.headers... on one line — any site can make credentialed requests (cors-wildcard/cors-credentials-wildcard cover the literal '*'; this covers reflection, which those matchers miss). Multi-line option objects are a documented miss.
template-unescaped-outputwarningline-scantemplate-unescaped-okTemplate-engine unescaped-output syntax (EJS <%- %>, Handlebars {{{ }}}, Mustache {{& }}) — server-rendered stored/reflected XSS if any interpolated value is user-influenced; use the escaped form or a vetted sanitizer. Scoped to .ejs/.hbs/.mustache/.njk extensions (Pug != deliberately uncovered).
html-response-from-requestwarningmethod-scanhtml-response-okA res.send/write/end of HTML-shaped content in the same function as req.query/params/body/headers with no sanitizer — reflected XSS (co-occurrence heuristic; the res.send-HTML sink security/taint-flow does not list).
dangerous-html-concatwarningline-scanhtml-concat-okAn HTML tag string literal concatenated with a variable ("<div>" + userVar) in a response-context file — an injection sink if the variable is user-influenced; use an auto-escaping template engine or sanitizer.
csp-disabledwarningline-scancsp-disabled-okContent-Security-Policy turned off or wide-open (contentSecurityPolicy: false, unsafe-inline, default-src *) — removes the browser's last-line XSS mitigation; keep a restrictive policy.

browser

Rule idSeverityMatcherSuppress markerDetects
no-system-dialogsinfoline-scanbrowser-okBlocking system dialog (alert/confirm/prompt) — freezes the page/tab (main-thread block), can't be styled or tested.
no-document-writewarningline-scandocument-write-okdocument.write/writeln — breaks HTML parsing post-load, blocked under many CSP/PWA setups.
postmessage-wildcardwarningline-scanpostmessage-target-okpostMessage(..., '*') — a wildcard targetOrigin broadcasts the payload to whatever origin currently holds the window (opener/embedder swaps included); pass the intended origin literal.
unsafe-html-sinkwarningline-scanunsafe-html-okA non-literal value assigned to innerHTML/outerHTML, passed to insertAdjacentHTML, or set as dangerouslySetInnerHTML's __html — the standalone XSS sink check (security/taint-flow fires on these sinks only with a request-derived source in the same .ts/.tsx function; this rule needs no source — component props/state are the common carrier — and covers .js/.jsx). Plain string-literal assignments stay silent.
javascript-urlwarningline-scanjavascript-url-okA literal javascript: scheme URL in an href/src attribute, DOM property assignment, or setAttribute — executes arbitrary script on click/load; validate the scheme against an http(s) allowlist. Catches the literal form only (a dynamic href is a separate, harder class).
location-assign-dynamicwarningline-scanlocation-assign-okA non-literal assigned to location/location.href or passed to location.assign/replace — client-side open-redirect / DOM-XSS navigation sink (be-security/open-redirect covers the server res.redirect(req.*) side). Literal/absolute-path targets and const location = useLocation() stay silent.
jquery-html-sinkwarningline-scanjquery-html-okA non-literal passed to jQuery .html()/.append()/.prepend()/... in a jQuery file — the same HTML-injection surface as innerHTML; use .text() for plain text or sanitize.
vue-v-htmlwarningline-scanvue-v-html-okVue's v-html directive renders raw HTML — XSS if the bound value is user-influenced (the Vue analog of dangerouslySetInnerHTML); prefer {{ }} interpolation or sanitize.
unsanitized-markdown-htmlwarningmethod-scanmarkdown-html-okA markdown renderer (marked/markdown-it/remark/...) whose output reaches an HTML sink in the same function with no sanitizer — markdown renderers emit raw HTML by default; run output through DOMPurify/sanitize-html. (.vue SFCs are not span-projected today, so same-file co-occurrence does not fire — .ts/.tsx/.js/.jsx does.)

fullstack

Rule idSeverityMatcherSuppress markerDetects
mixed-content-egresswarningline-scanmixed-content-okPlain-http:// URL literal — mixed content/MITM risk; excludes localhost/private-IP/XML-namespace lookalikes.
localhost-egress-committedwarningline-scanlocalhost-okCommitted localhost/private-IP endpoint — breaks outside the dev machine.
get-with-bodywarningmethod-scanget-body-okA GET request carrying a body (method: 'get' alongside a body: property in the same function) — servers/proxies may silently drop the body on a GET.
ws-no-authinfomethod-scanws-auth-okWebSocket opened/upgraded (new WebSocket(...)/.upgrade(...)) with no auth material (token/auth/session/cookie/jwt) visible in the same function — unauthenticated realtime channel.

http

Rule idSeverityMatcherSuppress markerDetects
read-model-pathinfoline-scanread-model-okapiRoutes.get(...) with no cache-strategy marker (// cache:, // no-cache:, // read-model-ok:) on the same line.
auth-gateswarningline-scanauth-gate-okRoute under a protected path segment (/admin/, /internal/, /dev/) whose handler identifier carries no admin/role/guard/protect keyword.
route-exposurewarningline-scanroute-exposure-okRoute under a dev/debug/internal/test/playground path segment whose handler identifier carries no guard-hint keyword.

java-security

Rule idSeverityMatcherSuppress markerDetects
sql-taintwarningline-scansql-taint-okSQL built by string concatenation — injection risk.
weak-cryptowarningline-scanweak-crypto-okWeak/deprecated cryptography — MD5/SHA-1/DES/RC4/ECB.
cmd-injectionwarningmethod-scancmd-injection-okRuntime.exec/ProcessBuilder co-occurring with string concatenation in the same method — command-injection risk.

perf

Rule idSeverityMatcherSuppress markerDetects
api-in-loopwarningmethod-scanapi-in-loop-okNetwork call made inside a loop or array-iteration callback — the HTTP analogue of N+1.

redis

Rule idSeverityMatcherSuppress markerDetects
flushall-in-codecriticalline-scanredis-flush-okflushall/flushdb call or quoted command literal reachable from application code — wipes every key in the database/instance; one bug or exposed admin endpoint away from total data loss.
keys-glob-scanwarningline-scanredis-keys-okKEYS scan (.keys('pattern') with a string argument, or a quoted 'KEYS' command) — O(N) walk that blocks the single-threaded server; use the SCAN cursor family or an index set.
client-no-error-listenerwarningline-scanredis-error-okredis/ioredis client created in a file with no .on('error', ...) anywhere in it — node-redis emits error on an EventEmitter, so an unhandled listener crashes the process on the first connection blip.

security

Rule idSeverityMatcherSuppress markerDetects
taint-flowcriticalmethod-scantaint-okA tainted-source access and a dangerous sink call in the same function body (coarse v1 co-occurrence, not real dataflow — see the rule's own message for the three documented precision limits).
eval-dynamic-codewarningline-scaneval-dynamic-okeval( with a non-literal argument, or any new Function( — constructing code from strings at runtime defeats CSP and every static analyzer (taint-flow covers eval+request-source in .ts/.tsx; this rule is source-free and .js-inclusive).

sql

Rule idSeverityMatcherSuppress markerDetects
query-logic-densityinfoline-scanquery-logic-okSQL literal embeds 2+ conditional-logic constructs (CASE WHEN branches / IF·IIF calls).
nplus1warningmethod-scann+1-okawait on a store/ORM call inside a loop or array-iteration callback — N+1 query pattern.
count-in-loopwarningmethod-scancount-in-loop-okstore.count()/prisma.<model>.count() called inside a loop or array-iteration callback.
app-side-aggregation-reduceinfomethod-scanapp-agg-okA findMany()/prepare(...).all() result is reduced in application code (.reduce(...)).
app-side-aggregation-filter-lengthinfomethod-scanapp-agg-filter-okA findMany()/prepare(...).all() result is counted via .filter(...).length.
race-condition-toctouwarningmethod-scantoctou-okA read (findOne/findById/findUnique) feeds a branch that calls create/upsert/insert — TOCTOU race under concurrent requests.
sql-delete-no-wherecriticalline-scansql-delete-no-where-okA closed SQL string literal DELETE FROM <table> with no WHERE in application code — a whole-table delete. Fires only when the entire statement is one closed literal with no interpolation/concat, so the missing WHERE is statically provable; migration paths are destructive-migration's turf at disclosure severity.
sql-update-no-wherecriticalline-scansql-update-no-where-okA closed SQL string literal UPDATE <table> SET ... with no WHERE in application code — a whole-table update; same closed-literal proof discipline and migration-path handoff as sql-delete-no-where.
truncate-in-app-codecriticalline-scansql-truncate-app-okA quote-anchored TRUNCATE [TABLE] <table> reachable from application code (migration paths excluded) — a full-table wipe one call away, the SQL analog of redis/flushall-in-code.
destructive-migrationinfoline-scansql-destructive-migration-okDROP TABLE/DROP COLUMN/TRUNCATE — plus closed-literal whole-table DELETE FROM/UPDATE ... SET — inside a migration path. Info/non-gating by design: migrations are usually deliberate; the value is review-time attention on NEW migrations, with a two-phase (deprecate, then drop) recommendation.
select-starinfoline-scansql-select-star-okSELECT * FROM inside a string literal — over-fetch plus silent schema-drift coupling; select explicit columns (SELECT COUNT(*) does not fire).
like-leading-wildcardinfoline-scansql-like-leading-wildcard-okA LIKE pattern literal starting with % — the leading wildcard defeats index use, forcing a full scan on every call.

typescript

Rule idSeverityMatcherSuppress markerDetects
no-explicit-anyinfoline-scanany-okany type used.
as-castinfoline-scanas-okas type cast used (import-alias as excluded via exclude_pattern).
unhandled-promise-use-effectwarningline-scanunhandled-promise-okuseEffect callback declared async — React drops the returned Promise, no cleanup possible.
async-handler-no-trywarningmethod-scanasync-handler-okAn on<Event>={async ...} JSX handler has an await but no try/catch.
float-equalitywarningline-scanfloat-eq-okA decimal-fraction/exponent numeric literal compared with ==/===/!=/!== (either order) — IEEE754 equality on computed floats is unreliable (0.1 + 0.2 !== 0.3); use an epsilon comparison, integer minor units, or a decimal library. Money-named identifiers are be-db/float-money-compare's turf (no double-fire).
always-false-comparisonwarningline-scanalways-false-okA constant-result comparison: NaN on either side of ==/===/!=/!== (never equal to anything — use Number.isNaN), and strict === [] / === {} (reference-compares a fresh literal — use .length === 0 / Object.keys). Loose == [] is deliberately out of scope (coercion can make it true). The NaN half overlaps ESLint's use-isnan — zzop's value is zero-config coverage.
numeric-string-comparisonwarningline-scannumeric-string-cmp-okA numeric-looking string literal on either side of </>/<=/>= — string comparison is lexicographic ('10' < '9' is true; '10.0.0' < '9.0.0' is true); convert with Number(...) or use a semver library. Generic type arguments (Extract<T, '1'>) do not fire.
tofixed-arithmeticwarningline-scantofixed-arith-ok.toFixed() returns a STRING — using the result in -/*///% arithmetic silently coerces it back, losing the rounding intent; do arithmetic first, format last (+-concat for display is deliberately out of scope).
date-pitfallsinfoline-scandate-pitfall-okThree date footguns: a date-only ISO string (new Date('2024-01-15') parses as UTC midnight, the slash form as local — off-by-one-day across timezones), a 10-digit epoch literal (UNIX seconds where Date wants milliseconds — yields a 1970 date), and 86400000/24*60*60*1000 day arithmetic (breaks across DST's 23/25-hour days). Context-dependent (fine in UTC-only systems) — hence info.
foreach-async-callbackwarningline-scanforeach-async-ok.forEach(async ...) ignores the returned promises entirely — the loop "completes" before any callback runs and errors become unhandled rejections; use for...of with await, or Promise.all(items.map(async ...)) (be-reliability/await-in-map covers the .map(async sibling).
promise-async-executorwarningline-scanpromise-async-exec-oknew Promise(async ...) — the Promise constructor never observes the async executor's returned promise, so its rejections are swallowed; usually a redundant wrapper (overlaps ESLint's no-async-promise-executor — zzop's value is zero-config coverage).
parseint-no-radixinfoline-scanparseint-radix-okSingle-argument parseInt — the radix parameter documents intent and guards hex/legacy-octal parsing surprises; write parseInt(x, 10). Nested-call arguments are a documented miss (never guessed).

Stub packs (roadmap)

conventions, jsp-security, react, and routes load successfully today — each is a valid pack with an empty rules: [] — but ship no detections yet. Each needs either declaration→use tracking, cross-repo/cross-file joins, or JSX/AST structure the DSL can't express; see writing your own pack below for what does and doesn't fit the DSL.

Native analyses (40 ids)

Whole-graph/whole-repo analyses, registered under RuleKind::Native so they share one enable/severity/suppression gating surface with DSL rules (RuleConfig). Each id is registered by its owning crate's own register_native_analyses — the kernel (packages/core) itself registers none, staying rule-vocabulary-free. rules/native/rules-graph owns circular, unreachable, dead-candidates, dead-exports (dependency/dead-code graph rules). rules/native/rules-http owns duplicate-route, unsafe-read-endpoint/non-idempotent-write, route-shadowing, mutating-route-no-auth, unprovided-consume (single-tree HTTP/route rules). rules/native/rules-cross-layer owns the 20 cross-layer/* ids (multi-tree cross-layer join rules). rules/native/rules-schema owns schema-structural, schema-usage, soft-delete-bypass, orderby-unindexed, enum-string-drift. packages/metrics owns seams, criticality, scores, health, recommendations — score computations, not findings-producing rules, that ride the same toggle/gating surface. zzop_engine::register_all_native composes all five.

The 20 cross-layer/* ids are the multi-tree exception: they run over zzop_engine::analyze_trees's joined CrossLayerResult (every other row here runs per-tree), exposed as crossLayerFindings alongside crossLayer in analyzeTrees's output. None of the 20 honor an inline suppression marker — they are disable-only: rules: { "<id>": "off" } in config, or disabledRules for embedders.

IdDefault severityDetects
circularwarningImport cycles in the dependency graph (Tarjan SCC, graph.rs).
unreachableinfoClosed "dead islands" — files imported in-repo (fan-in > 0) yet unreachable from any entrypoint (reachability.rs).
dead-candidatesinfoFile-level dead-code candidates: fan-in == 0 and not an entry-point pattern (tests/Storybook/dev-tool config/.d.ts excluded) (dead.rs).
dead-exportsinfoSymbol-level dead-export detection — exported symbols never imported anywhere, with unused-vs-in-file-only reasons (dev-tool config files excluded) (dead_exports.rs).
seamsinfoStrangler-seam scoring — folders that are self-contained (few boundary-crossing import edges), i.e. good first-extraction candidates (seams.rs).
criticalitywarningTransitive blast-radius scoring — surfaces stable-but-critical files a churn-weighted risk score underweights (criticality.rs).
scoresinfo17 structural health scores, 0–100 (scores/compute.rs).
healthinfoComposite structural-health index rolling the per-metric scores up into one number (health.rs).
recommendationsinfoROI-ranked improvement recommendations derived from FileNodes, coupling, and circular deps (recommendations.rs).
schema-structuralwarningPrisma schema structural rules — god-model, missing timestamps, FK with no index, nullable FK, implicit FK, float-as-money, temporal-as-string, redundant index, stale updatedAt (rules/native/rules-schema/src/structural.rs).
schema-usagewarningPrisma schema usage-aware cross-check — dead model/field, migration churn, store-binding map, layered on the structural rules (rules/native/rules-schema/src/usage.rs).
unsafe-read-endpointwarningA GET/HEAD ("safe") endpoint whose handler reaches a database/store write via call-graph BFS — violates the safe-method contract.
non-idempotent-writewarningA write endpoint (PUT/DELETE always; POST/PATCH for accumulation only) that reaches a non-idempotent create, atomic-accumulate, or counter-bump operation via call-graph BFS — a retry duplicates or doubles the effect.
duplicate-routewarningThe same (METHOD, path) HTTP route provided 2+ times across the tree.
soft-delete-bypasswarningA findMany/findFirst/findUnique/count call site on a model with a deletedAt/deleted_at marker field whose argument span never mentions that field — a soft-deleted row can leak back into a "live" read (rules/native/rules-schema/src/join.rs).
orderby-unindexedwarningA single-field literal orderBy: { field: 'asc' } naming a field with no @id/@unique/leading-@@index coverage on the target model — an unindexed sort that gets slower as the table grows (rules/native/rules-schema/src/join.rs).
enum-string-driftwarningA literal-object field: 'Literal' at a query call site whose field resolves to exactly one declared schema enum, where 'Literal' is not one of that enum's members — a string that drifted out of sync with the enum (rules/native/rules-schema/src/join.rs).
route-shadowingwarningWithin one file, a param-segment route (/x/{}) registered earlier than a same-shape literal-segment route of the same method makes the later literal route unreachable in a first-match router (Express/Koa/Fastify-style) (rules/native/rules-http/src/route_shadowing.rs).
mutating-route-no-authinfoA POST/PUT/PATCH/DELETE route whose handler's call-graph BFS never reaches a callee named like an auth guard (auth/guard/verify/session/token/permission/acl) — misses route-level middleware, so a middleware-guarded route can false-positive (rules/native/rules-http/src/mutating_route_no_auth.rs).
unprovided-consumeinfoAn HTTP IoConsume whose key matches no IoProvide anywhere in the analysis, gated to trees that provide at least one HTTP route themselves — a typo'd path, a renamed/removed backend route, or a route this analysis failed to parse (rules/native/rules-http/src/unprovided_consume.rs).
cross-layer/unconsumed-endpointinfoA crossLayer.unconsumedProvides http entry — an endpoint no tree in this analyzeTrees run calls. Caveats: consumers outside the analysis (another repo, a mobile client, an unresolved dynamic URL) may still exist (rules/native/rules-cross-layer/src/cross_layer/unconsumed_endpoint.rs).
cross-layer/method-mismatchwarningA crossLayer.unprovidedConsumes http consume whose path exactly matches a provide somewhere in the analysis, but the method differs (e.g. FE calls POST /api/users, only GET /api/users is provided) (rules/native/rules-cross-layer/src/cross_layer/method_mismatch.rs).
cross-layer/version-skewwarningA crossLayer.unprovidedConsumes http consume whose key differs from a provide only in one version-shaped path segment (/v1/ vs /v2/) (rules/native/rules-cross-layer/src/cross_layer/version_skew.rs).
cross-layer/path-near-missinfoA crossLayer.unprovidedConsumes http consume whose key matches a provide once {} parameter positions are allowed to differ, but is otherwise segment-identical — strict elsewhere (a plural/typo literal difference does not count) (rules/native/rules-cross-layer/src/cross_layer/path_near_miss.rs).
cross-layer/route-near-missinfoA crossLayer.unprovidedConsumes http consume whose key differs from a same-method provide by exactly one structural dimension — case (letter casing) or prefix (an all-literal 1-2 segment leading base path added/removed, e.g. /api) — disjoint from path-near-miss's same-count parameter-generalization case (rules/native/rules-cross-layer/src/cross_layer/route_near_miss.rs).
cross-layer/shared-db-tablewarningThe same db-table key CONSUMED (not provided) by 2+ distinct source trees — evidence of a naming collision or a genuinely shared database; message says to verify which (rules/native/rules-cross-layer/src/cross_layer/shared_db_table.rs).
cross-layer/duplicate-routewarningThe same http (method, path) key PROVIDED by 2+ DISTINCT source trees — the cross-tree counterpart to duplicate-route above (rules/native/rules-cross-layer/src/cross_layer/duplicate_route.rs).
cross-layer/external-shadow-internalwarningA crossLayer.externalConsumes consume (absolute URL) whose normalized method+path matches a route an analyzed tree provides — the caller hardcodes one environment's host instead of the relative/proxied path (rules/native/rules-cross-layer/src/cross_layer/external_shadow_internal.rs).
cross-layer/external-secret-in-urlwarningA crossLayer.externalConsumes consume whose URL query string carries a secret-named parameter (token/key/apikey/secret/...) — credentials in URLs leak through logs, referrers, and history, whether the value is a literal or interpolated (rules/native/rules-cross-layer/src/cross_layer/external_secret_in_url.rs).
cross-layer/external-duplicated-integrationwarningThe same external host called directly from 2+ distinct source trees — a duplicated third-party integration; centralize behind one client or a backend proxy (rules/native/rules-cross-layer/src/cross_layer/external_duplicated_integration.rs).
cross-layer/external-host-fanoutinfoThe same external host called directly from 3+ distinct files — vendor calls scattered across the codebase instead of centralized in one client module (rules/native/rules-cross-layer/src/cross_layer/external_host_fanout.rs).
cross-layer/external-base-url-driftinfoThe same external path consumed against 2+ different hosts (port included) — base-URL/config drift for what looks like one logical service (rules/native/rules-cross-layer/src/cross_layer/external_base_url_drift.rs).
cross-layer/external-version-inconsistentinfoOne external host consumed through both version-shaped (/v1/...) and versionless paths — inconsistent API version pinning against the same vendor (rules/native/rules-cross-layer/src/cross_layer/external_version_inconsistent.rs).
cross-layer/external-ip-literalwarningA crossLayer.externalConsumes consume whose host is a raw IP literal (loopback excluded — committed localhost URLs are the DSL localhost-egress-committed rule's turf) — environment-specific addressing committed into code (rules/native/rules-cross-layer/src/cross_layer/external_ip_literal.rs).
cross-layer/ambiguous-consumewarningA consume whose key is provided by 2+ distinct trees (crossLayer.ambiguousConsumes) — which provider actually serves the call depends on deploy-time routing the analysis cannot see (rules/native/rules-cross-layer/src/cross_layer/ambiguous_consume.rs).
cross-layer/unconsumed-mutation-endpointwarningA crossLayer.unconsumedProvides http entry with a write method (POST/PUT/PATCH/DELETE) — an unconsumed mutation endpoint is standing attack surface, not just dead code; intentionally co-fires with cross-layer/unconsumed-endpoint (rules/native/rules-cross-layer/src/cross_layer/unconsumed_mutation_endpoint.rs).
cross-layer/unprovided-mutation-callwarningA crossLayer.unprovidedConsumes http consume with a write method — a state-changing call whose target no analyzed tree provides; intentionally co-fires with the unprovided-diagnosis rules above (rules/native/rules-cross-layer/src/cross_layer/unprovided_mutation_call.rs).
cross-layer/route-shadowingwarningA {}-parameter route pattern provided by one tree that would shadow a same-method, same-shape literal route provided by a DIFFERENT tree if both are served behind one first-match gateway — the cross-tree counterpart to route-shadowing above (rules/native/rules-cross-layer/src/cross_layer/cross_tree_route_shadowing.rs).
cross-layer/unresolved-consume-ratioinfoA tree whose http consumes are majority-unresolved (dynamic URLs, generated SDK clients, wrapper functions) — self-reports that the cross-layer join is mostly blind for that tree instead of staying silent (rules/native/rules-cross-layer/src/cross_layer/unresolved_consume_ratio.rs).
cross-layer/sdk-import-no-visible-consumeinfoA tree importing an SDK-shaped package (@scope/sdk, *-sdk, openapi*, *api-client*) from 3+ files while having fewer visible http consumes than unresolved-consume-ratio's floor — consumption flows through a generated/vendor client the egress extractor cannot see; the not-even-visible half of the blind-spot partition (rules/native/rules-cross-layer/src/cross_layer/sdk_import_no_visible_consume.rs).
cross-layer/unconsumed-procedureinfoA tRPC procedure (kind trpc, key "VERB dotted.path", composed at assembly from cross-file router fragments) that no analyzed tree calls — TypeScript's compiler catches calls to nonexistent procedures but not unused definitions. Caveats server-side createCaller/SSR consumers this analysis cannot see (rules/native/rules-cross-layer/src/cross_layer/unconsumed_procedure.rs).

Not yet implemented: architecture rules (layer-violations, feature-envy — no crate is scaffolded for them yet), cognitive/nested-loop complexity scoring, precise taint-flow dataflow (today's security/taint-flow is a documented coarse v1 co-occurrence check), an auth-state-machine analysis, additional cross-file HTTP graph checks (API churn, frontend/backend spec drift), a JSX/React structural rule pack, env/i18n sync checks, and worker-route extraction — each needs either a whole-graph join the DSL can't express or real AST/JSX shape.

Matchers

Every DSL rule declares one of four matcher shapes. Full field-by-field semantics are in the DSL reference; this is the short version.

MatcherOperates overUse it for
line-scanPer-line regex over a file's raw text.Lexical patterns — a keyword, a call, a literal shape on one line.
method-scanMulti-pattern co-occurrence within one symbol's body span (innermost span wins on overlap; files without spans are skipped)."These patterns appear together in one function" — e.g. a network call and a $transaction( in the same method.
symbol-scanA file's declared symbols (functions/classes/consts/types/interfaces).Naming-convention or banned-export rules line-scan can't express reliably.
io-scanA file's IoFacts — the cross-layer IO (HTTP routes, DB tables, ...) the parser projects alongside symbols.Boundary-convention rules, e.g. "every HTTP endpoint must be versioned under /api/v[0-9]+/".

All four operate on one file's SourceFile slice in isolation — none can see a second file's content. A rule's suppress_marker applies to line-scan and method-scan findings only: a // <marker> comment on the finding's own line, or the single line directly above it, suppresses it. symbol-scan/io-scan findings have no source-line concept to anchor a suppress comment against, so they carry no inline marker. See dsl-reference.md in the repo for the full matcher field tables.

Write your own pack

A pack is one <id>.json file loaded from a configured packs directory (the packsDir option) — no first-party/third-party distinction exists at the interpreter level. Here's a small line-scan rule with a suppress marker, flagging a hardcoded debug header value that should come from config/env instead:

debug-headers.json
{
  "id": "debug-headers",
  "framework": "any",
  "schema_version": 1,
  "rules": [
    {
      "id": "hardcoded-debug-token",
      "severity": "warning",
      "message": "X-Debug-Token header set to a string literal — this bypasses per-environment config and risks shipping a real token. Read it from env/config instead.",
      "suppress_marker": "debug-token-ok",
      "matcher": {
        "type": "line-scan",
        "file_pattern": "(?i)\\.(ts|tsx)$",
        "require_file": "X-Debug-Token",
        "skip_comment_lines": true,
        "line_pattern": "[\"']X-Debug-Token[\"']\\s*:\\s*[\"'][^\"'`]+[\"']",
        "snippet_max": 160
      }
    }
  ]
}

packsDir accepts either one directory or an array of directories. Each is loaded independently and then merged by pack id: if the same id shows up in more than one directory, the pack from the later directory in the list replaces the earlier one whole (not a per-rule merge) — this is how a caller adds packs alongside the bundled ones, or overrides a bundled pack outright, without forking the engine.

Not every detection fits the four matchers above. Reach for a native rule instead when the check needs declaration→use tracking (an identifier declared but never read), a cross-file join (resolving a constant or route handler defined in another file), call-graph BFS ("handler X, or something it calls transitively, does Y"), or real AST/JSX shape rather than text co-occurrence — cyclomatic/cognitive complexity and JSX-structural checks have no honest regex-over-lines encoding.