Security

July 1, 2026 · View on GitHub

MockServer's security scanning, vulnerability management, and the security posture of released and pre-release artifacts.

Overview

MockServer is a development and testing tool -- it is not designed for production deployment. Its security posture reflects this: the project invests heavily in automated scanning and dependency management to keep the supply chain clean, while deliberately retaining certain capabilities (like SSRF forwarding and trust-all TLS) that are essential for testing but would be vulnerabilities in a production service.

Users who need to lock down a MockServer deployment can harden these capabilities through configuration. The consumer-facing API Security: Configuration Hardening guide (source) documents the recommended property values -- loopback binding, SSRF blocking, upstream TLS validation, the TLS protocol floor, response-template class restrictions, request-parsing limits, and control-plane authentication (mTLS/JWT).

See SECURITY.md for the full security policy, including intentional security behaviours and vulnerability reporting.

Control-plane audit logging

When MockServer runs as shared infrastructure, an opt-in audit log records who changed mock state (control-plane mutations: who/what/when/where/outcome) so changes are accountable. It is off by default and is not data-plane traffic logging. Enable it with controlPlaneAuditEnabled and retrieve it via GET /mockserver/audit. See event-system.md and configuration-reference.md.

Security-relevant properties of the audit log:

  • Records no bodies and no headers. Each entry stores only redacted, structural metadata (method, control-plane path with the query string dropped, logical operation, source address, best-effort principal, outcome). It can never become a sink for request payloads or credential headers.
  • Redaction (by omission). The path's query string is stripped, and no header or body is ever stored, so there is no credential-bearing free text to scrub; the summary field is unused (null) in v1. The best-effort principal parser reads only the JWT sub claim (or the mTLS subject CN) and never stores the raw bearer token. (If a non-null summary is added later it must be scrubbed via FixtureRedactor's default sensitive set + ***REDACTED*** at that point.)
  • Default-off and fail-soft. When disabled, control-plane operations behave byte-for-byte identically. The audit emit is wrapped in try/catch and can never throw into the request path.
  • Best-effort, UNVERIFIED principal (v1). The principal is read from an unverified JWT sub (no signature verification) or the mTLS client-certificate CN, else anonymous. Verified identity is a later unit (1.5-A) — do not treat the v1 principal as an authenticated subject; treat it as a hint correlated with the (separately enforced) control-plane authentication.

Static Analysis: CodeQL

GitHub's CodeQL semantic analysis runs automatically on:

  • Every push to master
  • Every pull request targeting master
  • Weekly (Tuesdays at 22:00 UTC)

CodeQL scans four languages in the monorepo:

LanguageScope
Javamockserver/ (server, core, clients, integrations)
JavaScriptmockserver-ui/, mockserver-client-node/, mockserver-node/
Pythonmockserver-client-python/
Rubymockserver-client-ruby/

Results appear in the GitHub Security tab. CodeQL detects issues including SQL injection, path traversal, insecure deserialization, cross-site scripting, and other OWASP Top 10 categories.

Workflow: .github/workflows/codeql-analysis.yml

Dependency Scanning: Dependabot

Dependabot monitors 8 package ecosystems across the monorepo for outdated and vulnerable dependencies:

