Skip to content

chore(deps): update dependency org.mock-server:mockserver-netty-no-dependencies to v7.5.0 - #2354

Merged
renovate[bot] merged 1 commit into
mainfrom
renovate/org.mock-server-mockserver-netty-no-dependencies-7.x
Jul 29, 2026
Merged

chore(deps): update dependency org.mock-server:mockserver-netty-no-dependencies to v7.5.0#2354
renovate[bot] merged 1 commit into
mainfrom
renovate/org.mock-server-mockserver-netty-no-dependencies-7.x

Conversation

@renovate

@renovate renovate Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
org.mock-server:mockserver-netty-no-dependencies (source) 7.4.07.5.0 age confidence

Release Notes

mock-server/mockserver-monorepo (org.mock-server:mockserver-netty-no-dependencies)

v7.5.0

Security
  • BREAKING: response templates can no longer reach arbitrary Java classes by default, closing the
    template remote-code-execution path reported as
    GHSA-7pwj-xvc2-hfpc.

    A caller who can reach the management API can register an expectation, and a response template was able
    to load java.lang.Runtime and execute OS commands in the MockServer process. Both engines that could
    do this are now sandboxed out of the box:
    • velocityDisallowClassLoading now defaults to true (was false), installing Velocity's
      SecureUberspector so a template cannot reach classes through $request.class.classLoader.loadClass(...).
      This is the more exposed half of the issue, and the half the report did not cover: Velocity ships in
      the DEFAULT distribution, whereas the JavaScript engine does not.
    • JavaScript templates now resolve no Java classes unless an operator grants them. Previously an
      empty javascriptAllowedClasses and empty javascriptDisallowedClasses meant unrestricted
      Java.type(...) access; that combination — the out-of-the-box state — now denies every class.
    • The GraalJS guest context no longer grants access to the members of java.lang.Class or
      java.lang.ClassLoader. Denying classes at Java.type(...) alone was not sufficient: real host
      objects are bound into the context (faker and the other built-in helpers), and under the previous
      HostAccess.ALL a template could walk from one of them to a classloader —
      faker.getClass().getClassLoader().loadClass('java.lang.Runtime') — reaching Runtime without the
      class filter ever being consulted. That walk is now closed, so host-class lookup is the single complete
      gate; a regression test drives four such walks (including through request) and fails if any resolves.
      Velocity's SecureUberspector already blocked the equivalent walk through its own bound helpers, which
      is now covered by a test too.
      Both flips are fully reversible with one property and remove no functionality: set
      mockserver.velocityDisallowClassLoading=false, or list the classes your templates need in
      mockserver.javascriptAllowedClasses (the single entry * lets any class resolve again). Templates that
      do not touch Java classes are unaffected, which is the overwhelming majority — JavaScript templates have
      the full ES2023 standard library available regardless of this setting. A refused class is logged once at
      WARN naming the class and the property to set, because GraalJS otherwise surfaces a refusal only as the
      class being undefined ("... is not a function"); the log is bounded and de-duplicated so a hostile
      template cannot flood it. mockserver.javascriptAllowedClasses is now also settable through the Spring
      test listener's @MockServerTest properties, which it was not before — it was a nice-to-have while the
      default was unrestricted, and is the only way to grant a class now that it is not. The insecure-mode WARN
      now fires when an operator has explicitly opened the
      sandbox rather than when it is closed. Proven end-to-end by a Netty integration test that registers the
      reported payload through the real management API and asserts the OS command creates no marker file, with
      a negative control on a deliberately unsandboxed server that DOES create it — so a regression cannot pass
      as an inert payload. This lands DEF-2 and DEF-3 of docs/plans/later/security-defaults.md ahead of the
      other default flips listed there; JavaScript went further than that plan proposed (deny everything, not a
      built-in "safe types" allow-list) because deny-by-default is the only form that stays safe as the JDK
      grows new reachable classes.
