Skip to content

quelltest rule pack, sanitisers and suppression directives

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 idFlagsRiskConfidence
mass-assignmentThe 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.10high
regex-injectionA RegExp, $regex value or .match/.search argument built from a non-literal that is not escaped.10medium
nosql-injectionA 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 }.10high
path-traversalA request value reaching fs.readFile/writeFile/unlink/readdir/rm (and sync or stream forms), res.sendFile, res.download, path.join or path.resolve.10high
eval-inputeval, vm.runInNewContext, vm.runInThisContext or new Function with a non-literal argument.10high
unguarded-calljwt.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.9high
check-then-actA function that looks up (exists, findOne, countDocuments) and then writes (create, save, insertOne) without handling a duplicate-key error (11000): racy under concurrent requests.9medium
hard-coded-secretA 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.9high
open-redirectA request value deciding the target of res.redirect or new URL(...).8medium
missing-awaitA 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.8high
unparsed-jsonJSON.parse on request data outside try/catch.7high
unguarded-network-callfetch, 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.7medium
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.6medium
unhandled-async-routeAn async route handler (app.get(...), router.post(...)) with no try statement.5medium
deep-response-chainAn indexed read or a four-level read on a response-like value (res.data.items[0].plan) without optional chaining.5medium
falsy-fallback|| with a non-zero numeric default (pct || 10): 0 falls back too. Use ?? when 0 is valid.4medium

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?

CategoryFlagsBase riskConfidence
throwsA throw new ...Error(...). Suggestion includes the message so you assert it.6high
schemazod parse, safeParse, parseAsync and z.object in modules that import zod.5medium
boundaryA relational or equality comparison in if, a ternary or while. Enum-style string comparisons are risk 2 and low confidence.4medium
jsdocA function with @throws, @param or @example tags.3medium
nullishOptional chaining or a == null style check that is not already inside a guard or fallback.3low
asyncAn async function that awaits.2low

Which sanitisers does it recognise?

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

SinkCalls 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?

DirectiveWhereEffect
// quell-ignore [rule,...]On the line of the finding or the line aboveSuppresses findings there. With rule ids or categories it suppresses only those (// quell-ignore path-traversal). Counted in suppressed in the JSON report.
// quell-ignore-fileAnywhere in a fileThe scanner skips the whole file.
// quell-ignore-mutant <reason>On a source line or the line aboveverify never mutates that line. Use it for equivalent mutants. It does not suppress scanner findings.
// quell-allow-weaken <reason>Inside a testgate exempts that test. See the test gate.

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.