{"record":{"id":"03b15d1093523a12","repo":"elastic/elasticsearch","slug":"maximum-nested-depth-of","errorCode":null,"errorMessage":"maximum nested depth of ","messagePattern":"maximum nested depth of ","errorType":"validation","errorClass":"ParseException","httpStatus":null,"severity":"error","filePath":"libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownBinary.java","lineNumber":475,"sourceCode":"        }\n    }\n\n    private static void writeWKBGeometryCollection(\n        StreamTokenizer stream,\n        ByteArrayOutputStream out,\n        ByteBuffer scratch,\n        boolean coerce,\n        int depth,\n        boolean explicitZ,\n        GeometryValidator validator\n    ) throws IOException, ParseException {\n        if (WellKnownText.nextEmptyOrOpen(stream).equals(WellKnownText.EMPTY)) {\n            writeInt(out, scratch, 7);\n            writeInt(out, scratch, 0);\n            return;\n        }\n        if (depth >= WellKnownText.MAX_NESTED_DEPTH) {\n            throw new ParseException(\"maximum nested depth of \" + WellKnownText.MAX_NESTED_DEPTH + \" exceeded\", stream.lineno());\n        }\n        List<byte[]> subGeometries = new ArrayList<>();\n        ByteArrayOutputStream subOut = new ByteArrayOutputStream();\n        writeWKBGeometry(stream, subOut, scratch, coerce, depth + 1, validator);\n        byte[] subBytes = subOut.toByteArray();\n        subGeometries.add(subBytes);\n        boolean hasZ = wkbTypeHasZ(subBytes);\n        while (WellKnownText.nextCloserOrComma(stream).equals(WellKnownText.COMMA)) {\n            subOut = new ByteArrayOutputStream();\n            writeWKBGeometry(stream, subOut, scratch, coerce, depth + 1, validator);\n            subBytes = subOut.toByteArray();\n            subGeometries.add(subBytes);\n            if (wkbTypeHasZ(subBytes) != hasZ) {\n                throw new IllegalArgumentException(\"all elements of the collection should have the same number of dimension\");\n            }\n        }\n        WellKnownText.checkZorMAttribute(explicitZ, hasZ);\n        writeInt(out, scratch, hasZ ? 1007 : 7);","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownBinary.java#L457-L493","documentation":"writeWKBGeometryCollection (line 460) guards recursion depth: at line 474 it throws ParseException if depth >= WellKnownText.MAX_NESTED_DEPTH (1000, defined at WellKnownText.java:49). Each nested GEOMETRYCOLLECTION increments depth (passed as depth+1 at lines 479/485). This is a denial-of-service safeguard against pathologically or maliciously nested input that would otherwise unbounded-recursion the parser.","triggerScenarios":"Calling WellKnownBinary.fromWKT on a WKT string with GEOMETRYCOLLECTION nesting deeper than 1000 levels, e.g. 1001 layers of 'GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(...(POINT(0 0))...))'. Generated/malicious input is the typical source.","commonSituations":"Accepting untrusted WKT input without a depth sanity check (web-facing ingestion endpoints). Bugs in data-generation code that recursively wraps collections. Adversarial payloads aimed at stack-overflowing the parser. Extremely unlikely from normal GIS data, where nesting rarely exceeds 2-3 levels.","solutions":["Apply a much smaller client-side depth limit (e.g. 20-50) on untrusted WKT before passing to fromWKT, to fail fast and free the parser from the heavy work.","Validate/cap GEOMETRYCOLLECTION nesting in your ingestion schema or ingest processor.","If legitimately deep nesting is expected (rare), pre-flatten the collection before serialization.","Catch ParseException and surface a 400 to the client rather than crashing the ingest path."],"exampleFix":"// before\nbyte[] wkb = WellKnownBinary.fromWKT(deeplyNestedWkt, BO, false, v); // throws at depth 1000\n\n// after — pre-check depth cheaply\nif (countNesting(wkt, \"GEOMETRYCOLLECTION\") > 50) {\n    throw new IllegalArgumentException(\"nesting too deep\");\n}\nbyte[] wkb = WellKnownBinary.fromWKT(wkt, BO, false, v);","handlingStrategy":"validation","validationCode":"static final int CLIENT_MAX_DEPTH = 50;\nint countCollectionNesting(String wkt) {\n    int d = 0, max = 0;\n    for (int i = 0; i < wkt.length(); i++) {\n        if (wkt.regionMatches(true, i, \"GEOMETRYCOLLECTION\", 0, 18)) { d++; max = Math.max(max, d); }\n        // crude: decrement on ')' is unreliable; better to parse\n    }\n    return max;\n}\nboolean depthWithinBudget(String wkt) { return countCollectionNesting(wkt) <= CLIENT_MAX_DEPTH; }","typeGuard":"// no type guard — operates on raw WKT string before parsing","tryCatchPattern":"try {\n    return WellKnownBinary.fromWKT(wkt, bo, coerce, v);\n} catch (ParseException e) {\n    if (e.getMessage().startsWith(\"maximum nested depth\")) {\n        throw new IllegalArgumentException(\"input nesting exceeds client budget\", e);\n    }\n    throw e;\n}","preventionTips":["Apply a small client-side depth cap (e.g. 50) on untrusted WKT before fromWKT.","Reject pathologically nested GEOMETRYCOLLECTIONs at the API boundary.","Treat the library's 1000 cap as a DoS backstop, not a target."],"tags":["geo","wkt","wkb","geometrycollection","nesting","dos-safeguard","elasticsearch"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}