Fixed
  • A property file that cannot be read is now reported instead of ignored in silence
    (#​2358).
    When a
    mockserver.propertyFile an operator had explicitly configured could not be read, MockServer applied
    none of its properties and said nothing about it — at any log level. The only symptom was that every
    property in the file appeared to be at its default, which surfaces far downstream as unexplained
    behaviour: in the reported case an unreadable (but present) mounted file meant initializationJsonPath
    was never set, so no expectations loaded, no loading JSON initialization file: line appeared, and no
    error was logged either. The message existed but was unreachable in practice — gated at DEBUG and
    emitted during static initialisation, before any log level has been applied, so neither
    -Dmockserver.logLevel=DEBUG nor a -logLevel argument could surface it. Such a file is now logged at
    WARN, naming the path and the underlying reason verbatim; because FileNotFoundException covers "not
    there" and "not allowed to read it" alike, that reason is usually the whole answer (Permission denied
    in the reported case, typically SELinux labelling or a rootless/user-namespace UID mismatch). A property
    file that is merely absent at its default location stays quiet, as does the Docker image's built-in
    -Dmockserver.propertyFile=/config/mockserver.properties, which the entrypoint always passes and which
    therefore expresses no intent — otherwise every container started without a mounted config would warn.
    Inside the image, only MOCKSERVER_PROPERTY_FILE can express that intent, and it does.
  • The mockserver-node launcher suite no longer fails intermittently on a TLS handshake reset. The
    two tests that exercise jvmOptions did so over HTTPS against a server started with
    dynamicallyCreateCertificateAuthorityCertificate=true, and issued that HTTPS request as soon as
    start_mockserver resolved. start_mockserver only proves the HTTP control plane is answering — it
    polls PUT /mockserver/retrieve over plain HTTP — but with a dynamically created certificate
    authority the server still has to generate a CA key pair and a leaf certificate before it can serve
    TLS on that same (port-unified) port. A handshake arriving in that window was closed mid-negotiation
    and surfaced as ECONNRESET "Client network socket disconnected before secure TLS connection was
    established", failing whichever of the two tests lost the race. This accounted for every
    mockserver-node failure on master over the preceding 40 builds (5 of 40, ~12%), so it was the sole
    cause of the pipeline's intermittent red. Both tests now wait for an actual TLS handshake to complete
    before asserting, which gates them on the condition they really depend on rather than retrying the
    assertions. The new waitForTlsReady helper is verified to reject — not resolve — both when nothing
    is listening and when a listener accepts the TCP connection then destroys it mid-handshake, which is
    exactly the failure signature it exists to absorb. The readiness budget is deliberately generous
    (120s): waiting costs nothing when the server is healthy, since a ready server completes the
    handshake on the first attempt in milliseconds, so the limit only decides how much CI contention is
    tolerated before a slow start is misreported as a fault. An earlier 30s budget went green five builds
    running and then expired on a loaded agent — the same flake wearing a clearer error message. A start
    that takes over 5s is now reported even when it passes, because readiness creeping towards the limit
    is the signal that the next run will not make it.
  • archiver.glob() works again in @mockserver/testcontainers (Node), and CVE-2026-14257 stays
    closed.
    The previous remedy for the brace-expansion denial of service (GHSA-mh99-v99m-4gvg,
    patched only in 5.0.8) was a blanket "brace-expansion": "^5.0.8" override. That resolved the whole
    tree to a single hoisted 5.0.8 and npm audit reported zero vulnerabilities — but 5.x changed the
    CommonJS export from a callable function to an object ({ expand, EXPANSION_MAX, ... }), while the
    minimatch copies actually installed (3.1.5, 5.1.9, 9.0.9) all call it as expand(pattern). Every
    glob containing a brace therefore threw TypeError: expand is not a function, crashing
    archiver.glob(). The blast radius is narrower than it first looks — testcontainers copies files
    with archiver.directory()/.append(), which pass no brace pattern and still work — so what broke
    is brace globbing for anything in this module's runtime tree that does use it. The failure was
    invisible because minimatch short-circuits patterns with no {, so plain globs kept working and the
    unit suite stayed green. The override is now targeted: readdir-glob and archiver-utils' glob
    take minimatch@^10.2.5, which depends on brace-expansion@^5.0.5 and is written against the new
    API, so both runtime copies land on the patched 5.0.8 with a matching minimatch. jest keeps its own
    minimatch@3.1.5 + brace-expansion@1.1.16 pairing and is untouched. npm audit --omit=dev still
    reports 0 vulnerabilities, and a new dependency-integrity unit test drives a brace pattern through
    both runtime minimatch copies and through a real archiver.glob() tar, plus asserts expansion stays
    bounded — it fails against the blanket override, so the silent half of this cannot return.
  • A forward responseOverride that replaces the body no longer inherits the upstream response's
    Content-Length, which truncated the response on the wire.
    The override swapped the body but left the
    upstream header in place, so the client read only as many bytes as the body it replaced — a 34-byte
    override behind an upstream Content-Length: 13 arrived as 13 bytes — or hung waiting for bytes that
    never came. The stale header is now dropped so the encoder recomputes it from what is actually written;
    a Content-Length set by the override itself, and connectionOptions.contentLengthHeaderOverride, are
    still honoured, and a header-only override (one that sets no body) is untouched. This affects every body
    override, and it was the remaining reason a FILE response body returned from a responseOverride
    still reached the client wrong after
    #​2450: the file was materialised
    correctly and then cut short by the stale length. Covered by a Netty integration test that drives a real
    forward-with-override through a real upstream and asserts the bytes the client receives.
  • The JetBrains plugin's LLM tool window now sends a valid expectation
    (#​2455).
    "Load into Server" was
    rejected with 400 incorrect expectation json format because the builder emitted a shape that never
    existed on the server: a flat completion string, a top-level finishReason, stream, and usage,
    and a provider of OPEN_AI. The completion text, streaming flag, stop reason, and token usage
    belong INSIDE the completion object (text, streaming, stopReason, usage.inputTokens /
    usage.outputTokens), and providers are the Provider enum names (OPENAI, AZURE_OPENAI, …). The
    provider and field catalogues shared with the VS Code extension are corrected the same way — they
    offered OPEN_AI, VERTEX_AI, messages, stream, finishReason and a top-level usage, none of
    which the server accepts — and completion inside a completion object now offers the nested fields.
    The plugin has always bundled the correct schema; it simply never validated its own output against it,
    and the previous tests asserted the builder matched the same invented shape it produced. Both editors
    now validate against the bundled schema in their test suites.
  • httpLlmResponse.provider now accepts every provider MockServer implements. The JSON Schema enum
    listed 9 of the 14 org.mockserver.model.Provider constants, so MISTRAL, XAI, DEEPSEEK, GROQ,
    and OPENROUTER were rejected with 400 incorrect expectation json format even though each has a
    fully registered response codec. The five missing values are added to the core schema, the generated
    VS Code and JetBrains schemas, and both copies of the OpenAPI specification, and a new parity test
    fails if the enum and Provider ever diverge again in either direction. The provider list on the
    LLM response mocking documentation and in the Rust client's field docs is updated to match.
  • The cloud blob-store, async-broker and transparent-proxy CI steps no longer OOM-kill their own build
    before any test runs.
    Each ran its Docker container with --memory=4g, but mockserver/.mvn/jvm.config
    pins the Maven JVM to -Xmx6144m and the wrapper prepends it to MAVEN_OPTS, so the -am dependency
    build was permitted a 6 GB heap inside a 4 GB cgroup and the kernel intermittently killed it with exit 137
    — losing the very coverage those fail-closed steps exist to guarantee. Raised each to --memory=7g, the
    value every other ./mvnw step already uses and which fits the single-agent c5.2xlarge/m5.2xlarge
    default-queue instances with margin.
Added
  • A cassette is now auto-registered when a fixture is loaded or recorded via the MCP tools, so it
    appears under GET /mockserver/cassettes without a separate PUT /mockserver/cassettes call.

    Previously the server-side cassette registry was populated only by an explicit
    PUT /mockserver/cassettes, so a fixture loaded with the load_expectations_from_file MCP tool, or
    written with record_llm_fixtures, never showed up in the dashboard's Cassettes tab unless the
    caller also registered it by hand. Both MCP tool handlers now register the fixture in
    CassetteRegistry at the point the file is loaded/written — the file path as the key, the loaded/
    written expectation count, and an origin of loaded or recorded respectively — so
    GET /mockserver/cassettes (which serialises that registry) lists it automatically. Re-loading or
    re-recording the same path updates the existing entry in place rather than duplicating it.

  • Clustered (Infinispan) expectation reload-on-startup is now proven end-to-end. A new test
    (ClusteredExpectationPersistenceReloadTest in mockserver-state-infinispan) forms an in-JVM
    JGroups cluster consisting of a bare "fleet keeper" InfinispanStateBackend that stays up for the
    whole test plus a full MockServer node started with stateBackend=infinispan,
    clusterEnabled=true and persistExpectations=true. An expectation is created on that node over
    the wire, the persisted document is polled for through the keeper's backend (proving it really
    replicated across the REPL_SYNC blob cache), the node is then stopped completely, and a fresh node
    is started against the same cluster and the same persistedExpectationsPath — which must restore
    the expectation and MATCH a real HTTP request with it. The local persisted file is asserted to be
    empty first, so the restore cannot be coming from the filesystem-initializer route. The reload path
    in ExpectationFileSystemPersistence was already covered at unit level in mockserver-core
    (ExpectationBlobStoreRestoreTest, against an InMemoryBlobStore, with no server and no cluster)
    and end-to-end only against S3/MinIO behind a Docker gate; what no test proved is that a clustered
    node's InfinispanBlobStore is the store HttpState wires into that restore, nor that a real
    restarted member of a live cluster recovers the fleet's shared expectations. A second test sets
    blobStoreRestoreTimeoutSeconds=0 (the documented way to skip the restore) and asserts the fresh
    node does NOT serve the expectation, which permanently pins the fact that no other mechanism —
    JGroups state transfer of the expectations cache, a stray invalidation event, or the local file —
    restores expectations when a node starts. Verified by a positive control: disabling the reload
    path in production makes the restarted node answer with an empty body and turns the test red.

  • The response-aware arm of the eviction false-green guard is now proven end-to-end over HTTP. A new
    Netty integration test (EvictedResponseVerificationIntegrationTest) boots a real server with
    maxLogEntries=2 and failVerificationOnEvictedLog=true, registers an expectation so a GET /was-responded exchange is recorded as a real EXPECTATION_RESPONSE request-response pair, then floods
    the bounded event log with further unmatched traffic so that pair is evicted. A subsequent
    verify(request("/was-responded"), response().withStatusCode(418), never()) through the Java client must
    throw an AssertionError saying the response "could not be verified" because entries were discarded
    after reaching maxLogEntries. MockServerEventLog implements this guard twice — once in verifyRequest
    and once, through a completely separate counting path over recorded pairs, in verifyResponse — and only
    the request arm had an *IntegrationTest; the response arm was covered solely by an engine-level test
    against an in-process event log. The test uses never() because it is the simplest shape that reaches
    the guard: the guard sits on the PASS branch behind any asserted upper bound (getAtMost() != -1 — so
    atMost(n), between(0,n) and exactly(0) reach it too), whereas an atLeast(1)/once() verification
    of an evicted pair fails earlier with an ordinary "Response not found" message and proves nothing.
    never() is exactly the case a guard-less server would answer with a silent false green. The assertion pins the message to Response could not be verified so it cannot be
    satisfied by the request-side arm. Verified by a positive control (disabling only the response-side guard
    in production makes the verification pass silently and turns the test red).

  • The eviction false-green guard is now proven end-to-end over HTTP. A new Netty integration test
    (EvictedLogVerificationIntegrationTest) boots a real server with maxLogEntries=2 and
    failVerificationOnEvictedLog=true, records a GET /was-called request, then floods the bounded
    request-log ring with further traffic so the /was-called entry is evicted. A subsequent
    verify(request("/was-called"), never()) through the Java client must throw an AssertionError whose
    message says the log "could not be verified" because entries were discarded after reaching
    maxLogEntries — proving the guard refuses to certify absence it can no longer see, rather than
    silently passing. Previously the guard was only covered by an engine-level test against an in-process
    MockServerEventLog and no *IntegrationTest exercised it across the wire. Verified by a positive
    control (disabling the guard in production makes verify(never()) pass silently and turns the test
    red).

  • Custom gRPC response metadata and trailing metadata are now proven against a real grpc-java
    client.
    Two new tests in GrpcUnaryClientIntegrationTest register an expectation whose gRPC
    response carries both custom response metadata authored with withHeader(...) and custom trailing
    metadata authored with withTrailer(...), drive it with a live grpc-java client, and read the
    values back off the real io.grpc.Metadata objects the client receives (via a capturing
    ClientInterceptor, and via StatusRuntimeException.getTrailers() on the error path). The
    assertions are deliberately discriminating: the response metadata must arrive in the initial
    headers
    and not in the trailers, the trailing metadata must arrive in the trailers and not be
    folded into the initial headers, and both values must round-trip byte-for-byte including a value
    carrying =, ;, , and spaces. Previously this behaviour was exercised only structurally
    (EmbeddedChannel / model-level assertions, which cannot tell a trailer emitted as a trailer from
    one folded into the headers) and by the existing -bin metadata tests, which deliberately accept
    the value from either side because a body-less unary response may legitimately collapse to
    Trailers-Only. Verified by positive controls: dropping the user-authored trailers turns both tests
    red, and dropping the user-authored response headers turns the header assertion red.

  • The maxResponseBodySize limit is now proven behaviourally against a real upstream. A new
    integration test (MaxResponseBodySizeIntegrationTest) boots a forwarding MockServer configured with a
    4KB maxResponseBodySize, points it at a raw upstream socket that returns a 64KB body, and drives it
    over a plain client socket: the oversized body fails the forward and the client receives 502 Bad
    Gateway
    with none of the payload relayed, while a control request whose body sits under the limit is
    forwarded intact. A third case repeats the oversized body with Transfer-Encoding: chunked and no
    Content-Length, proving the cap is enforced against the bytes actually accumulated by the forward
    client's aggregator rather than merely against a declared header. Previously this documented,
    memory-protecting bound — read whenever a forward-client pipeline is built — had no behavioural
    coverage at all, so a regression that dropped the wiring (or passed an unbounded value) would have
    removed the limit silently; only the inbound analogue maxRequestBodySize was verified. The new test
    covers the HTTP/1.1 forward aggregator; the HTTP/2 forward path reads the same property (for the
    per-stream aggregator and to derive the client's maxFrameSize) and remains uncovered.
    maxResponseBodySize accordingly moves from ENFORCEMENT_EXEMPT to ENFORCEMENT_VERIFIED in
    ConfigurationEnforcementClassificationTest. Verified by a positive control (restoring an unbounded
    aggregator lets the oversized body through with a 200 and turns both over-limit assertions red).

  • The Ruby client now proves live SSE stream consumption over the wire. New integration examples
    (spec/integration_spec.rbSSE streaming) register an httpSseResponse expectation via the Ruby
    client against a running MockServer, then open a real streaming HTTP consumer and assert every data:
    frame arrives in order, that the reconstructed multi-delta message matches, and that a multi-line
    data: payload survives the framing intact (Content-Type: text/event-stream). Previously the Ruby
    suite only asserted the JSON keys of a built streaming expectation (a2a_spec) and never consumed a
    live SSE stream, so a silent server-emission or client-parsing drop would have gone uncaught. Verified
    by a positive control (dropping events from the emitted stream turns the received-frames assertion red).

  • The assumeAllRequestsAreHttp protocol-detection fallback now has direct unit coverage. Two
    paired EmbeddedChannel tests in DirectProxyUnificationHandlerTest drive
    PortUnificationHandler.decode() with an HTTP request using a non-standard method (PURGE, which is
    not one of GET/POST/PUT/HEAD/OPTIONS/PATCH/DELETE/TRACE/CONNECT): with
    assumeAllRequestsAreHttp=true the full HTTP pipeline is added (rather than falling to binary request
    proxying), and with the flag disabled the HTTP codec is not added — proving the flag is the only
    difference. Previously the fallback branch was exercised only by a live-socket integration test and
    the config getter's own unit test, so the EmbeddedChannel protocol-detection path for the flag was
    unexercised.

  • HTTP/3 streaming response bodies are now proven end-to-end through the action pipeline with a real
    QUIC client.
    A new integration test (Http3StreamingForwardIntegrationTest) registers a forward
    expectation on the HTTP/3 port (with streamingResponsesEnabled) pointing at an upstream Server-Sent
    Events stream that serves an early event immediately and withholds the late event for 1.5s, then drives
    it with a live Netty QUIC client and asserts both events arrive as SEPARATE DATA frames spread across
    that delay — proving the streaming relay funnels through HttpActionHandler ->
    ResponseWriter.writeResponse -> Http3ResponseWriter.writeStreamingResponse and emits chunks
    incrementally. Previously Http3StreamingIntegrationTest drove Http3ResponseWriter directly from a
    hand-built QUIC server (bypassing expectation matching), and Http3MockingMatrixIntegrationTest
    exercised the real pipeline over QUIC but only with non-streaming actions, so incremental delivery of a
    streamed body through the full pipeline was untested. QUIC-gated like the sibling HTTP/3 tests so it
    skips cleanly where the native transport is unavailable.

  • The dashboard's Monaco code editor is now proven in a real browser end-to-end. A new Playwright
    e2e test (mockserver-ui/e2e/dashboard.spec.ts) drives the actual bundled Monaco editor in the
    served dashboard's composer against a live MockServer: it asserts Monaco's own DOM
    (.monaco-editor / .view-lines) renders, authors a JSON response body via real editor input,
    raises and clears a live validation marker from Monaco's JSON language web worker, then registers
    the mock and confirms the Monaco-authored body round-trips to the server (present in
    PUT /mockserver/retrieve and served verbatim on the matching request). Previously the 178
    jsdom/vitest specs globally replaced Monaco with a bare <textarea>, so nothing exercised the real
    editor's tokenisation, web-worker validation, or DOM.

  • SOCKS4 CONNECT tunnelling is now proven end-to-end over a real socket. A new socket-level
    integration test (NettyHttpProxySOCKS4IntegrationTest) performs a raw SOCKS4 CONNECT handshake
    against a bound MockServer targeting a loopback EchoServer, then sends an HTTP GET through the
    granted tunnel and asserts the EchoServer received the request and returned 200 (bytes relayed by
    Socks4ConnectHandler). Previously SOCKS4 was only exercised by an EmbeddedChannel unit test
    (Socks4ProxyHandlerTest, which asserts handler removal) while every real-socket proxy integration
    test used SOCKS5, so nothing drove the SOCKS4 relay path over the wire.

  • Per-host forward-proxy client-certificate selection is now proven at a real TLS handshake. A new
    integration test (ForwardWithCustomClientCertificateByHostIntegrationTest) configures
    forwardProxyClientCertificatesByHost to present two independent client certificates (each backed by
    its own CA) keyed by host, then forwards through MockServer to two secure upstream EchoServers that
    each REQUIRE client auth and trust only ONE of the two client CAs. Because the host string is the
    cert-mapping key while the connection target is fixed independently by the forward port, the same
    upstream is reached under both host keys: the mapped cert is accepted (200) and the mismatched cert is
    rejected at the handshake (502). This makes the presented client certificate a load-bearing assertion,
    verified by a positive control (degrading the mapping to always present cert A flips host B's accepted
    case to 502). Previously NettySslContextFactoryTest asserted only SslContext identity/distinctness
    and the pure resolver -- nothing drove the per-host cert to an actual mTLS handshake.

  • LLM streaming physics for Gemini, Ollama, and Bedrock are now proven over a real socket. Streaming
    physics over the wire was previously e2e-tested only for Anthropic and OpenAI (both SSE); Gemini, Ollama,
    and Bedrock rested on self-derived golden JSONL plus codec unit tests, with no socket streaming e2e. Three
    new tests in LlmAgentLoopE2eTest serve a streaming httpLlmResponse for each provider, connect a real
    socket client, and assert both that the wire Content-Type is the provider's streaming media type
    (text/event-stream for Gemini SSE, application/x-ndjson for Ollama NDJSON,
    application/vnd.amazon.eventstream for Bedrock AWS event-stream binary framing) and that the text
    reconstructed by concatenating the streamed deltas — Gemini candidates[].content.parts[].text, Ollama
    message.content, and Bedrock's base64-wrapped Anthropic text_delta fragments decoded from the
    CRC32-validated binary event-stream messages — equals the completion text exactly. The Bedrock case
    de-chunks the HTTP/1.1 chunked body and decodes the binary framing end to end.

  • The PHP client's fidelity harness now gates the TYPED model, not just raw replay. The existing
    RoundTripFidelityTest deserialises each shared fixture with Expectation::fromArray(), which stores
    the decoded array verbatim in rawData and replays it unchanged -- so it records zero gaps for every
    fixture BY CONSTRUCTION and can never detect a field the typed builders (HttpResponse, HttpForward,
    HttpError, HttpRequest) fail to model. A new TypedRoundTripFidelityTest closes that tautology:
    for every shared fixture it reconstructs each action/matcher block THROUGH the typed model (reflection
    copies across only the properties each class declares, recursing into declared nested typed objects,
    then serialises via the class's own toArray()) and diffs the rebuilt structure back against the
    fixture -- the server-schema side of the contract -- so any server field the typed model drops surfaces
    as a concrete diff path derived from the corpus, never from the client's own key list. This immediately
    documented five real request-matcher gaps the raw harness hid (dnsClass/dnsName/dnsType,
    pathParameters, protocol, plus NottableString method/path), each pinned in a per-field gap
    ledger with a stale-entry ratchet, while the httpResponse/httpForward/httpError models are proven
    to cover their entire fixture surface. A positive-control test proves the gate fires when a typed
    builder drops a field it is supposed to carry (removing statusCode from HttpResponse::toArray()
    turns the suite red for every fixture carrying it, and green again on restore) -- the exact regression
    the raw-replay harness cannot catch. Shared comparator logic is extracted to FidelityComparator.

  • The Go client's FORWARD and ERROR response actions are now proven over the wire. New integration
    tests (response_action_integration_test.go) register a httpForward and a httpError
    (dropConnection) action via the Go client and drive real requests that assert the SERVER actually
    performs them: the forwarded request loops back through a match-once, higher-priority self-forward to
    a distinct upstream response body (needing no externally-reachable upstream, so it runs identically in
    the CI sibling-container harness and against a locally port-mapped server), and the error endpoint
    drops the connection at the transport level (no HTTP response) while a control endpoint on the same
    server still answers cleanly. Previously the Go client's response-action coverage was builder/JSON
    only -- no test drove a forward or error action to completion over a socket.

  • Rust client wire tests proving the server actually enforces negation matchers and performs
    response actions.
    The Rust integration suite previously only asserted control-plane serialization
    (matcher_value_tests.rs) and registered a forward expectation it never drove (test_forward_expectation
    "won't actually forward"), so nothing proved a running MockServer acted on either. Three new
    #[ignore]d integration tests drive a live server over the data plane via a dependency-free raw
    socket: test_negation_matcher_enforced_over_wire registers a NottableString negation (bare
    "!foo", explicit MatcherValue::not_literal, and an escaped literal "!foo") and asserts the
    server matches a non-foo value (200) while excluding foo (404) — and that an escaped "!foo"
    matches literally rather than as a negation; test_forward_action_actually_forwards registers a
    higher-priority run-once FORWARD that loops back to the server's own port plus a lower-priority
    fall-through RESPOND, and asserts the distinctive fall-through body is returned only if the forward
    genuinely executed (topology-independent — no external upstream); and
    test_error_action_actually_returns_raw_bytes registers an ERROR action and asserts the server
    writes the configured raw bytes back. Run in CI by the existing rust-integration-test step.

  • Wire-level .NET client test coverage for NottableString header negation and for
    forward/error response actions.
    The .NET integration suite asserted expectation creation but
    never proved the server honours what the client sends: there was no test that a !foo header
    matcher (MatcherValue.NotLiteral) is transmitted and enforced over the wire, nor that a
    registered forward or error action is actually performed. A new WireBehaviorTests drives real
    requests through a running MockServer (reached via the existing MOCKSERVER_URL harness) to prove:
    a "not foo" header matcher matches a non-foo request (200) and rejects a foo request (404); the
    escaped literal MatcherValue.Literal("!foo") matches only a header whose value really is !foo;
    a forward action is genuinely forwarded (the path is received twice — original plus the re-entered
    forward — not merely served directly); and an error action actually drops the connection (a
    transport failure, not an HTTP status). Each assertion was confirmed to redden when the
    corresponding client behaviour is degraded.

  • Live-broker test coverage proving Kafka SASL credentials reach and are enforced by a real
    broker.
    Kafka SASL/SSL security was only asserted at the property-map level
    (KafkaMessagePublisherSecurityTest) and every live-broker Kafka integration test used a plaintext
    bootstrap, so nothing proved the configured credentials actually authenticate against a broker. A
    new KafkaSecurityLiveBrokerIntegrationTest starts a Testcontainers Kafka whose external listener
    is SASL_PLAINTEXT/PLAIN with a broker-side JAAS config that knows a single credential, then
    drives MockServer's KafkaMessagePublisher with a KafkaSecurity: a correctly-credentialed
    publisher publishes successfully and the message is read back by a matching credentialed consumer,
    while a wrong-password publisher is rejected with an authentication exception (proving enforcement,
    not merely that plaintext works). Docker-gated so it SKIPS cleanly when Docker is unavailable.

  • Credential-enforcement test coverage for MQTT security against a secured live broker. MQTT
    MqttSecurity credentials were only asserted at the options-carrier unit level
    (MqttSecurityOptionsTest), while the sole live Mosquitto integration test ran an
    allow_anonymous plaintext broker — so nothing proved credentials are actually applied and
    enforced on the wire. A new MqttTlsLiveBrokerIntegrationTest drives MockServer's MQTT publisher
    against a Testcontainers Mosquitto broker configured with a password_file and
    allow_anonymous false: it asserts that a publisher wired with the correct MqttSecurity
    username/password authenticates and delivers a message to an authenticated subscriber, and that a
    publisher with the wrong password (and one with no credentials) is rejected by the broker at
    CONNECT. Docker-gated so it skips cleanly when Docker is unavailable.

  • Live-broker test coverage for the AsyncAPI control-plane load() → publish-on-load path. The
    control-plane's broker-connecting path (createBrokerConnections / publishOnLoad) was untested
    against a real broker — AsyncApiControlPlaneImplTest loads without a reachable broker (asserting
    publishers=0), and the endpoint IT covers only the broker-less endpoints. A new Docker-gated
    AsyncApiControlPlaneLiveBrokerIntegrationTest (Testcontainers Kafka) drives load() with a real
    brokerConfig, publishOnLoad:true and consume:true, then proves the control-plane genuinely
    connected and published by consuming the on-load message with a plain third-party Kafka client and
    asserting status() reports publishers>0, a subscriber, and the recorded round-tripped message.

  • Over-the-wire test coverage for an LLM refusal preset served with a rate-limit quota. The LLM
    refusal presets, provider-specific rate-limit headers, and stateful request-count quota were only
    asserted at the body-builder / handler-unit level; nothing drove them through a running server. A
    new LlmRefusalQuotaRateLimitIntegrationTest serves an httpLlmResponse configured with an
    Anthropic refusal preset and a 2-request quota, then asserts on the raw socket response that the
    first two requests return a 200 refusal envelope (stop_reason:"refusal") carrying the
    anthropic-ratelimit-requests-* headers, and that the third (over-quota) request flips to a 429
    rate_limit_error envelope with the exhausted rate-limit headers and Retry-After.

Fixed
  • The testcontainers-mockserver (Python) port assertions no longer break against testcontainers
    4.15.0, which keys DockerContainer.ports by str(port) rather than int.
    with_exposed_ports
    now stores self.ports[str(port)] = None (4.15.0 types the attribute as
    dict[str, Optional[int]]), so the suite's assert 1080 in container.ports started failing with
    assert 1080 in {'1080': None} and took five tests — and the whole MockServer Python pipeline,
    and with it the umbrella MockServer build — red on master. The tests now read the exposed ports
    through a normaliser that parses each key to an int (tolerating a "1080/tcp" protocol suffix),
    so they assert the same thing under either key style. This also closes a latent false green: the
    assert MOCKSERVER_PORT not in container.ports check in test_replaces_default_port passed
    trivially once the keys became strings, and so would no longer have caught with_server_port
    failing to drop the previously exposed port. Only the tests changed — MockServerContainer itself
    was already correct, as get_exposed_port takes an int and normalises internally.
    <blobStoreKeyPrefix>/<file name> instead of under the writing machine's absolute local path, and a
    blobStoreKeyPrefix that does not end in a separator is now treated as a folder-style prefix instead
    of being glued straight onto the key (mockserver + x.json was mockserverx.json and is now
    mockserver/x.json). Anything persisted by an earlier version is stored under the OLD name and will
    NOT be restored after upgrading — the one-line migration is below.** The blob key was the ABSOLUTE
    local persistedExpectationsPath (for example /var/folders/.../persistedExpectations.json) and the
    configured blobStoreKeyPrefix was concatenated onto it with plain string addition. With the prefix
    shape the documentation recommends — blobStoreKeyPrefix="mockserver/", with a trailing separator —
    that composed mockserver//var/folders/.../persistedExpectations.json, and MinIO rejects the doubled
    // outright with HTTP 400, "Object name contains unsupported characters", so under that one
    configuration nothing was ever persisted and consequently nothing could be restored on restart. Under
    every other prefix shape the write SUCCEEDED and restore worked — a leading / is a legal byte in an
    S3 object name, and the read composed exactly the same name back — but the object was then named after
    the writing container's local filesystem layout, so a second instance that resolved
    persistedExpectationsPath to a different absolute path (started from a different working directory,
    or in a different container) looked under a different name and silently restored nothing. The key is
    now derived by the new shared org.mockserver.state.BlobKeys helper in mockserver-core: for every
    store other than FilesystemBlobStore the key is the FILE NAME of persistedExpectationsPath alone,
    and prefix and key are joined with exactly one separator, with any leading separator dropped and any
    repeated separators collapsed, so every prefix shape a user can configure — unset, mockserver,
    mockserver/, /mockserver/ — now produces the same valid object name
    (mockserver/persistedExpectations.json). That normalisation is applied for all
    put/get/list/delete operations wherever blobStoreKeyPrefix is applied, so it renames EVERY
    blob key, not only the persisted-expectations document. FilesystemBlobStore is unaffected: it
    interprets the key as a file path, so it keeps the absolute path and writes exactly the file it always
    did. Upgrading: because the old key always embedded the absolute local path and the new key is the
    bare file name, the object name changes for every non-filesystem persistence user, under every prefix
    shape and on every platform — there is no configuration in which the old name is preserved. On the
    first start after upgrading, the restore looks under the new name, misses (logged at INFO with the
    name it looked for), and the instance starts with no restored expectations; the next expectation change
    then writes a fresh object under the new name and leaves the old one behind. To carry existing state
    across the upgrade, copy or rename the object once before starting the new version, for example
    aws s3 mv s3://<bucket>/<prefix>/<old absolute path> s3://<bucket>/<prefix>/<file name> — otherwise
    accept the miss and let the first expectation change re-create it. One long-standing footgun goes away
    with the change: restoring after a restart no longer requires the two instances to resolve
    persistedExpectationsPath to the same absolute path, only to the same file name. Deployments that
    must NOT share state within one bucket should give each its own blobStoreKeyPrefix (or its own file
    name). Proven by a Docker-gated MinIO round trip that writes and then reads back an expectation with a
    trailing-slash blobStoreKeyPrefix (the exact configuration that returned HTTP 400 before), a MinIO
    put/get/list/delete round trip across all four prefix shapes, and Docker-free unit coverage of the key
    composition and of the key the persistence layer derives.
  • The configuration enforcement-evidence guard no longer certifies evidence it cannot see.
    ConfigurationEnforcementClassificationTest records, for every risky configuration property, the
    Class#method test that proves an instance-set value changes observable behaviour. It validated those
    pointers by loading the class — but it runs in mockserver-core, so any pointer naming a test in a
    sibling module was silently skipped on ClassNotFoundException. That exempted precisely the most
    valuable evidence, the end-to-end Layer C pointers: renaming, moving or deleting the referenced test
    left a dangling pointer and the guard still passed green, for maxRequestBodySize,
    maxResponseBodySize, wasmEnabled, redactSecretsInLog, clusterEnabled, dnsEnabled,
    grpcBidiStreamingEnabled, http3ConnectUdpEnabled and transparentProxyEnabled. A class that
    cannot be loaded is now resolved
    by locating its .java source under any module's src/test/java and asserting the file declares both
    the class and the referenced method, so cross-module pointers are checked in a full reactor build and
    when only some modules are built. The guard fails closed: a pointer resolvable by neither route is now
    a failure naming the dangling pointer, never a silent skip. Anti-vacuity assertions in the spirit of
    the sibling ConfigurationCallSiteGuardTest keep the scan honest — the set of pointers resolved by
    source scan must match the declared cross-module ratchet exactly, mockserver-netty and
    mockserver-state-infinispan must both have contributed, and classpath resolution must still cover
    the bulk of the pointers — so a scan that resolves nothing cannot pass. Verified by degrading a real
    mockserver-netty test method name: the guard now fails with a message naming the dangling pointer,
    where the previous version passed green with the identical defect in place.
  • gRPC trailing metadata is no longer silently dropped over HTTP/3. Every trailer an expectation
    authored — response().withTrailer("x-request-cost", "42"), and the gRPC chaos profile's
    customTrailers — reached an HTTP/1.1 or HTTP/2 client but never reached an HTTP/3 client at
    all
    , in every branch of the HTTP/3 gRPC response path (with a body and body-less, and both with
    and without proto descriptors loaded). The same expectation therefore produced different trailing
    metadata depending only on which transport the client happened to use, and there was no error or
    warning anywhere to indicate the loss — a test asserting on trailing metadata over HTTP/3 simply
    saw nothing. The cause was that Http3GrpcResponseWriter builds its HTTP/3 frames by hand rather
    than through MockServerHttpResponseToFullHttpResponse.mapResponseWithTrailers (which is what
    carries trailers on the other transports), and GrpcHttp3Adapter.buildTrailingHeadersFrame /
    buildTrailersOnlyFrame populated only grpc-status and grpc-message; the response's own
    trailers were never read. They are now emitted on the terminal frame: on the trailing HEADERS
    frame when the response has a body, and folded into the Trailers-Only frame when it does not,
    which is the shape gRPC defines for that form (HTTP-Status Content-Type Trailers, where
    Trailers includes custom metadata) and leaves the framing unchanged — there is still exactly one
    terminal frame, written with SHUTDOWN_OUTPUT, so an added trailer cannot cost the response its
    end-of-stream marker the way it did on HTTP/2 before the fix above. A user-authored trailer cannot
    override or spoof the transport's own status: the new shared
    GrpcResponseStatusResolver.passThroughTrailers (the trailer twin of passThroughHeaders)
    excludes grpc-status, grpc-message and grpc-status-name, mirroring the exclusion the HTTP/2
    path makes in remainingTrailers, and also excludes the connection-specific fields,
    content-length/content-type and pseudo-header names that RFC 9114 forbids in a trailer section.
    Trailer field names are lower-cased and CR/LF stripped from values before reaching the frame,
    because HTTP/3 field names must be lower-case and Netty's DefaultHttp3Headers rejects an
    upper-case one by throwing — so an expectation authoring withTrailer("X-Request-Cost", …) would
    otherwise have taken the client's entire response down rather than dropping a single field.
    Verified over the wire by two new tests in Http3GrpcIntegrationTest that drive a live in-JVM
    Netty QUIC client and read the metadata off the HEADERS frames it actually received, asserting
    which side each value arrived on (a trailer must not be folded into the initial headers, a
    response header must not be repeated as a trailer) and asserting the HEADERS-frame count so the
    metadata cannot arrive at the cost of correct framing, plus adapter-level coverage in
    GrpcHttp3AdapterTest. Positive control: neutering the trailer pass-through in production turns
    all seven new assertions red with the trailer absent.
  • A FILE response body is now served verbatim (and templated) on every response path, and a FILE
    request body is now actually matched (#​2450).
    A response whose body is a FILE (a filePath with
    no template engine) was previously read on the static response action only; the same FILE body
    returned from an object callback, a class callback, a response template, or a forward
    responseOverride reached the wire unread, emitting the file path string instead of the file
    contents. Materialisation now lives in a single shared FileBodyMaterialiser invoked from the two
    response-write funnels (writeResponseActionResponse, covering the static, object-callback,
    class-callback, response-template and SSE paths, and writeForwardActionResponse, covering the
    forward responseOverride), so all five producers — and the shared WAR/servlet path — serve the file
    contents. Templated FILE bodies (a FileBody carrying a Velocity/Mustache templateType) are rendered
    against the request on these paths too, not only verbatim ones; a text content type yields the decoded
    string and a binary or absent content type yields the raw bytes intact. A missing or unreadable file
    now produces a clean, logged 500 whose body does not leak the path, instead of a broken connection or
    the path string. Separately, a FILE body used for request matching had no case in
    BodyMatcherBuilder, so the body constraint was silently ignored (it matched any body); it now matches
    the request body against the exact file contents (string or binary). This resolves the earlier
    "static response only" caveat.
  • A forward responseOverride that replaces the body is no longer truncated to the upstream
    Content-Length.
    When a forwarded request's response is overridden with a new body,
    HttpResponse.update() replaced the body but kept the Content-Length inherited from the upstream
    response. A replacement body longer than the upstream body was therefore truncated on the wire (and a
    shorter one could over-run) — visible only to a real client, since every layer above the encoder held
    the full, correct response. update() now drops the inherited Content-Length whenever the override
    supplies a new body (or a generateFromSchema), unless the override itself sets an explicit
    Content-Length (which is still honoured verbatim), so the encoder recomputes the length from the
    actual body. This closes the residual on the forward-override path left by the FILE-body fix above.
  • A gRPC error response carrying custom trailing metadata no longer loses its status. On HTTP/2 a
    body-less gRPC response is collapsed into the gRPC Trailers-Only form, which moves grpc-status
    into the initial HEADERS frame and relies on that frame being end-of-stream. When the expectation
    also authored a custom trailer with withTrailer(...), that trailer kept a separate trailing
    HEADERS frame alive, so the initial frame was no longer end-of-stream: a real client read it as
    ordinary headers (where grpc-status is ignored) and then found no status at all in the terminal
    frame, failing the call with UNKNOWN: missing GRPC status. In other words, adding a single
    trailer to an error response destroyed the error — the caller lost both the status code and the
    message. GrpcToHttpResponseHandler.asTrailersOnlyIfHttp2 now skips the Trailers-Only collapse
    whenever any user-authored trailer remains, keeping grpc-status/grpc-message in the trailing
    HEADERS frame alongside the custom metadata, which is the correct shape in that case. The same fix
    covers the gRPC chaos fault path, which produced the byte-identical broken shape: a fault response
    configured with customTrailers emits them as real trailers alongside grpc-status/grpc-message
    on a body-less response, so a chaos-injected error over HTTP/2 also reached the client as
    UNKNOWN: missing GRPC status instead of the configured status. Found by the new real-client
    trailing-metadata coverage described under Added.
  • gRPC-Web now re-frames matched-expectation responses correctly over a real HTTP/1.1 socket, and
    is covered by an over-the-wire integration test.
    Every previous gRPC-Web test drove the handler
    through an EmbeddedChannel and set x-grpc-web-content-type directly on the response, so none
    exercised the actual mock-matching path: there the marker lives on the request only and was lost,
    and a matched expectation went back to a browser client as application/grpc with grpc-status in
    HTTP trailers a gRPC-Web client cannot read. The original request content-type is now retained in
    the per-stream GrpcPendingRequests record alongside the resolved service/method, so
    GrpcToHttpResponseHandler re-frames the response as gRPC-Web (length-prefixed message frame + a
    0x80 trailer frame carrying grpc-status in the body, base64-encoded for the -text variant).
    A new GrpcWebOverTheWireIntegrationTest posts a real application/grpc-web and
    application/grpc-web-text framed request to a running server over a raw socket and asserts on the
    exact bytes a browser client would receive.
  • The AsyncAPI control-plane HTTP endpoints now have an over-the-wire integration test.
    PUT /mockserver/asyncapi, GET /mockserver/asyncapi and PUT /mockserver/asyncapi/verify were
    only exercised at the orchestrator/control-plane level, so a regression in the Netty →
    HttpStateAsyncApiControlPlaneRegistry routing or response wiring would not have been caught.
    A new AsyncApiControlPlaneIntegrationTest boots a real MockServer and drives all three endpoints
    over a raw socket without any live broker: it asserts the load response (201, loaded:true,
    channel count, zero publishers/subscribers), the status response (200, channels, counts, and the
    empty/unloaded case), and the broker-less verify verdict (406 with the "at least 1 … found 0"
    failure detail) plus the blank-body 400.
  • A StreamingBody response delivered to a real HTTP/2 inbound client is now covered end-to-end.
    NettyResponseWriter.writeStreamingResponse re-stamps the request's HTTP/2 stream id onto the
    streaming response head (the field is not part of the copied header multimap) and flushes each chunk
    as it arrives, but no test drove that path with a real HTTP/2 client — Http2SseStreamingIntegrationTest
    covered only the SSE sibling and explicitly noted the StreamingBody case was untested, while the
    existing streaming-relay tests drive an HTTP/1.1 inbound socket. A new Http2StreamingBodyIntegrationTest
    drives a real prior-knowledge h2c multiplex client through a streamingResponsesEnabled forward
    MockServer to an SSE upstream and asserts on the frames the client receives on its OWN request stream:
    both events arrive (proving the stream-id stamp — the #​2419 class), and the early event's DATA frame
    arrives promptly as one of at least two separate, in-order DATA frames rather than being buffered into
    one. Degrading the write path to buffer chunks until stream completion was verified to make the
    incremental-delivery assertion go red.
  • PROXY-protocol destination resolution is now proven to drive transparent-proxy forwarding over a real
    socket.
    ProxyProtocolOriginalDestinationHandler was only exercised via EmbeddedChannel, which asserts
    the handler sets the REMOTE_SOCKET channel attribute but never that this attribute actually chooses the
    forward target end-to-end. A new non-privileged loopback ProxyProtocolForwardingIntegrationTest runs
    MockServer with transparentProxyEnabled=true and no fixed remote, opens a raw socket, writes a valid
    PROXY v1 TCP4 header naming a loopback EchoServer as the destination followed by a plain GET whose
    Host header points at an unrelated (closed) decoy port, and asserts the EchoServer reflects the request
    back — proving the PROXY-protocol REMOTE_SOCKET, and not the Host header, drives forwarding. The test
    needs no NET_ADMIN/privileged capability because the PROXY-protocol header is an application-level byte
    prefix. Verified as a genuine regression guard by a positive control: ignoring the PROXY-header
    destination turns the forwarding assertions RED, restoring it returns them GREEN.
  • A FILE response body with no template engine now serves the file contents, not the file path (#​2450).
    A static response with a body of type FILE and a `filePat

Note

PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

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

🚦 Automerge: Enabled.

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 this update 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 the dependencies Pull requests that update a dependency file label Jul 29, 2026
@renovate
renovate Bot enabled auto-merge (squash) July 29, 2026 22:39
@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

Benchmark run finished with conclusion skipped for 20294a17d8891cc537db4e2ef6d2f3e9e74c8fd9.

Benchmark summary artifact was not found; see the workflow run for details.

@renovate
renovate Bot merged commit ee64917 into main Jul 29, 2026
25 checks passed
@renovate
renovate Bot deleted the renovate/org.mock-server-mockserver-netty-no-dependencies-7.x branch July 29, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant