# quelltest rule pack, sanitisers and suppression directives

> Every quelltest find rule id with what it flags, its risk and confidence, the sanitisers it recognises, and the quell-ignore and quell-ignore-mutant directives.

Canonical page: https://quelltest.com/docs/npm/rules

`quell find` parses each file with Babel and emits findings with a category or a rule id, a risk score, a confidence level and a stable id. This page lists every rule, what it flags, the sanitisers that stop a finding, and the comment directives that silence one. Findings are review hints, not proof of defects: they point at code that has no test executing it and that is a common place for bugs.

## How are findings ranked?

Findings are sorted by `risk` (higher first), then by file and line. Each rule has a fixed base risk. Risk goes up by 1 when the file path or the enclosing function name matches `auth`, `login`, `token`, `password`, `payment`, `pay`, `billing`, `charge`, `refund`, `delete`, `remove`, `admin`, `permission`, `role` or `secret`. `confidence` (`low`, `medium`, `high`) reflects how precise the rule was on the bundled benchmark and on real code reviews; filter with `--min-confidence`.

One hop of data flow is tracked from `req.body`, `req.query` and `req.params` (also `request.*`): aliasing, spread, `$set`, destructuring, `JSON.parse` and `new URL`. Parameters that shadow a tainted name and reassignments to clean values stop the flow.

## Which rules are security and robustness rules (`category: risk`)?

| Rule id | Flags | Risk | Confidence |
| --- | --- | --- | --- |
| `mass-assignment` | The whole request body written to a document: `Object.assign(doc, req.body)`, `Model.create(req.body)`, `{ ...req.body }`, `{ $set: req.body }`, `new Model(req.body)`; or a request value assigned to `status`, `role`, `roles`, `isAdmin`, `isActive`, `permissions`, `plan` or `type`. | 10 | high |
| `regex-injection` | A `RegExp`, `$regex` value or `.match`/`.search` argument built from a non-literal that is not escaped. | 10 | medium |
| `nosql-injection` | A request value used as an operand in a query (`find`, `findOne`, `updateOne`, `deleteMany`, `countDocuments`, ...), where it can be an operator object such as `{ "$ne": null }`. | 10 | high |
| `path-traversal` | A request value reaching `fs.readFile`/`writeFile`/`unlink`/`readdir`/`rm` (and sync or stream forms), `res.sendFile`, `res.download`, `path.join` or `path.resolve`. | 10 | high |
| `eval-input` | `eval`, `vm.runInNewContext`, `vm.runInThisContext` or `new Function` with a non-literal argument. | 10 | high |
| `unguarded-call` | `jwt.verify`, `jsonwebtoken.verify`, `bcrypt.compare`/`hash` or `bcryptjs.compare`/`hash` outside `try/catch` or an async wrapper; they throw on expired, malformed or non-string input. | 9 | high |
| `check-then-act` | A function that looks up (`exists`, `findOne`, `countDocuments`) and then writes (`create`, `save`, `insertOne`) without handling a duplicate-key error (`11000`): racy under concurrent requests. | 9 | medium |
| `hard-coded-secret` | A string literal of 8+ characters (no spaces) as the key of `jwt.sign`/`verify` or `createHmac`, or assigned to a name ending in `secret`, `password`, `api_key`, `private_key`, `access_token` or `auth_token`. Placeholders and route-like strings are ignored. | 9 | high |
| `open-redirect` | A request value deciding the target of `res.redirect` or `new URL(...)`. | 8 | medium |
| `missing-await` | A promise-returning ORM call (`save`, `create`, `updateOne`, ...) used as a statement in an async function, or a field read off an un-awaited query result. Needs a Model-like receiver or an ORM mentioned in the module. | 8 | high |
| `unparsed-json` | `JSON.parse` on request data outside `try/catch`. | 7 | high |
| `unguarded-network-call` | `fetch`, `axios`, `got`, `ky`, `http.get/request` outside `try/catch`, a `.catch` chain or an async wrapper; asks for a timeout or signal when none is set. | 7 | medium |
| `unguarded-member-call` | `.map`/`.filter`/`.find`/... called on a response-like value (`data`, `res`, `response`, `result`, `payload`, `body`, `json`) that is dereferenced without a guard or optional chaining. | 6 | medium |
| `unhandled-async-route` | An async route handler (`app.get(...)`, `router.post(...)`) with no `try` statement. | 5 | medium |
| `deep-response-chain` | An indexed read or a four-level read on a response-like value (`res.data.items[0].plan`) without optional chaining. | 5 | medium |
| `falsy-fallback` | `\|\|` with a non-zero numeric default (`pct \|\| 10`): `0` falls back too. Use `??` when `0` is valid. | 4 | medium |

