DEV Community

Cover image for Rename the Variable, Change the Security Verdict
Ofri Peretz
Ofri Peretz

Posted on Originally published at ofriperetz.dev

Rename the Variable, Change the Security Verdict

Four lines, run through my own XPath injection rule today:

xpath.select("//user[@id='" + id     + "']", doc);   // silent
xpath.select("//user[@id='" + userId + "']", doc);   // FIRES

export const QueryValidateSchema = QueryInputSchema; // FIRES
export const SchemaA             = SchemaB;          // silent
Enter fullscreen mode Exit fullscreen mode

The first two are the same vulnerability. The last two are the same statement.
The only thing that changed is what the variables are called.

The heuristic

no-xpath-injection has to decide whether a value is attacker-controlled. Here
is how it decides:

const varName = inputNode.name.toLowerCase();
if (["req", "request", "query", "params", "input", "user", "search"]
      .some((keyword) => varName.includes(keyword))) {
  return true;   // tainted
}
Enter fullscreen mode Exit fullscreen mode

That is the whole test. Not where the value came from — what it is spelled.

Both errors, one cause

The false positive. QueryInputSchema lowercases to queryinputschema,
which contains query and also input. Tainted. There is no XPath call in
that statement at all — it is a Zod schema being re-exported. The rule reports
an injection risk on an assignment.

The false negative. id contains none of the seven substrings, so the
value is not tainted, so a concatenation flowing into xpath.select — a sink
this rule already recognises, it is in the default xpathFunctions list — is
not reported. Textbook XPath injection, silently accepted.

The //user[...] string does contain user. It does not matter: the check
runs on the identifier, never on the literal.

One predicate, opposite failures. That is what makes this a single defect
rather than two bugs that happen to share a file.

Why this is worse than being wrong

A rule with a 70% false-positive rate is annoying and legible — you read the
findings, you see the noise, you tune it or drop it.

This is not that. This rule is right about the shape of the problem and
wrong about the input to it, which means its errors correlate with naming
conventions rather than with code. A codebase that says userInput gets
flagged everywhere. A codebase that says val, x, raw or id gets a clean
report and the same vulnerabilities.

Two teams, identical risk, opposite verdicts — and neither has any way to tell
from the output which one they are.

It also fails toward silence in exactly the case that matters. Short, neutral
identifiers are what you find in the compact, clever code most likely to be
doing something sharp with strings.

What it should read instead

The value's origin, not its spelling. Did it come from a request handler
parameter, a process.argv, a req.* member, a function boundary the rule
cannot see past? That is
taint analysis,
and it is more expensive than a substring match — which is precisely why the
substring match was written.

The honest framing is that this rule is a heuristic wearing a taint rule's
messaging. The fix is not a longer keyword list. A longer list moves the
boundary; it does not stop the verdict being a function of names.

The check that finds these

Take any rule you rely on. Write the vulnerable case. Then rename every
identifier in it to a single letter
and run it again.

If the verdict changes, the rule is reading names. You now know something about
your tooling that no amount of reading its findings would have told you —
because a finding you never get is indistinguishable from a clean file, and
precision and recall measured on a corpus that shares your naming conventions
will both look fine
.
It is the same reason a labelled fixture set catches what unit tests
miss
:
the tests assert the rule's behaviour on the names its author chose.

This is a known class in our own tracker, with a lint check that gates new
occurrences and a documented list of the ones still outstanding. This rule is
on that list. Measured against the workspace build of
eslint-plugin-secure-coding
today — if you are on the published release, re-run before assuming.

The rule is
no-xpath-injection;
the predicate is in
its source.
Both are open, which is the only reason this article can be specific.

⭐ Star the repo if you would rather find your linter's blind spots in an article than in an incident.

eslint-plugin-secure-coding is part of the Interlace ESLint ecosystem. Source on GitHub · Follow: Dev.to/ofri-peretz

Rename the variables in your worst file to single letters. Does your linter still find the bug?

Top comments (0)