All findings · Lucene finding · LFV-082 · flexible-parser-literal-narrowing

Flexible StandardQueryParser narrows out-of-range int literals with intValue(), so i:[2147483648 TO *] matches every document with the field

With an Integer PointsConfig, the flexible StandardQueryParser turns the literal 2147483648 (one past Integer.MAX_VALUE) into -2147483648 with intValue(), so i:[2147483648 TO *] silently becomes the whole int range and matches every document with the field instead of none. Every release since points support (6.0.0) has it, 10.6.0 included.

Summary

Priority
Low, rank 533Moved down from Medium: with the opt-in flexible parser and an Integer PointsConfig, an out-of-range literal (a timestamp typed into an int field) wraps so a range filter fails open and matches every document, silent, but the parser config is rare and no Elasticsearch or Solr path uses it.
Impact
silent wrong results
Since
6.0.0 for PointsConfig (3.4.0 for the legacy numeric config)
Affected
every release from 3.4.0 to 10.6.0, and main
Status
documented
Found by
code reading while verifying a sibling parser bug

AI-assisted page: this finding was first signalled by code reading or testing and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

For a field with an Integer PointsConfig, the parser converts every parsed literal to an int with Number.intValue() and never checks that it fits. A literal outside the int range keeps only its low 32 bits, so the bound of the range moves somewhere else: 2147483648 becomes -2147483648.

Background: numeric fields in the flexible parser

  1. The flexible StandardQueryParser (org.apache.lucene.queryparser.flexible.standard) treats a field as numeric only if you register a PointsConfig for it with setPointsConfigMap: a NumberFormat that parses the query text, and the type the field was indexed with (Integer, Long, Float or Double).
  2. For such a field, i:[5 TO *], i>=5 and i:5 become point queries (IntPoint.newRangeQuery) instead of term queries. An open end * becomes the type's limit, here Integer.MAX_VALUE.
  3. The NumberFormat returns a general Number (for 2147483648 and the integer format, a Long); the parser then converts it to the configured type.
Summary of the bug: the query i:[2147483648 TO *] on a field with an Integer PointsConfig; the NumberFormat returns the Long 2147483648, intValue() turns it into -2147483648 (Integer.MIN_VALUE); the built query is i:[-2147483648 TO 2147483647] and the one document with i = 0 matches, where 0 hits are expected.
The minimal test step by step: the literal 2147483648, intValue() turning it into -2147483648, the built query i:[-2147483648 TO 2147483647] and 1 hit where 0 are expected. Select the still to open it at full size. Generated with AI, checked against the code.

The finding, step by step

  1. Configure the flexible StandardQueryParser the way its own test does: an Integer PointsConfig with NumberFormat.getIntegerInstance(Locale.ROOT) for field i. Index one document with IntPoint("i", 0).
  2. Parse i:[2147483648 TO *]: "i is at least 2,147,483,648". Integer.MAX_VALUE is 2,147,483,647, so no int qualifies: you expect 0 hits, or a parse error like Lucene's XML query parser gives for the same bound.
  3. You get the query i:[-2147483648 TO 2147483647], the whole int range, and 1 hit: the filter matches every document with the field. No error, no warning.
  4. Why: the parser reads the literal as a Long and converts it with Number.intValue(), which keeps only the low 32 bits. 2147483648 becomes -2147483648 (Integer.MIN_VALUE); nothing checks the range.
  5. Other forms are wrong too: i:4294967296 matches the document with 0, and i:[0 TO 3000000000] becomes i:[0 TO -1294967296] and drops every in-range document. A literal beyond the long range saturates at the int limit instead, which is wrong too. In the forms tested, Long fields saturate, so only a value at exactly Long.MIN_VALUE or Long.MAX_VALUE is wrong.

Who is affected