Each rule's suggestion text tells you which test to write, for example `path-traversal`: test `../` and absolute paths and use `path.basename` or a base-directory check.

## Which categories come from the code's own contracts?

| Category | Flags | Base risk | Confidence |
| --- | --- | --- | --- |
| `throws` | A `throw new ...Error(...)`. Suggestion includes the message so you assert it. | 6 | high |
| `schema` | zod `parse`, `safeParse`, `parseAsync` and `z.object` in modules that import zod. | 5 | medium |
| `boundary` | A relational or equality comparison in `if`, a ternary or `while`. Enum-style string comparisons are risk 2 and `low` confidence. | 4 | medium |
| `jsdoc` | A function with `@throws`, `@param` or `@example` tags. | 3 | medium |
| `nullish` | Optional chaining or a `== null` style check that is not already inside a guard or fallback. | 3 | low |
| `async` | An async function that awaits. | 2 | low |

## Which sanitisers does it recognise?

Which call neutralises a request value depends on the sink: `String()` stops operator objects but not `../`.

| Sink | Calls that stop the finding |
| --- | --- |
| Regular expressions (`regex-injection`) | Names containing `escape` or `sanitiz` (any case), or `x.replace(/[...]/g, '\$&')`. A sanitiser applied through a variable (`const p = escapeRegex(q)`) counts. |
| NoSQL queries (`nosql-injection`) | `String`, `Number`, `Boolean`, `parseInt`, `parseFloat`, names containing `escape` or `sanitiz`, or ending in `toString`. |
| File paths (`path-traversal`) | `basename` (as `path.basename`), names containing `sanitiz`, or names starting with `validate`. `String()` does not count. |
| Everything else (redirects, `JSON.parse`, URLs) | `String`, `Number`, `Boolean`, `parseInt`, `parseFloat`, `encodeURIComponent`, `encodeURI`, `basename`, names containing `escape` or `sanitiz`, names starting with `validate`. |

A redirect is also accepted when the target has a fixed single-slash prefix (`'/users/' + id`) or was checked earlier in the same function with both `startsWith('/')` and `startsWith('//')`. A `try` block counts as handling a call only when the call is inside the `try` itself, not in `catch`, `finally` or a callback created inside it.

## How do I suppress a finding?

| Directive | Where | Effect |
| --- | --- | --- |
| `// quell-ignore [rule,...]` | On the line of the finding or the line above | Suppresses findings there. With rule ids or categories it suppresses only those (`// quell-ignore path-traversal`). Counted in `suppressed` in the JSON report. |
| `// quell-ignore-file` | Anywhere in a file | The scanner skips the whole file. |
| `// quell-ignore-mutant <reason>` | On a source line or the line above | `verify` never mutates that line. Use it for equivalent mutants. It does not suppress scanner findings. |
| `// quell-allow-weaken <reason>` | Inside a test | `gate` exempts that test. See [the test gate](https://quelltest.com/docs/npm/gate.md). |

Whole files and folders can also be excluded with `--exclude GLOB` (alias `--ignore`) or a `.quellignore` file with one glob per line (`#` starts a comment). Patterns match paths relative to the scanned directory; `--include GLOB` restricts the scan. Always put a reason after `quell-ignore-mutant` and `quell-allow-weaken` so a reviewer can judge it.

## How accurate is the rule pack?

On the bundled benchmark (`npx quell eval`), 11 of the 18 defects reported against a real Express and React code base are detected, which is every defect a line-level rule can see; the other 7 (wrong business rules, UI behaviour) are documented known misses. On 7 files of guarded code the risk rules produced no findings. The fixtures are written by the authors, so treat them as regression guards, not as an independent benchmark. See [limits](https://quelltest.com/docs/npm/limits.md).
