Regex Anchor Precedence
ID |
javascript.regex_anchor_precedence |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
code-style |
Description
Reports an alternation whose first branch starts with ^ or whose last branch ends with $, while
at least one other branch of the same alternation carries no such anchor.
Rationale
Anchors bind tighter than |, so ^a|b|c$ is not "match a, b or c, anchored at both ends" —
it parses as (?:^a)|b|(?:c$):
const verb = /^get|post|put/;
verb.test("get"); // true — as intended
verb.test("unsupported-put") // true — NOT intended: only the first branch is anchored
The layout suggests every branch is bounded; only the outermost ones actually are. In a validation regex that means the pattern accepts input it was written to reject, and the gap is invisible on review because the anchors are right there in the source.
Remediation
Wrap the alternatives in a non-capturing group so the anchors apply to the whole thing, or repeat the anchors on every branch if each was really meant to be anchored on its own:
const verb = /^(?:get|post|put)$/;
// or
const verb = /^get$|^post$|^put$/;
What is not reported
An alternation where every branch carries the same anchor on its own (a$|b$|^c$) is not
reported — there is no gap between branches to be misled by. An alternation already wrapped in a
group (^(?:a|b|c)$), or one whose anchored branches are each explicitly scoped in their own group
((?:^a)|b|(?:c$)), reads exactly as intended and is not reported either.
Two idioms are exempt. A branch that is nothing but the anchor — (?:,|$), (|[\\]) — is the
deliberate "this or the boundary" form and has no content to anchor. The two-branch trim shape
^A|B$, as in text.replace(/^,|,$/g, ""), already anchors each branch at the edge it strips.
An alternation with no anchor at either end is out of scope.