Users of the flexible StandardQueryParser who register an Integer PointsConfig (opt-in; Luke's search tab builds one from its type map) and whose query strings can carry a number outside the int range, such as a millisecond timestamp or a 64-bit id typed into an int field. One document is enough, and the wrong result is silent.

Mechanism, terms and numbers538 words

With a PointsConfig of type Integer, the flexible StandardQueryParser parses each point literal with the configured NumberFormat and converts it with Number.intValue() without checking that it fits in an int (PointRangeQueryNodeProcessor.java:109-111, PointQueryNodeProcessor.java:94-95 on main 645d4fe14a20). For 2147483648 the NumberFormat returns a Long, and intValue() keeps its low 32 bits: -2147483648. So i:[2147483648 TO *], which no int satisfies, parses to i:[-2147483648 TO 2147483647] and matches every document with the field, silently. i:4294967296 matches the document with 0, and i:[0 TO 3000000000] becomes i:[0 TO -1294967296] and returns none of the in-range documents. A literal beyond the long range saturates at the int limit instead, and in the forms tested Long fields saturate too, so there only a document at exactly the limit is wrong. The unchecked narrowing was written in 2011 (LUCENE-1768, first released in 3.4.0) and copied into the points processors in 2016 (LUCENE-7089, 6.0.0); every release up to 10.6.0 and main are affected. Rejecting a literal outside the type's range, as Lucene's XML query parser already does, fixes it; clamping the bound instead keeps the range semantics.

The bug

All lines are on main 645d4fe14a20, under lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/.

  1. Pipeline. StandardQueryNodeProcessorPipeline.java:53-55 runs OpenRangeQueryNodeProcessor (turns * into an empty bound), then PointQueryNodeProcessor and PointRangeQueryNodeProcessor.
  2. Parse. PointRangeQueryNodeProcessor.java:79-107 parses each non-empty bound with the configured NumberFormat (:82, :97); an unparseable literal throws QueryNodeParseException (:84-91, :99-106). For 2147483648 the integer DecimalFormat returns a Long; the JDK documents that it returns most integer values as a Long and a value that does not fit in a long as a Double (DecimalFormat.java:2202-2208).
  3. Narrow, unchecked. For an Integer type, :109-111 do upperNumber = upperNumber.intValue(); lowerNumber = lowerNumber.intValue();. Long.intValue() keeps the low 32 bits: 2147483648 becomes -2147483648, 4294967296 becomes 0, 3000000000 becomes -1294967296. A literal beyond the long range is a Double, and Double.intValue() saturates: i:[99999999999999999999 TO *] becomes i:[2147483647 TO 2147483647] and matches a document at exactly Integer.MAX_VALUE.
  4. Build. builders/PointRangeQueryNodeBuilder.java:63-79 casts to Integer, maps the empty upper bound to Integer.MAX_VALUE (:73-75) and calls IntPoint.newRangeQuery("i", -2147483648, 2147483647) (:79): the whole int range.
  5. Single value. PointQueryNodeProcessor.java:83 parses and :94-95 narrow the same way; :115 builds [v TO v].
  6. Long fields. PointRangeQueryNodeProcessor.java:112-114 and PointQueryNodeProcessor.java:96-97 call longValue(). Beyond the long range the NumberFormat returns a Double, and Double.longValue() saturates: l:[2^63 TO *] becomes [Long.MAX_VALUE TO Long.MAX_VALUE] and matches only a document at exactly that value. Executed with the integer format in four Long forms (three ranges and one single value); other formats were not tested.

The literal forms

Five documents with i = Integer.MIN_VALUE, -5, 0, 5, Integer.MAX_VALUE (and l at the long equivalents), main 645d4fe14a20, assertions on and off:

queryparses tohitsexpected
i:[2147483648 TO *], i>=2147483648i:[-2147483648 TO 2147483647]50
i:[* TO -2147483649], i<="-2147483649"i:[-2147483648 TO 2147483647]50
i:2147483648i:[-2147483648 TO -2147483648]10
i:4294967296i:[0 TO 0]10
i:[0 TO 3000000000]i:[0 TO -1294967296]03
i<=3000000000i:[-2147483648 TO -1294967296]15
l:[2^63 TO *] (written in full)l:[Long.MAX_VALUE TO Long.MAX_VALUE]10

In-range literals, including the limits themselves (i:2147483647, i:"-2147483648", i:[-2147483648 TO 2147483647]), are correct.

Exposure and likelihood203 words
  • Who is hit. Callers of setPointsConfigMap with an Integer type (or, in the narrow saturating form, Long). The parser is opt-in, but the queryparser overview calls it "probably a good place to start" for new users (overview.html:38-40). Lucene's index browser Luke builds exactly this PointsConfig from its type map and parses the user's query string (SearchImpl.java:221-244). One downstream application, IPED, uses PointsConfig (its issue #510). The study found no PointsConfig use in Elasticsearch or Solr.
  • What goes wrong. Silent wrong results in both directions: a bound past the near end of the int range fails open and matches every document with the field; a bound past the far end drops in-range documents (i:[0 TO 3000000000] returns 0 of 3); a single out-of-range value matches an unrelated value (i:4294967296 matches 0). Deterministic, with assertions on or off.
  • What it takes. One document, one query string, the default codec. The literal must be outside the int range: realistic accidents are a value from another domain typed into an int field (a 13-digit millisecond timestamp, a 64-bit id, a phone number) or an upper bound picked "big enough".
  • How likely: rare. Normal queries use values inside the field's domain.

Minimal repro

One JUnit method on the public API, in the parser's package like the upstream tests: one document with IntPoint("i", 0), the upstream test's Integer PointsConfig, and parse("i:[2147483648 TO *]"). The assertion message prints the parsed query. Fails with assertions on and off.

TestPointRangeOutOfIntRange.java (lucene/queryparser, package org.apache.lucene.queryparser.flexible.standard, 1 test)53 lines
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.queryparser.flexible.standard;

import java.text.NumberFormat;
import java.util.Locale;
import java.util.Map;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.flexible.standard.config.PointsConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestPointRangeOutOfIntRange extends LuceneTestCase {

  public void testLowerBoundAboveIntMax() throws Exception {
    try (Directory dir = new ByteBuffersDirectory()) {
      try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
        Document doc = new Document();
        doc.add(new IntPoint("i", 0));
        w.addDocument(doc);
      }
      StandardQueryParser parser = new StandardQueryParser();
      parser.setPointsConfigMap(
          Map.of("i", new PointsConfig(NumberFormat.getIntegerInstance(Locale.ROOT), Integer.class)));
      Query query = parser.parse("i:[2147483648 TO *]", "i");
      try (DirectoryReader reader = DirectoryReader.open(dir)) {
        // expected: no int value is >= 2^31, so 0 hits; actual: 2147483648 wraps to Integer.MIN_VALUE
        assertEquals(query.toString(), 0, new IndexSearcher(reader).count(query));
      }
    }
  }
}
Command
cp $REPRO/TestPointRangeOutOfIntRange.java $LUCENE/lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/
cd $LUCENE && ./gradlew :lucene:queryparser:test --tests org.apache.lucene.queryparser.flexible.standard.TestPointRangeOutOfIntRange -Ptests.seed=DEADBEEF
Expected
0 hits: no int is at least 2147483648 (or a clean parse error).
Observed
1 hit: AssertionError: i:[-2147483648 TO 2147483647] expected:<0> but was:<1> at line 49.

All files of this repro: repro/LFV-082/

More files, environment, full output235 words

TestPointRangeOutOfIntRange (53 lines with the license header, one test method, public API only). It fails at the count assertion on line 49; the message is the parsed query, which shows the wrap before any search runs, so the test framework's random codec and locale do not matter (the NumberFormat uses Locale.ROOT).

As a regression test this method, with the same configuration and query, can go into lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/ unchanged if the fix keeps the range semantics. With the recommended fix (reject the literal) its count assertion becomes expectThrows(QueryNodeParseException.class, () -> parser.parse("i:[2147483648 TO *]", "i")), and the document, index and searcher are no longer needed (35 lines).

The same test fails on the 10.6.0, 10.5.2 and 9.12.3 release jars (standalone runner), and on 9.1.0, the oldest release whose test framework it compiles against.

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT): fails; also fails on the 10.6.0, 10.5.2, 9.12.3 and 9.1.0 release jars
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux aarch64 (plain integer conversion, nothing platform-specific)
Command (environment)
cp $REPRO/TestPointRangeOutOfIntRange.java $LUCENE/lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/
cd $LUCENE && ./gradlew :lucene:queryparser:test --tests org.apache.lucene.queryparser.flexible.standard.TestPointRangeOutOfIntRange -Ptests.seed=DEADBEEF

Observed output

# gradle, main 645d4fe14a20, -Ptests.seed=DEADBEEF
TestPointRangeOutOfIntRange > testLowerBoundAboveIntMax FAILED
    java.lang.AssertionError: i:[-2147483648 TO 2147483647] expected:<0> but was:<1>
        at org.apache.lucene.queryparser.flexible.standard.TestPointRangeOutOfIntRange.testLowerBoundAboveIntMax(TestPointRangeOutOfIntRange.java:49)

# standalone runner, stock jars, seed DEADBEEF
main 645d4fe14a20  -ea / -da   tests_run=1 failures=1   same message
10.6.0             -ea / -da   tests_run=1 failures=1   same message
10.5.2             -ea / -da   tests_run=1 failures=1   same message
9.12.3             -da         tests_run=1 failures=1   same message

Why it looks unintended

  • The parser documents a range by the values it selects; no int is at least 2147483648, yet every document matches.
  • The same processors reject an unparseable literal with QueryNodeParseException; Lucene's XML query parser rejects this exact bound.
  • No javadoc, test or changes entry allows narrowing; upstream tests use only in-range literals.
The full argument325 words
  • Contract. StandardQueryParser.java:145-158: "inclusive range; selects documents whose name field has any value between Jones and Smith, including boundaries" and "one-sided range; selects documents whose score field is larger than 2.5". PointsConfig.java:39, :57: the type is "the numeric type used to index the numeric values", with no precondition on literals. No javadoc, package doc, CHANGES or MIGRATE entry mentions narrowing or out-of-range literals. Equally, nothing says what should happen to a literal the type cannot represent: the javadoc rules out the observed result but does not choose between an error and clamping.
  • The same code rejects a literal it cannot parse. A literal from which the configured NumberFormat cannot read a number throws QueryNodeParseException(COULD_NOT_PARSE_NUMBER) (PointRangeQueryNodeProcessor.java:84-91, :99-106; PointQueryNodeProcessor.java:85-92); an empty one throws NUMERIC_CANNOT_BE_EMPTY (PointQueryNodeProcessor.java:104-108). Otherwise the processors are as lenient as the format: NumberFormat.parse(String) throws only when no number can be read at the start of the text, and the integer format stops at a decimal point, so i:12abc parses to i:[12 TO 12], i:1.5 to i:[1 TO 1] and i:[1.5 TO 3.9] to i:[1 TO 3] (executed on main). A maintainer may see the narrowing as part of that leniency.
  • The sibling XML query parser rejects this bound. xml/builders/PointRangeQueryBuilder.java:69-70: "A ParserException will be thrown if an error occurs parsing the supplied lowerTerm or upperTerm into the numeric type specified by type"; it uses Integer.parseInt (:86-87). On main, <PointRangeQuery fieldName="i" type="int" lowerTerm="2147483648"/> throws "Could not parse lowerTerm or upperTerm into a number".
  • Tests. Upstream TestPointQueryParser.java:33-84 uses only 1, 3, 1.5 and 3.6, with exactly the repro's configuration. No queryparser test uses 2147483648, 4294967296, MAX_VALUE or "overflow" with a PointsConfig.
  • History. The unchecked intValue()/longValue() came with LUCENE-1768 (#2842, 2011) for the legacy numeric processors and was copied by LUCENE-7089 (#8145, 2016), which added points "the exact same way legacy numerics works". Neither discussion mentions out-of-range literals, overflow or intValue.
The devil's-advocate check183 words

The strongest case for "not a bug": the literal lies outside the field's int domain, so Integer.class implicitly limits the input and the result is unspecified, a validation gap rather than a contract violation. A verifier of the sibling bug LFV-050 read it this way first, and the reviewer from another model family rated the argument moderate. Add to it that the processors inherit the format's general leniency (above), and that PointsConfig takes any caller-supplied NumberFormat, so an application can plug in a stricter one; the 2011 design discussion already left conversions to a caller-supplied NumberFormat (#2842).

Why it still fails: the parser accepts the literal without error and returns a query that matches values the query text excludes; the same processors reject a literal they cannot parse, and the XML parser rejects this one; with the stock configuration (upstream's test, Luke) an application can only avoid it by validating literals itself or writing a stricter NumberFormat; and the under-match form is a meaningful int query: i:[0 TO 3000000000] contains the documents 0, 5 and Integer.MAX_VALUE and returns none of them.

Suggested fix

Recommended Reject a parsed literal outside the type's range

Before the narrowing, reject a parsed literal outside the Integer or Long range with QueryNodeParseException, as the XML query parser does (+34 lines in the two processors). In-range literals are unchanged; the whole :lucene:queryparser suite passes. Trade-off: i:[0 TO 3000000000] then fails to parse instead of returning the in-range documents.

All options and trade-offs3 options · 728 words

Two fixes were validated with gradle in a scratch worktree at 645d4fe14a20 (JDK 25, -Ptests.seed=DEADBEEF); the third option is the candidate step's causal check.

Reject a parsed literal outside the type's range (validated by the checks) (recommended)

PointRangeQueryNodeProcessor.java checks both parsed bounds before :109; PointQueryNodeProcessor.java calls the same helper before :94., +34 lines, two files (with one code comment reworded after the fact check).

static void checkFitsType(Number number, Class<? extends Number> type, String text)
    throws QueryNodeParseException {
  final double value = number.doubleValue();
  final boolean fits;
  if (Integer.class.equals(type)) {
    fits = value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE;
  } else if (Long.class.equals(type)) {
    // a DecimalFormat returns a value that does not fit in a long as a Double
    fits = (number instanceof Double == false && number instanceof Float == false)
        || (value > -0x1p63 && value < 0x1p63);
  } else {
    fits = true;
  }
  if (fits == false) {
    throw new QueryNodeParseException(
        new MessageImpl(QueryParserMessages.COULD_NOT_PARSE_NUMBER, text, type.getName()));
  }
}

Trade-offs

  • Smallest change; in-range literals, including the limits, are unchanged; matches the XML query parser and the processors' own error for unparseable numbers; independent of LFV-050.
  • A range with one bound past the far end, such as i:[0 TO 3000000000], now fails to parse instead of returning the in-range documents. Applications that send such queries get an error instead of a wrong answer.
  • The check covers what a DecimalFormat returns (the configuration of upstream's test and of Luke): on Integer fields it compares doubleValue() with the int limits, which decides correctly for any integral value; on Long fields it range-checks only Double and Float results, so a custom NumberFormat that returns a BigDecimal or BigInteger beyond the long range is not caught. It is not a general literal validation: the format's own leniency (trailing text, a dropped fraction) stays. Reusing COULD_NOT_PARSE_NUMBER with the type name is a choice; a dedicated "out of range" message may read better.

What the checks validated

  • Gradle: :lucene:queryparser:checkGoogleJavaFormat passes; the whole :lucene:queryparser:test suite passes, 534 tests in 31 suites, 1 skipped, 0 failures, with the regression test (the minimal test with expectThrows) and TestPointQueryParser (4 tests).
  • An independent verifier's five-test literal matrix (its oracle accepts a clean parse error): 5 of 5 pass; every in-range control is unchanged and every out-of-range row is rejected, for example Could not parse text "2147483648" using java.lang.Integer.
  • The unchanged minimal test then fails with that exception, by design, hence the expectThrows form of the regression test.
  • Re-run with an unpublished file on a fresh scratch worktree of 645d4fe14a20: checkGoogleJavaFormat and the whole :lucene:queryparser:test suite pass, 534 tests in 31 suites, 1 skipped, 0 failures; the unchanged minimal test fails with the rejection.

Keep the range semantics: clamp the bound, match nothing beyond the limit (with LFV-050's builder fix) (validated by the checks)

In the processors, a bound beyond the type's range is clamped to the limit, and the limit is excluded when no value of the type can satisfy the bound: i:[2147483648 TO *] becomes i:{2147483647 TO *], i:[0 TO 3000000000] becomes i:[0 TO 2147483647]. The builder must then turn an exclusive bound at the limit into MatchNoDocsQuery, which is the fix of the sibling bug LFV-050 (it also makes an open bound inclusive). +54/-4 in the processors, +13 in the builder.

Trade-offs

  • Correct answers instead of an error, the unchanged minimal test is the regression test, and it fixes LFV-050 as well.
  • Larger, couples the two bugs, and changes the parsed query of every out-of-range literal. Without the builder part it does not work: the builder's lower + 1 wraps Integer.MAX_VALUE and the minimal test still fails with the same message.

What the checks validated

  • Gradle, same worktree: the unchanged minimal test passes; the literal matrix gives the exact count on every row (i:[2147483648 TO *] and i:4294967296 build MatchNoDocsQuery; i:[0 TO 3000000000] builds i:[0 TO 2147483647], 3 hits); checkGoogleJavaFormat and the whole :lucene:queryparser:test suite pass, 534 tests, 0 failures. The processor part alone: the minimal test still fails.

Saturate only (not recommended)

Replace intValue() with a clamp to [Integer.MIN_VALUE, Integer.MAX_VALUE].

Trade-offs

Incomplete: i:[2147483648 TO *] becomes i:[2147483647 TO 2147483647] and matches a document at exactly Integer.MAX_VALUE, the gap the Long sites already have; the one-document repro passes only because its value is 0. The single-value processor is not covered either.

Downstream note

An application that builds restriction filters from user-supplied query strings with this parser fails open: a number outside the int range, such as i:[2147483648 TO *], makes the filter match every document with the field.

How it was found

Code reading. A verifier checking the sibling bug LFV-050 (an exclusive bound at the int limit wraps in the builder) noticed that the processors narrow the parsed number without a range check; a follow-up executed it. The hunt then reproduced it on main and old releases, with an independent tie-break and a history sweep.

The method in full135 words

Method: non-formal. The 2026-10-22 study verified LFV-050, a finding of its BKD and points proof experiment, on main 7cecf79523. While reading PointRangeQueryNodeProcessor, its verifier noted that the parsed Number is narrowed with intValue() without a range check, and called it "a validation gap rather than a clean contract violation".

  • Study follow-up 4 executed it with its own four-literal test, found all four wrong, and showed that LFV-050's fixes do not change it: a separate root cause.
  • The hunt's candidate step reduced it to the one-method test and ran it on main and three releases; a review by another model family could not run code; a tie-break wrote its own literal matrix and main() probe, confirmed the root cause at both processor sites, and found the under-match form; a history sweep ran 136 releases.

Prior reports, history and checks

Prior reports

No prior report found (2026-10-30, re-checked) in GitHub issues, PRs and commits including the JIRA archive, the mailing lists, downstream projects, Stack Overflow or the web. Related: LUCENE-1768 (#2842) wrote the narrowing and LUCENE-7089 (#8145) copied it; neither discusses out-of-range literals.

Searches4 searches

Closest items, none about this bug: #2842 (LUCENE-1768), #8145 (LUCENE-7089), #7909 (any Format in NumericConfig), #7865 / #14697 (toQueryString of range nodes), #4411, #4416, #5207 (open ranges, the >= syntax), IPED #510 (an application's thread-safety bug around PointsConfig). Context: open #16820 (2026-10-12) lists the flexible and XML query parsers among possible deprecations for Lucene 11. Limits: private reports to the security list are not visible; GraphQL search does not cover PR review comments.

What was searched, when, and what turned up
WhereWhenHowFound
Search tool (GitHub issues/PRs/commits incl. the JIRA archive, dev@, java-user@, issues@, commits@, downstream, web)2026-10-30109 queries: the processor, builder and config class names (also the legacy NumericConfig ones), PointsConfig or StandardQueryParser with overflow, out of range or intValue, 2147483648, matches all documents252 distinct hits, 0 high and 12 medium overlap, none about this bug
GitHub GraphQL search (apache/lucene, lucene-solr, Lucene.NET, Solr, Elasticsearch, OpenSearch, all of GitHub)2026-10-30124 phrasings, including recent-activity queriesno report
Open PRs and file history2026-10-30410 open PRs by changed files; commits on both processorsno PR touches the point processors, the builder or PointsConfig
GitHub, re-check2026-10-30issue search for PointRangeQueryNodeProcessor; open PRs mentioning PointsConfig; last commits of both processors0 and 0; both files unchanged on upstream main 716d6f216f52

History

Written in LUCENE-1768 (c178c8eb1a81, 2011; first released in 3.4.0) for the legacy numeric config and copied into the points processors by LUCENE-7089 (89cc676f2bf5, 2016; 6.0.0). Every release from 3.4.0 to 10.6.0 (136 run) and main is affected.

Versions tested7 rows

Introduced by c178c8eb1a81, LUCENE-1768 "NumericRange support for new query parser" (trunk, 2011-07-09), shipped first through the branch_3x port f028bdb3eb20 (2011-08-18) in 3.4.0. Copied into PointRangeQueryNodeProcessor and PointQueryNodeProcessor by 89cc676f2bf5, LUCENE-7089 (2016-03-09; ports 306d3fe85240 on branch_6_0 and c8543efacf43 on branch_6x), first released in 6.0.0. The legacy path was removed in 7.0.0 (LUCENE-7418); the LUCENE-9570 reformat and #15889 left the narrowing as is. Old releases ran on JDK 25.

Versions and branches executed, with results
Version or commitVerdictResult
main 645d4fe14a20 affectedthe minimal test fails (gradle; standalone -ea and -da)
10.6.0, 10.5.2 affectedthe minimal test fails (-ea and -da)
9.12.3, 9.1.0 affectedthe minimal test fails
6.0.0 to 10.6.0 PointsConfiga main() port of the same case fails on every release
3.4.0 to 6.6.6 legacy NumericConfig (LegacyNumericConfig in 6.x)i:[2147483648 TO 4294967296] parses to i:[-2147483648 TO 0], 1 hit instead of 0
3.3.0 and older not affectedno numeric field configuration in the flexible parser
fixes A and B (scratch worktree at 645d4fe14a20) not affectedA rejects the literal, B returns 0 hits; the whole queryparser suite passes with either

Independent checks

A study follow-up, the candidate reproducer, an independent tie-break with its own tests and a 136-release history sweep all reproduce it; the reviewer from another model family could not run code and agreed on the root cause by reading.

Checks and verdicts276 words

The cross-model review returned a procedural "not real" because it could not execute anything. On substance it agreed on the root cause, found the inputs in contract and listed 9 of 10 evidence items for real; the tie-break answered its one counter-argument (an implicit int domain) with the under-match rows.

Each independent check and its verdict
CheckVerdictWhat it found
study follow-up 4 new, separate root causeIts four-literal test on main 7cecf79523: all wrong; LFV-050's fixes do not change it.
candidate reproducer realThe minimal test on main (-ea, -da), 10.5.2 and 9.12.3; a probe of 11 literal forms; a causal clamp.
cross-model review, refute lens (OpenAI GPT-5.6) procedural "not real", lowCould not run code; by reading: root cause agrees, inputs in contract, 9 of 10 evidence items for real.
Second adjudication real, highOwn five-test matrix on main (-ea with two seeds, -da), 10.6.0 (-ea, -da), 10.5.2 and 9.12.3 (-ea); main() probe on 6.0.0 to 10.6.0; root cause at both sites; independent of LFV-050 in both directions.
history sweep affected since 3.4.0136 releases on both configuration paths.
this doc gradle repro and two fixesRan the minimal test with gradle on a clean 645d4fe14a20 (fails) and validated fixes A and B with the whole queryparser suite.
fact checks of this doc, one from another model family (OpenAI GPT-5.6, read-only) sound; wording correctedOne re-ran the gradle repro (-ea, -da), fix A with the whole queryparser suite and a probe of the saturate-only option; both found the bug real as described.

Diagram file

The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes599 words

Finding record

LFV-082, snapshot of 2026-11-06

Registry title
Flexible StandardQueryParser narrows out-of-range int literals with intValue(), so i:[2147483648 TO *] matches every document with the field
Area
queryparser / flexible
Priority
Low, rank 533: Moved down from Medium: with the opt-in flexible parser and an Integer PointsConfig, an out-of-range literal (a timestamp typed into an int field) wraps so a range filter fails open and matches every document, silent, but the parser config is rare and no Elasticsearch or Solr path uses it.
Impact class
silent wrong results
Impact note
Users of the opt-in flexible StandardQueryParser with an Integer PointsConfig whose query string carries a literal outside the int range; ordinary scale; the same lines are in the 10.5.2 source (main executed).
Summary
You parse a point query whose literal lies outside the field's int range, such as i:[2147483648 TO *], i>=2147483648 or i:2147483648, with the flexible StandardQueryParser and an Integer PointsConfig. You expect no match, because no int value reaches that bound. Instead PointRangeQueryNodeProcessor and PointQueryNodeProcessor narrow the parsed number with Number.intValue(), which wraps it, so the range queries match all 5 documents of the repro and i:2147483648 matches the Integer.MIN_VALUE document.
Introduced
unknown (not yet bisected) (unknown (not yet bisected))
First affected version
unknown (not yet bisected)
Affected versions
main [exec]; the same lines are in the 10.5.2 source [read, registry build]
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed by AI-run checks: a cross-model review that could only read the code (its low-confidence “not real” is procedural), an adjudication that ruled it real and a prior-report search; not yet triaged by a person.
Found by
non-formal: code reading
Upstream and prior reports
none found (follow-up 4 read-only gh searches, 2026-10-22)
Lucene commit tested
main 645d4fe14a20: the minimal test through gradle (fails) and the standalone runner (-ea, -da); the 10.6.0, 10.5.2 (-ea, -da) and 9.12.3 release jars; an independent verifier's own five-test literal matrix on main, 10.6.0, 10.5.2 and 9.12.3 and its main() probe on 6.0.0 to 10.6.0; a history sweep of 136 releases (3.4.0 to 10.6.0); two fixes in a scratch worktree at 645d4fe14a20 with the whole :lucene:queryparser test suite.

Lucene and upstream sources

Finding record
  • a Lucene clone tag releases/lucene/10.5.2: lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointRangeQueryNodeProcessor.java:111-112
  • SECURITY.md:38, 50 (threat model)
The finding in 30 seconds
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/StandardQueryNodeProcessorPipeline.java:53-55
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointRangeQueryNodeProcessor.java:79-114 (parse :82, :97; errors :84-91, :99-106; intValue :109-111; longValue :112-114)
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointQueryNodeProcessor.java:83-97, :104-108, :115
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/builders/PointRangeQueryNodeBuilder.java:63-79
Who is affected
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/overview.html:38-40
  • a Lucene main checkout (645d4fe14a20) lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java:199-249 (PointsConfig :221-240, parse :244)
Why it looks unintended
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/StandardQueryParser.java:145-158
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/config/PointsConfig.java:35-40, :56-63
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointRangeQueryNodeProcessor.java:84-91, :99-106; processors/PointQueryNodeProcessor.java:85-92, :104-108
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/builders/PointRangeQueryBuilder.java:69-70, :83-87, :106-108
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/test/org/apache/lucene/queryparser/flexible/standard/TestPointQueryParser.java:33-84
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/config/PointsConfig.java:38-45, :84-94 (any caller-supplied NumberFormat)
  • OpenJDK 25.0.3 src java/text/NumberFormat.java:460-475, :499-507, :509-515 (parse(String) reads a leading number; integer-only parsing stops at the decimal point)
Diagrams
  • a Lucene main checkout (645d4fe14a20) lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/processors/PointRangeQueryNodeProcessor.java:109-111

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

A summary still, made in parallel with this doc from the code at 645d4fe14a20 and the executed runs.

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.