Contradictory Lookahead

ID

php.regex_contradictory_lookahead

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

Php

Tags

regex, reliability

Description

Reports a positive lookahead (?=X) immediately followed by a mandatory character or class Y, where X begins with a character or class that cannot overlap Y.

Rationale

A lookahead consumes nothing, so it and whatever follows it are tested against the same position in the subject. If the two disagree about that one character, no input can satisfy both:

// Bad
preg_match('/^(?=\d)[a-z]+$/', $s);   // 0 for every subject

The first character would have to be a digit and a lowercase letter at once.

  • The pattern is dead: the guard it protects never fires, for any input.

  • It usually comes from reading (?=…​) as "and then", when it means "and also, right here". A lookahead intended as a prerequisite for the whole subject needs a .* to travel: (?=.*\d).

Remediation

Let the lookahead scan forward, or drop it if what follows already says the same thing.

// Good
preg_match('/^(?=.*\d)[a-z\d]+$/', $s);   // "contains a digit somewhere"
preg_match('/^\d[a-z]+$/', $s);           // "starts with a digit, then letters"

What is not reported

Only the lookahead’s leading atom is examined, never its whole body, and only a single character or a simple class — including \d, \w, \s and their negations — takes part in the disjointness test. That is what keeps a lookahead which is a prefix of what follows quiet: /(?=a)ab/ is correct and common.

Two further shapes are deliberately not reported:

  • An atom whose quantifier can match nothing — /(?=a)b*/, /(?=a)b?/, /(?=a)b{0,3}/. It leaves the position to whatever comes after it, so there is no contradiction to decide. What matters is the quantifier’s minimum, not its presence: /(?=\d)[a-z]+/ has a minimum of one, so its first character is forced at the lookahead’s position and it is reported.

  • Anything compared against a bare ., as in the password-policy idiom /^(?=.*\d)(?=.*[a-z])[a-z]+$/. A dot accepts any character, so it contradicts nothing.

A negative lookahead (?!X) is out of scope: disagreeing with what follows is what it is for.

© 2026 Xygeni. All rights reserved