EcosystemDirectory(ies)PR Limit
Maven/mockserver20
Maven/mockserver/mockserver-maven-plugin10
npm/mockserver-ui, /mockserver-client-node, /mockserver-node, /.opencode10
pip/mockserver-client-python10
Bundler/mockserver-client-ruby, /jekyll-www.mock-server.com10
GitHub Actions/10
Docker/docker + subdirs, /docker_build/*10
Terraform/terraform/*10

The Docker and Terraform directory columns are summarised; Dependabot has no glob support, so each directory is listed explicitly in .github/dependabot.yml (10 Docker dirs, 4 Terraform dirs). When you add a new Docker/Terraform directory, add it there too or it will not be scanned.

Dependabot runs daily and opens pull requests for version updates and security patches. Minor and patch updates are grouped per ecosystem (e.g. maven-minor-and-patch) so related bumps land in a single PR instead of many.

Namespace Migration Status

The javaxjakarta namespace migration is complete (Spring 7, Spring Boot 4, Tomcat 11, Jetty 12, Jersey 4, jakarta.* artifacts at EE 10+). The Dependabot ignore list no longer carries jakarta-related blocks. JDK-namespace javax.* (e.g. javax.net.ssl, javax.xml.*, javax.script.*, javax.annotation.Nullable JSR-305) remains unchanged — those classes ship with the JDK and stay javax.

See the Java compatibility policy in AGENTS.md.

Version Ceilings (Java 17 Floor)

MockServer targets Java 17 as the minimum supported runtime. Some dependencies drop Java 17 support in newer major lines, so they are pinned below the version that requires a newer JDK. These ceilings are enforced in .github/dependabot.yml with explicit versions: [">=X.0.0"] ignore entries (stricter than ignoring only version-update:semver-major, and applied in every Maven block that references the dependency).

DependencyCeilingReason
com.puppycrawl.tools:checkstyle< 13.0.0 (stay on 12.x)checkstyle 13.x is compiled for Java 21 (class file version 65.0) and fails to load under Java 17 — see the CodeQL Analyze (java) build, which runs on Java 17
org.infinispan:infinispan-core< 15.0.0 (stay on 14.0.x)Infinispan 15.x requires Java 21+. The 14.0.x line is the last to support Java 17. Used only by mockserver-state-infinispan.
com.graphql-java:graphql-java< 25.0.0 (precautionary)graphql-java 22.x–24.x ship Java 11 bytecode and run fine on Java 17 (verified by mockserver-core build/tests on the Java 17 target). The ceiling is precautionary: a future major (25.x) could raise the runtime floor, so it must be manually verified on Java 17 before adoption. Pulls in com.graphql-java:java-dataloader (3.3.x); ANTLR4 runtime and reactive-streams arrive transitively. Used by org.mockserver.graphql.* for schema-driven GraphQL response synthesis.

When raising the Java floor: remove the corresponding ceiling here and the matching ignore entries in .github/dependabot.yml, then let the dependency upgrade. The Dependabot ignore does not block manual version bumps in pom.xml — keep this table in mind when hand-editing dependency versions.

Native / Platform Dependencies

Dependencies that interact with the OS kernel or native libraries, added for specific platform features. These are safe on all platforms — callers detect availability at runtime and fall through when unavailable.

DependencyVersionModulePurposeJava 17 compatible
net.java.dev.jna:jna${jna.version} (5.19.0)mockserver-nettyJNA-based getsockopt(SO_ORIGINAL_DST) for transparent proxy original-destination resolution (SoOriginalDstResolver). O(1) socket option read, tried before the O(n) conntrack table scan.Yes (supports Java 8+)
io.netty:netty-transport-classes-epoll${netty.version}mockserver-core, mockserver-nettyPure-Java API classes for EpollSocketChannel, EpollEventLoopGroup, EpollServerSocketChannel, and Epoll.isAvailable() — needed at compile time by NettyTransport (transport selection) and SoOriginalDstResolver (fd extraction). No native classifier (the .so is only needed at runtime on Linux).Yes (follows Netty BOM)
io.netty:netty-transport-native-epoll (classifier: linux-x86_64)${netty.version}mockserver-netty (runtime)Native JNI library that activates Epoll.isAvailable() on Linux x86_64. Bundled in the distribution jar-with-dependencies and Docker images. Inert on non-Linux platforms.Yes (follows Netty BOM)
io.netty:netty-transport-native-epoll (classifier: linux-aarch_64)${netty.version}mockserver-netty (runtime)Native JNI library that activates Epoll.isAvailable() on Linux aarch64 (ARM64). Bundled in the distribution jar-with-dependencies and Docker images. Inert on non-Linux/non-ARM platforms.Yes (follows Netty BOM)
io.netty:netty-codec-http3${netty.version}mockserver-netty (compile)HTTP/3 codec for experimental QUIC support (graduated from the Netty incubator into mainline Netty 4.2). Transitively pulls netty-codec-native-quic with native classifiers for linux-x86_64, linux-aarch_64, osx-x86_64, osx-aarch_64, windows-x86_64. The native artifact contains a BoringSSL JNI binding. Fail-soft at runtime: if the native cannot be loaded, Quic.isAvailable() returns false and the HTTP/3 server is not started.Yes (follows Netty BOM)

Embedded Data Grid Dependencies (Optional Module)

Dependencies introduced by mockserver-state-infinispan, which provides the Infinispan-backed StateBackend for clustered MockServer state. This module is optional -- it is not pulled into mockserver-core or any other module. Its transitive dependencies enter CodeQL/Dependabot scan scope only when the module is included in the reactor build.

DependencyVersionModulePurposeJava 17 compatible
org.infinispan:infinispan-core14.0.35.Finalmockserver-state-infinispanEmbedded (non-server) Infinispan cache manager for LOCAL and clustered KV stores. Provides the StateBackend implementation when stateBackend=infinispan is configured.Yes (14.0.x line targets Java 11+; 15.x raises to Java 21)
org.jgroups:jgroups(transitive of infinispan-core)mockserver-state-infinispanCluster transport for Infinispan. In LOCAL mode (default, clusterEnabled=false), no JGroups transport is started. In clustered mode (clusterEnabled=true), JGroups provides the SHARED_LOOPBACK in-JVM transport (for testing) or TCP transport for multi-host clustering. The default built-in JGroups stack (jgroups-loopback.xml) uses SHARED_LOOPBACK, which does not open network sockets; custom multi-host stacks use TCP bound to loopback by default.Yes
org.infinispan.protostream:protostream(transitive of infinispan-core)mockserver-state-infinispanProtocol Buffers serialization framework used internally by Infinispan. Not used directly by MockServer's clustered wire format (which uses JavaSerializationMarshaller with an explicit allow-list).Yes

JGroups network security note: In LOCAL mode (default, clusterEnabled=false), JGroups does not open any network listeners. In clustered mode (clusterEnabled=true), the default built-in JGroups stack uses SHARED_LOOPBACK transport (in-process, no network I/O), suitable for embedded testing. For multi-host clustering, users must provide a custom JGroups stack via the clusterTransportConfig property pointing to a JGroups XML file with a real transport (TCP/UDP) and appropriate discovery protocol (TCPPING, DNS_PING, etc.). The TCP transport should be configured with explicit bind addresses and firewall rules appropriate to the deployment environment.

Infinispan serialization allow-list (P0 security gate -- RESOLVED in Phase 2c): The Phase 2b LOCAL-mode backend used global.serialization().allowList().addRegexp(".*") -- a wildcard that permits deserialization of any class. This was safe in LOCAL mode because caches are heap-only with no network marshalling. Phase 2c resolves this P0 gate by configuring the clustered path with:

  1. JavaSerializationMarshaller as the explicit marshaller (instead of ProtoStream, to handle the generic VersionedWrapper<V> types without per-type proto schema definitions)
  2. An explicit package allow-list restricted to exactly the types that cross the wire:
    • org.mockserver.state.infinispan.* (VersionedWrapper)
    • org.mockserver.state.* (ExpectationEntry, Blob)
    • org.mockserver.mock.*, org.mockserver.model.*, org.mockserver.matchers.* (domain model)
    • com.fasterxml.jackson.* (ObjectNode for CRUD entities)
    • java.lang.*, java.util.*, java.time.*, [B (JDK types, byte arrays)

The ExpectationEntry uses custom writeObject/readObject to serialize the Expectation as its JSON string (via ExpectationDTO), avoiding the need for the entire domain model to implement Serializable. The LOCAL-mode path retains the ".*" wildcard because heap-only storage never deserializes untrusted bytes.

Cloud Blob Store Dependencies (Optional Modules)

Dependencies introduced by the cloud blob store modules, which provide durable BlobStore implementations for S3, GCS, and Azure Blob Storage. Each module is optional -- it is not pulled into mockserver-core or any other module. mockserver-core has zero compile-time or runtime dependencies on any cloud SDK; the cloud modules self-register via reflection when present on the classpath. Their transitive dependencies enter CodeQL/Dependabot scan scope only when the module is included in the reactor build.

DependencyVersionModulePurposeJava 17 compatible
software.amazon.awssdk:s32.31.9mockserver-blob-s3AWS SDK v2 S3 client for the S3-backed BlobStore. Supports any S3-compatible store (MinIO, LocalStack) via endpoint override.Yes (targets Java 8+)
com.google.cloud:google-cloud-storage2.49.0mockserver-blob-gcsGoogle Cloud Storage client for the GCS-backed BlobStore. Supports fake-gcs-server for testing.Yes (targets Java 8+)
com.azure:azure-storage-blob12.29.1mockserver-blob-azureAzure Blob Storage client for the Azure-backed BlobStore. Supports Azurite emulator for testing.Yes (targets Java 8+)

Dependency isolation: Each cloud SDK lives exclusively in its own module. The mockserver-core dependency tree contains no AWS, Google Cloud, or Azure artifacts. This is enforced by the module structure: core depends only on its own SPI interfaces (BlobStore, BlobStoreFactory), and cloud modules register their implementations via StateBackendFactory.registerBlobStoreFactory() at startup, discovered by reflection when blobStoreType is configured.

Test Dependencies (Docker-Gated)

Test-scoped dependencies used for Docker-gated integration tests. These are never bundled in released artifacts.

DependencyVersionModulePurpose
org.testcontainers:testcontainers1.21.4mockserver-async, mockserver-blob-s3, mockserver-blob-gcs, mockserver-blob-azure (test)Core Testcontainers API for Docker-gated integration tests
org.testcontainers:kafka1.21.4mockserver-async (test)Kafka container module for live-broker integration tests

The Testcontainers version (1.21.4) is aligned with the existing mockserver-testcontainers module. Note that mockserver-testcontainers depends on org.testcontainers:testcontainers (and its transitive docker-java-* 3.4.2 artifacts) at compile scope — not test scope — because its public MockServerContainer extends Testcontainers' GenericContainer; consumers of mockserver-testcontainers therefore resolve Testcontainers 1.21.4 transitively (overridable via their own dependency management), and these artifacts are in CodeQL/Dependabot scan scope for that module. The 1.20.6 to 1.21.4 bump was required to fix DockerClientFactory.isDockerAvailable() returning false on Docker Desktop 4.67+ / Engine 29.x / API 1.54 — the bundled docker-java 3.4.1 in 1.20.6 got a 400 on the info endpoint; 1.21.4 bundles docker-java 3.4.2 and includes explicit fixes for recent Docker Engine API changes. MQTT integration tests use a GenericContainer with eclipse-mosquitto:2.0 (no additional Testcontainers module needed). Transparent-proxy end-to-end tests (SoOriginalDstEndToEndIT, TproxyEndToEndIT) use the Docker CLI directly (via ProcessBuilder) to build and run privileged containers with NET_ADMIN for iptables REDIRECT/TPROXY rule setup — they do not use Testcontainers.

Maven Dependency Graph Submission

GitHub's built-in dependency graph automatically indexes all manifest files (pom.xml, package.json, Gemfile, requirements.txt) and their transitive dependencies. This enables Dependabot vulnerability alerts for the full dependency tree -- currently tracking 2000+ packages including 347 Maven dependencies.

Vulnerability Scanning: Snyk

Snyk provides a second layer of vulnerability scanning, independent of Dependabot:

  • PR status checks: Two Snyk integrations (security/snyk (mockserver) and security/snyk (jamesdbloom)) run on every pull request
  • Dashboard: app.snyk.io/org/mockserver/projects
  • Policy file: .snyk documents any vulnerability IDs that are explicitly ignored, along with the rationale and a review date

The .snyk policy file excludes mockserver-examples (sample code, not shipped). As of the Java 17 / Jakarta EE 10 modernisation the ignore list is empty — the Java-11-era ignores (which suppressed ~20 Spring/Jetty/Boot/OkHttp/Reactor CVEs whose only fix required Java 17+) were removed once those vulnerable versions left the dependency tree. Vulnerabilities are now resolved through normal upgrades; add a new, dated ignore only when a deliberate constraint genuinely blocks a fix.

Renewing Snyk ignores

When an ignore is added, give it a dated expires: (convention: 3 months out) so it cannot silently outlive its rationale. Renewal is a manual checkpoint: before the expiry date, re-run the Snyk scan, confirm the constraint still holds, and either remove the ignore (if the fix is now available) or refresh the expires: date with an updated reason. An empty ignore list (the current state) needs no renewal.

See Snyk Security for the full triage workflow, CLI commands, and vulnerability status by module.

CI/CD Infrastructure Security

Controls applied to the Buildkite build infrastructure and release pipeline. See AWS Infrastructure and CI/CD for full details.

Least-Privilege CI Secrets

Each agent queue receives only the Secrets Manager policies it actually consumes — the single buildkite-read-build-secrets policy has been replaced by per-secret, per-queue policies:

QueueIAM policies attachedRationale
defaultread-build-secrets-default (API token + Sonatype), read-dockerhub-secret, ecr-public-pushSnapshot Docker push on master
triggerread-buildkite-api-tokenTrigger polling only needs API token
perfread-buildkite-api-token, perf-resultsCommit guard + S3 results
releaseread-build-secrets-release, read-release-secrets, read-dockerhub-secret, ecr-public-push, release-website-tfstateFull release publishing

The dependency-cache policy is detached from all queues (runtime wiring reverted pending cache-integrity implementation).

Release Secret Hygiene (File-based, not env vars)

Release scripts write secrets to 0600 files under .tmp/ (volume-mounted into Docker containers) rather than passing them as docker run -e flags. This prevents secrets appearing in /proc/1/environ or docker inspect output on the agent host.

Released Image Signing

All release Docker images (Docker Hub + ECR) are cosign-signed by digest after push, using the same key stored in mockserver-release/cosign-key. This allows consumers to verify image provenance. The release Docker step runs on the release queue (the only queue granted read_release_secrets, which includes the cosign key) and auto-installs a checksum-pinned cosign binary; signing is non-fatal if the key is unavailable. See Docker for the verification command.

CloudTrail Audit Events

The CloudTrail trail (mockserver-management-trail) uses advanced event selectors to capture:

  • All management events (which include every Secrets Manager API call — GetSecretValue on mockserver-build/ and mockserver-release/ secrets is logged here)
  • All S3 object-level data events on the Terraform state bucket (mockserver-terraform-state/)

Secrets Manager is not a supported CloudTrail data-event resource type, so secret access is audited via management events rather than a dedicated data-event selector (an AWS::SecretsManager::Secret data selector is rejected by the CloudTrail API with InvalidEventSelectorsException).

GuardDuty Alerting

GuardDuty findings with severity ≥ 7 (HIGH and CRITICAL) trigger an EventBridge rule that forwards to the existing buildkite-mockserver-alerts SNS topic. The SNS topic is encrypted with alias/aws/sns.

KMS Encryption at Rest

ResourceCMK
Terraform state bucketalias/mockserver-terraform-state (bootstrap CMK, rotation enabled)
AWS Config delivery bucketCloudTrail CMK (alias/mockserver-cloudtrail, shared)
SNS alerts topicalias/aws/sns (AWS-managed)

VPC Flow Logs

VPC flow logs are set to traffic_type = ALL on all four VPCs (default, trigger, release, perf queues), capturing both accepted and rejected traffic. Previously only REJECT traffic was logged.

Tfstate Lock Scoping

The buildkite-release-website-tfstate IAM policy grants s3:DeleteObject only on website/terraform.tfstate.tflock (the S3-native lock file), not on the state file itself. GetObject/PutObject on the state file are separate statements. This prevents accidental or malicious deletion of the live state.

AI Security Review

In addition to automated scanning, every code change receives a security-focused review as part of the AI-assisted development process:

Dedicated Security Auditor Agent

A specialist security-auditor AI agent performs targeted security reviews with a checklist covering:

  • Secrets & credentials -- hardcoded tokens, API keys, connection strings, leaked PEM/JKS content, .env files
  • Input validation -- untrusted data paths, missing bounds checks, charset assumptions
  • Injection prevention -- command injection, LDAP injection, XSS, XXE, SSRF
  • Network security -- TLS defaults, certificate validation, cipher suite selection, hostname verification
  • Java-specific -- unsafe deserialization, Runtime.exec() usage, weak random, information leakage
  • Netty-specific -- malformed request handling, ByteBuf release, pipeline state, WebSocket validation
  • Dependencies -- known CVEs, version pinning, transitive dependency risk

Security Lens in Every Code Review

The Review Constitution (applied to every commit, not just security-flagged ones) includes an Insecurity lens based on STRIDE threat modelling with 13 security principles, including MockServer-specific rules:

  • TLS certificate validation must be explicit
  • Control plane must be protectable
  • Template injection must be prevented
  • CORS headers must not weaken security

GitHub Security Features

The repository uses several GitHub security features:

FeatureStatusPurpose
Code scanning (CodeQL)ActiveStatic analysis for vulnerabilities
Dependabot alertsActiveVulnerable dependency detection
Dependabot security updatesActiveAutomatic PRs for security fixes
Dependency graphActiveTransitive dependency visibility
Security advisoriesActivePrivate vulnerability reporting
Secret scanningActive (GitHub default)Prevents accidental secret commits

SNAPSHOT and Pre-Release Versions

What are SNAPSHOT versions?

In the Maven ecosystem, a -SNAPSHOT suffix (e.g., 5.16.0-SNAPSHOT) indicates the in-development version of the next release. SNAPSHOT artifacts are published to the Sonatype snapshots repository and represent the latest state of the master branch.

Security status of pre-release artifacts

SNAPSHOT and pre-release versions may contain unresolved security advisories. This applies to:

ArtifactPre-Release IdentifierRegistry
Java JARs-SNAPSHOT suffix (e.g., 5.16.0-SNAPSHOT)Maven Central snapshots
Docker imageslatest and SNAPSHOT tagsDocker Hub
Node.js packagesPublished only at release timenpm
Python packagePublished only at release timePyPI
Ruby gemPublished only at release timeRubyGems

At formal release time, all known security issues are resolved to the extent technically possible. This means:

  1. All Dependabot and Snyk alerts with available patches are addressed
  2. Dependencies are updated to their latest compatible versions
  3. Any new CodeQL findings are reviewed and resolved
  4. The Snyk policy file's ignore expiry dates are reviewed and renewed only when a deliberate constraint still prevents a fix

Between releases, the master branch and SNAPSHOT artifacts may temporarily carry unresolved advisories -- for example, when a new CVE is published against a dependency but the fix has not yet been integrated.

Base Image OS CVEs (Expected Baseline)

Container scanners (Trivy, Grype, the ArtifactHub Helm security report) will always list a residual set of CVEs against the Debian OS packages baked into the gcr.io/distroless/java17 base image (libc6, libexpat1, zlib1g, libuuid1, libpng16, liblcms2-2, libbz2-1.0). These belong to the JRE base layer, not to MockServer code or its Maven dependencies, and are expected — most carry Fixed in: - (no upstream Debian patch yet), so they cannot be remediated at build time regardless of the JRE/Java version. The pinned base-image digests are kept current automatically by Dependabot's docker ecosystem, so fixes are adopted as soon as distroless rebuilds. See Docker → Base Image CVE Baseline for the full triage guide.

Recommendations for Consumers

  • For maximum security: Pin to a specific release version (e.g., 7.3.0), not latest or SNAPSHOT
  • For Renovate/Dependabot users: Configure version constraints to only match release versions, not SNAPSHOTs
  • For Docker users: Use versioned tags (e.g., mockserver/mockserver:7.3.0) rather than latest
  • Subscribe to releases: Watch the GitHub releases page for new versions with resolved security issues

Vulnerability Reporting

To report a security vulnerability in MockServer, use:

Do not open public issues for security vulnerabilities. See SECURITY.md for full reporting guidelines.