Hazelcast published two advisories on 8 October 2026. CVE-2026-107726 is rated critical: a client can read arbitrary memory from any cluster member, crash members, and in some Enterprise configurations corrupt memory. CVE-2026-107725 is rated high: missing authorization checks in the IMap Predicates API that the advisory says may allow arbitrary code execution on a member. Both were fixed months earlier, in Enterprise 5.6.1 on 25 March and in 5.7.0 on 13 May.
This post is for teams that run Hazelcast clusters or embed Hazelcast in Java services. It covers what each fix changed in the code, who counts as a "low-privileged client", what upgrading changes for your permission config, and why your dependency scanner may give you the wrong answer in both directions. It ends with a script that checks your build files and member configs.
The short answer
Upgrade to Hazelcast 5.7.0 (Community) or to Enterprise 5.7.0, 5.6.1, 5.5.10 or 5.4.5. Community Edition gets no patch releases, so 5.7.0 is its only fix. After upgrading, two things need configuration: the zero-config Compact serialization filter is opt-in, and fine-grained client permissions now need explicit aggregate and projection actions or those operations fail. Don't rely on a scanner to find this: the advisories name only the com.hazelcast:hazelcast artifact, so builds that use hazelcast-enterprise show nothing, and OSV's copy of the data flags the fixed Enterprise-line versions 5.4.5, 5.5.10 and 5.6.1 as still vulnerable.
The two bugs
| CVE-2026-107726 | CVE-2026-107725 | |
|---|---|---|
| Advisory | GHSA-6v25-8wq6-xq4j | GHSA-w294-6q5q-53p8 |
| Severity | Critical, CVSS 4.0 9.3 (no privileges required) | High, CVSS 4.0 8.7 (low privileges) |
| Impact per advisory | Read member memory (heap, off-heap, JVM address space), crash members, possible code execution in some Enterprise configurations | Arbitrary code execution on a member |
| Fix commit | 361979d, "Reflective compact serialization filtering" | 5d68f48, "Add missing IMap aggregate and projection permissions" |
| Workaround | Restrict cluster access to trusted, hardened clients | None |
Both advisories credit k-jamroz as the finder.
CVE-2026-107726: zero-config Compact serialization
Compact is Hazelcast's binary format. Its zero-config mode needs no registration: when Hazelcast meets a class with no other serializer, it extracts a schema from the class by reflection and uses that, reading and writing fields "regardless of whether those fields are public", in the docs' words. Before the fix, nothing limited which classes qualified.
The fix adds a class filter in front of reflective Compact serialization. By default it blocks JDK classes (java., javax., com.sun., sun., jdk.) and a built-in list of Hazelcast internal classes. The fix's own tests use two of those, HeapData and Packet, which wrap raw bytes. You can add an allowlist and a blocklist in the same format as Hazelcast's Java serialization filter.
The important detail is what happens without configuration. The new ReflectiveClassFilter treats a missing allowlist as "every class not blocked is eligible". Hazelcast's documentation says the built-in blocklist "does not cover your own classes" and recommends an allowlist of only the classes your application needs. The 5.7.0 release notes say the same filter will get "stricter defaults in a future major release". The setter is marked @Beta.
CVE-2026-107725: a permission that matched everything
This one is easier to see in the code. In Hazelcast 5.4.0 through 5.6.0, the client tasks for IMap.aggregate() and IMap.project() asked for a map permission with the action aggregate or projection:
// DefaultMapAggregateMessageTask, 5.6.0
public Permission getRequiredPermission() {
return new MapPermission(getDistributedObjectName(), ActionConstants.ACTION_AGGREGATE);
}
But MapPermission didn't know those action names. Its initMask method turned each known action into a bit and silently ignored the rest, so the required permission had a mask of zero. The check that compares a granted permission with a required one is a bitmask test:
// InstancePermission.implies, 5.6.0
boolean maskTest = ((this.mask & that.mask) == that.mask);
With a required mask of zero, that test always passes. Any map permission whose name pattern matched the map, even one granting only listen or put, was enough to run aggregations and projections. Aggregators and projections are objects the client sends; the member deserializes them and runs them. The fix adds nine lines: it adds AGGREGATE and PROJECTION bits to MapPermission and includes them in ALL.
In 5.3.8 these tasks fell back to the generic query check, which requires read, so the zero-mask gap appears from 5.4.0. The advisory's affected range still covers 5.3.x and older.
Who is the "low-privileged client"?
The advisories describe a malicious client with low privileges. How low depends on your edition and config:
- Community Edition, or Enterprise with security disabled. There is no client authentication or authorization. Hazelcast lists authentication, role-based access control and TLS as Enterprise Edition features. Any process that can open a connection to a member's client port is a fully trusted client. CVE-2026-107725 adds nothing in that case, because a client could already do anything, but CVE-2026-107726 lets that client read member memory and crash members.
- Enterprise with security enabled. A client needs credentials. On 5.4 to 5.6, any principal with any permission on a map could run aggregations and projections on it. CVE-2026-107726 is scored as needing no privileges at all.
So the network is the real boundary for most clusters. Hazelcast's hardening guide says members and clients shouldn't sit on internet-facing or untrusted networks, and that client-to-member traffic is plain text unless you enable TLS.
Fixed in March and May, disclosed in October
| Date | Event |
|---|---|
| 16 March 2026 | Permission fix committed (5d68f48) |
| 23 March 2026 | Compact filter committed (361979d) |
| 25 March 2026 | Enterprise 5.6.1 released |
| 13 May 2026 | 5.7.0 released, Community and Enterprise |
| 8 October 2026 | Both advisories and CVEs published |
That's 197 days from the first fixed Enterprise release to the advisory, and 148 days from the Community fix. The 5.7.0 release notes did list both changes under Security, in plain terms: new IMap projection and aggregation permissions, and configurable class restrictions "mitigating risks from unsafe deserialization". Teams that read release notes had the information in May. Teams that wait for a scanner alert got it in October, if their scanner reports it correctly at all.
What your scanner will tell you
We queried the OSV API, which many open-source scanners use, for each artifact and version on 18 October 2026:
| Artifact and version | Published in | Vendor status | OSV returns | Result |
|---|---|---|---|---|
hazelcast 5.6.0 | Maven Central (Community) | Affected | both advisories | Correct |
hazelcast 5.7.0 | Maven Central (Community) | Fixed | nothing | Correct |
hazelcast 5.4.5 | Hazelcast repository | Fixed | both advisories | False positive |
hazelcast 5.5.10 | Hazelcast repository | Fixed | both advisories | False positive |
hazelcast 5.6.1 | Hazelcast repository | Fixed | both advisories | False positive |
hazelcast 5.6.2 | Hazelcast repository | Fixed | both advisories | False positive |
hazelcast-enterprise 5.5.9 | Hazelcast repository | Affected | nothing | Missed |
hazelcast-enterprise 5.6.0 | Hazelcast repository | Affected | nothing | Missed |
hazelcast-enterprise 5.7.2 | Hazelcast repository | Fixed | nothing | Correct |
Two things go wrong.
The Enterprise artifact isn't named. GitHub's advisory database lists only com.hazelcast:hazelcast. Enterprise Edition is published as com.hazelcast:hazelcast-enterprise in Hazelcast's own repository, and its POM (we checked 5.5.10) declares no compile or runtime dependency on the core artifact. So an unpatched Enterprise 5.5.9 build produces no finding. Three older Hazelcast advisories in the same database (for CVE-2022-36437, CVE-2023-33265 and CVE-2023-45860) do list hazelcast-enterprise; this pair doesn't.
The fixed Enterprise lines look vulnerable. The advisories give per-line ranges ("below 5.4.5", "5.5.0 up to 5.5.10", "5.6.0"), but every range lists 5.7.0 as its first patched version. OSV's copy uses that as the fix event, so for OSV each range runs to 5.7.0. Hazelcast's repository publishes the core artifact at 5.4.5, 5.5.10, 5.6.1 and 5.6.2, and OSV reports both advisories for all four. GitHub's own API still shows the narrower ranges, so two tools reading the same advisory can disagree.
Neither problem is about the bugs themselves. They come from how the advisory was mapped to packages.
Check your builds and configs
This script reads pom.xml, gradle.lockfile and mvn dependency:list output for Hazelcast artifacts, and hazelcast.xml or hazelcast.yaml member configs for security, the zero-config filter and map permissions that will change behaviour after the upgrade. It uses only the Python standard library:
#!/usr/bin/env python3
"""Check Hazelcast dependencies and member configs for CVE-2026-107726 and CVE-2026-107725.
Usage: python3 hz_check.py FILE [FILE ...]
Accepts pom.xml, gradle.lockfile, `mvn dependency:list` output, and
hazelcast.xml / hazelcast.yaml member configs. Standard library only.
"""
import re
import sys
import xml.etree.ElementTree as ET
# Fixed versions per release line, from the vendor advisories (GHSA-6v25-8wq6-xq4j, GHSA-w294-6q5q-53p8)
FIXED = {(5, 4): (5, 4, 5), (5, 5): (5, 5, 10), (5, 6): (5, 6, 1)}
DEP = re.compile(r"com\.hazelcast:(hazelcast(?:-enterprise)?)(?::jar)?:(\d+\.\d+(?:\.\d+)?)")
def vtuple(v):
parts = [int(x) for x in re.findall(r"\d+", v)[:3]]
return tuple(parts + [0] * (3 - len(parts)))
def check_version(artifact, version):
v = vtuple(version)
if v >= (5, 7, 0):
status = "fixed"
elif v[:2] in FIXED:
status = "fixed" if v >= FIXED[v[:2]] else "AFFECTED"
else:
status = "AFFECTED (older line: no public fix, ask Hazelcast support or move to 5.7.0)"
notes = []
if artifact == "hazelcast-enterprise":
notes.append("no advisory names this artifact, so scanners that match on package name report nothing")
elif status == "fixed" and v < (5, 7, 0):
notes.append("fixed per vendor, but OSV records end the range at 5.7.0, so OSV-based scanners still flag it")
elif v[:2] in FIXED and v < (5, 7, 0):
notes.append("on Community Edition the only fix is 5.7.0 (community gets no patch releases)")
return status, notes
def scan_deps(path, text):
found = set(DEP.findall(text))
if path.endswith(".xml") and "<project" in text:
root = ET.fromstring(text)
ns = {"m": root.tag.split("}")[0].strip("{")} if root.tag.startswith("{") else {}
q = (lambda t: "m:" + t) if ns else (lambda t: t)
props = {}
p = root.find(q("properties"), ns)
if p is not None:
for e in p:
props[e.tag.split("}")[-1]] = (e.text or "").strip()
for d in root.iter(("{%s}" % ns["m"] if ns else "") + "dependency"):
g, a, v = (d.findtext(q(t), "", ns).strip() for t in ("groupId", "artifactId", "version"))
v = re.sub(r"\$\{([^}]+)\}", lambda m: props.get(m.group(1), m.group(0)), v)
if g == "com.hazelcast" and a in ("hazelcast", "hazelcast-enterprise") and re.match(r"\d", v):
found.add((a, v))
for a, v in sorted(found):
status, notes = check_version(a, v)
print(f" {a} {v}: {status}")
for n in notes:
print(f" note: {n}")
return bool(found)
def mini_yaml(text):
"""Just enough YAML for hazelcast.yaml: nested maps, lists of scalars and lists of maps."""
lines = [re.sub(r"\s+#.*$", "", l) for l in text.splitlines() if l.strip() and not l.strip().startswith("#")]
root = {}
stack = [(-1, root)]
for i, line in enumerate(lines):
indent = len(line) - len(line.lstrip())
body = line.strip()
while indent <= stack[-1][0]:
stack.pop()
parent = stack[-1][1]
nxt = lines[i + 1] if i + 1 < len(lines) else ""
nxt_indent = len(nxt) - len(nxt.lstrip())
child_is_list = nxt.strip().startswith("- ") and nxt_indent > indent
if body.startswith("- "):
item = body[2:].strip()
m = re.match(r"^([\w.-]+):\s*(.*)$", item)
if m:
d = {}
parent.append(d)
stack.append((indent, d))
k, val = m.group(1), m.group(2).strip("'\"")
if val:
d[k] = val
else:
d[k] = [] if child_is_list else {}
stack.append((indent + 1, d[k]))
else:
parent.append(item.strip("'\""))
continue
m = re.match(r"^([\w.-]+):\s*(.*)$", body)
if not m or not isinstance(parent, dict):
continue
k, val = m.group(1), m.group(2).strip("'\"")
if val in ("[]", "{}"):
parent[k] = [] if val == "[]" else {}
elif val:
parent[k] = val
else:
parent[k] = [] if child_is_list else {}
stack.append((indent, parent[k]))
return root
def as_list(x):
return x if isinstance(x, list) else ([] if x in (None, {}, "") else [x])
def config_from_yaml(text):
hz = mini_yaml(text).get("hazelcast", {})
sec = hz.get("security", {}) if isinstance(hz.get("security"), dict) else {}
zc = hz.get("serialization", {}).get("compact-serialization", {}).get("zero-config-filter")
flt = None
if isinstance(zc, dict):
wl = zc.get("whitelist", {})
entries = sum(len(as_list(v)) for v in wl.values()) if isinstance(wl, dict) else 0
flt = {"defaults_disabled": str(zc.get("defaults-disabled", "false")).lower() == "true", "allow": entries}
perms = []
cp = sec.get("client-permissions", {}) if isinstance(sec.get("client-permissions"), dict) else {}
for p in as_list(cp.get("map")):
if isinstance(p, dict):
perms.append((p.get("name", "*"), p.get("principal", "*"), [a.lower() for a in as_list(p.get("actions"))]))
has_all = "all" in cp
return str(sec.get("enabled", "false")).lower() == "true", flt, perms, has_all
def config_from_xml(text):
root = ET.fromstring(text)
strip = lambda t: t.split("}")[-1]
for e in root.iter():
e.tag = strip(e.tag)
sec = root.find("security")
enabled = sec is not None and sec.get("enabled", "false").lower() == "true"
flt = None
zc = root.find("serialization/compact-serialization/zero-config-filter")
if zc is not None:
wl = zc.find("whitelist")
flt = {"defaults_disabled": zc.get("defaults-disabled", "false").lower() == "true",
"allow": len(list(wl)) if wl is not None else 0}
perms, has_all = [], False
if sec is not None:
for p in sec.iter("map-permission"):
acts = [(a.text or "").strip().lower() for a in p.iter("action")]
perms.append((p.get("name", "*"), p.get("principal", "*"), acts))
has_all = sec.find("client-permissions/all-permissions") is not None
return enabled, flt, perms, has_all
def scan_config(path, text):
if path.endswith((".yaml", ".yml")):
if not re.search(r"^hazelcast:", text, re.M):
return False
enabled, flt, perms, has_all = config_from_yaml(text)
elif re.search(r"<hazelcast[\s>]", text) and "<project" not in text:
enabled, flt, perms, has_all = config_from_xml(text)
else:
return False
if not enabled:
print(" security: OFF. Every client that can reach a member port is fully trusted,")
print(" so 'low-privileged client' means any client (client security is an Enterprise feature)")
else:
print(" security: on")
if flt is None:
print(" zero-config Compact filter: not configured (built-in blocklist of internal classes only)")
elif flt["defaults_disabled"] and flt["allow"] == 0:
print(" zero-config Compact filter: zero-config serialization disabled")
elif flt["allow"]:
print(f" zero-config Compact filter: allowlist with {flt['allow']} entr{'y' if flt['allow'] == 1 else 'ies'}")
else:
print(" zero-config Compact filter: present but no allowlist (blocklist only)")
if has_all:
print(" all-permissions granted to a principal: includes aggregate and projection")
for name, principal, acts in perms:
if "all" in acts or not acts:
continue
missing = [a for a in ("aggregate", "projection") if a not in acts]
if missing:
print(f" map-permission name={name} principal={principal} actions={','.join(acts)}")
print(f" unfixed 5.4 to 5.6 builds: any action here already allows aggregate() and project() on '{name}'")
print(f" fixed builds: {' and '.join(missing)} will be denied unless you add them")
return True
def main(paths):
for path in paths:
text = open(path, encoding="utf-8", errors="replace").read()
print(path)
if not (scan_config(path, text) or scan_deps(path, text)):
print(" nothing Hazelcast-related found")
if __name__ == "__main__":
main(sys.argv[1:] or sys.exit(__doc__))
On five fixture files: a Community service pinned to 5.6.0, an Enterprise Gradle build on 5.5.9, a Maven build already on the Enterprise-line core 5.5.10, an Enterprise member config with security on, and a Community member config:
fx/pom.xml
hazelcast 5.6.0: AFFECTED
note: on Community Edition the only fix is 5.7.0 (community gets no patch releases)
fx/gradle.lockfile
hazelcast-enterprise 5.5.9: AFFECTED
note: no advisory names this artifact, so scanners that match on package name report nothing
fx/deps.txt
hazelcast 5.5.10: fixed
note: fixed per vendor, but OSV records end the range at 5.7.0, so OSV-based scanners still flag it
fx/hazelcast.yaml
security: on
zero-config Compact filter: not configured (built-in blocklist of internal classes only)
map-permission name=orders-* principal=reporting actions=read
unfixed 5.4 to 5.6 builds: any action here already allows aggregate() and project() on 'orders-*'
fixed builds: aggregate and projection will be denied unless you add them
map-permission name=sessions principal=web actions=put,read,remove
unfixed 5.4 to 5.6 builds: any action here already allows aggregate() and project() on 'sessions'
fixed builds: aggregate and projection will be denied unless you add them
fx/hazelcast-community.xml
security: OFF. Every client that can reach a member port is fully trusted,
so 'low-privileged client' means any client (client security is an Enterprise feature)
zero-config Compact filter: allowlist with 1 entry
The reporting principal shows both sides of the change. On 5.6.0 its read-only grant on orders-* already lets it run aggregations and projections, which is the bug. On a fixed version, those calls fail until you add the two actions. If your dashboards or reports use aggregate() and break after the upgrade, add aggregate and projection to the principals that need them. Granting all would fix the error too, but it also gives those principals every other action on the map.
Knowing which artifact a service actually ships is half of this problem. Safeguard's interactive walkthrough shows a full dependency inventory, including how each component got into the build:
What to do
- Find every Hazelcast deployment, embedded members included. Search build files for both
hazelcastandhazelcast-enterprise, because scanners won't flag the second. - Upgrade. Community users move to 5.7.0. Enterprise users can take 5.7.2 (9 October), the latest maintenance release, or the fixed release on their current line.
- Configure the zero-config filter on members and clients: an allowlist of your own data classes, or an empty allowlist with
defaults-disabledset to true if you don't use zero-config Compact. - Update client permissions before you roll out the upgrade, adding
aggregateandprojectiononly where they are needed. - Close the network path. Only application hosts should reach member ports. Enable security and mutual TLS if you're on Enterprise. Our Java deserialization guide covers the wider class of bug behind CVE-2026-107726.
- Suppress the false positives with a record, not silently. If your scanner flags 5.5.10 or 5.6.1, note that the vendor lists them as fixed so the next reviewer knows why.
The fair view
Hazelcast did the important part early. The fixes shipped in March and May, the release notes described them honestly, and the advisories give clear workarounds and fixed versions for every supported line. A delay between a fix and its CVE also gives users time to upgrade before details are public, which is a defensible choice for an RCE-class bug.
The cost of that choice falls on teams that only act on scanner alerts. They learned about this in October, and the package mapping means some of them still won't see it, while others will chase findings on versions that are already fixed. If you run Hazelcast, the vendor's release notes are the primary source, and a scanner result is a hint to check them.
Check yourself
You run Hazelcast Community 5.6.0 on a private network. Which of the two CVEs matters more for you, and why?
CVE-2026-107726. Community Edition has no client authorization, so every client is already fully trusted and the permission bug in CVE-2026-107725 adds nothing. The memory-read bug still lets any client that can connect read member memory and crash members. The fix is 5.7.0.
A client has only the read action on a map. On Hazelcast 5.6.0, can it run IMap.aggregate() on that map? What about on 5.7.0?
On 5.6.0, yes: the required permission had a mask of zero, so any granted permission on the map passed. On 5.7.0, no: it needs the aggregate action, so the call fails until you grant it.
Your scanner shows no findings for a service built on hazelcast-enterprise 5.5.9. Is it safe?
No. The advisories name only the com.hazelcast:hazelcast artifact, so scanners that match on package name don't flag hazelcast-enterprise. Enterprise 5.5.9 is below the 5.5.10 fix and is affected.