The Apache Storm community is pleased to announce that version 3.1.0 has been released and is available from the downloads page.
This is primarily a security and hardening release. It resolves a coordinated set of security issues (see the Security Fixes below), along with bug fixes, dependency upgrades, documentation improvements and a new Apache Iceberg sink. All users of Apache Storm 3.0.0 are strongly encouraged to upgrade. As with 3.0.0, Java 25 is required to run Apache Storm 3.x, and the Java API remains backwards-compatible with Storm 2.x.
The Storm 2.x line, including 2.8.9, has reached end of life and no longer receives security fixes. Users still on 2.x should treat the issues below as unpatched there and migrate to 3.x, where they are fixed in 3.1.0.
Several of the security fixes change default behaviour (for example, the
scheduler-strategy allowlist, JSONP wrapping, the state serializer and
nimbus.groups evaluation); please read the mitigations below before upgrading.
Special thanks are due to all those who have contributed to Apache Storm -- whether through direct code contributions, documentation, bug reports, security reports, or helping other users on the mailing lists. Your efforts are much appreciated.
JIRA issues and pull requests addressed in the 3.1.0 release of Storm. Documentation for this release is available at the Apache Storm project site.
Versions Affected: 3.0.0.
Description: Nimbus accepted the uploadedJarLocation argument of submitTopology / submitTopologyWithOpts as a server-side path and opened it directly, without checking that it referred to a file the caller had actually uploaded via beginFileUpload. An authenticated user with submission rights could therefore submit any path readable by the Nimbus daemon user as their topology jar; Nimbus copied it into the topology's jar blob, whose ACL grants the submitter read access, so the contents could be retrieved with the ordinary blob download RPCs. Candidate targets include the Nimbus Kerberos keytab, Thrift/UI TLS private keys and storm.yaml with the ZooKeeper authentication payload. Where nimbus.users is unset, submission is available to every authenticated principal, so no elevated privilege is required.
Mitigation: Upgrade to 3.1.0, where the submitted location is canonicalised and must resolve inside the Nimbus inbox. Users who cannot upgrade immediately should restrict submission via nimbus.users/nimbus.groups and rotate the Nimbus keytab and any TLS private keys or ZooKeeper credentials readable by the Nimbus daemon user. Local mode is unaffected.
Credit: Independently reported to the Apache Storm PMC by n0mi1k, with a proof of concept. Also found by the ASF using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: A topology's topology.blobstore.map lets the submitter choose a local name for each localised blob. That name was used to build a path under the topology's working directory without normalisation, in both AsyncLocalizer and Container.createBlobstoreLinks, and the symlink helper force-deletes whatever already exists at the target before creating the link. A submitter could use ../ segments to direct that delete-and-symlink at an arbitrary path, as the supervisor user, on every node the topology is scheduled onto -- enabling recursive deletion of supervisor-owned content and planting a symlink that causes a later worker launch to execute attacker-chosen code as another tenant's OS user, defeating supervisor.run.worker.as.user isolation.
Mitigation: Upgrade to 3.1.0, where the resolved target must lie inside the expected root at both call sites. Users who cannot upgrade immediately should restrict submission to trusted principals and may reject topology.blobstore.map entries containing path separators or .. before they reach Nimbus.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: Dependency artifacts uploaded with storm jar --artifacts were stored under a blob key derived only from the Maven coordinate, identical for every user and predictable in advance. When the blob already existed the uploader caught KeyAlreadyExistsException and silently reused it, with no check that the existing blob's content or owner matched. A user who uploaded a blob under such a key first therefore controlled the bytes every later submitter of the same coordinate received on the worker classpath, resulting in code execution inside another tenant's topology. Affects multi-tenant clusters that use the --artifacts feature.
Mitigation: Upgrade to 3.1.0, where each uploaded artifact receives a key carrying a freshly generated UUID and a pre-existing blob is no longer silently reused. The corrected key generation is on the submitting client, so every client running storm jar --artifacts must also be upgraded; upgrading the cluster alone does not close it. Operators should audit existing dep- blobs for unexpected owners.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: The setuid-root worker-launcher adjusts ownership and permissions of worker directories by walking the tree with FTS and calling lchown and chmod on each entry's full pathname while effective uid 0. Both syscalls re-resolve the path at call time, after FTS has classified the entry, and the trees are owned and writable by the untrusted topology user. A tenant could replace an intermediate directory component with a symlink between classification and the privileged operation, redirecting the root-owned lchown/chmod at an arbitrary host file. The operation is repeatable at will. This is the same defect class as the Hadoop container-executor issues from which the code derives.
Mitigation: Upgrade to 3.1.0, where the privileged walk operates on file descriptors it has already stat'd rather than on pathnames re-resolved at call time. The launcher must be rebuilt and reinstalled after upgrading; replacing the Java artifacts alone is not sufficient. Users who cannot upgrade should not run untrusted topology code on supervisors configured with supervisor.run.worker.as.user.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: When launching a Docker or OCI worker, the setuid-root worker-launcher first changes ownership of the entire worker directory to the untrusted topology user, and only afterwards reads and acts on the command file the supervisor wrote there. The file is opened without O_NOFOLLOW and without re-verifying its owner, so the tenant can replace its contents in the window between. On the Docker path the parsed command runs with real uid 0 and the sanitiser is not a privilege boundary (it admits -v with an arbitrary source, --device, --cap-add, --security-opt, --user, --net), yielding an attacker-authored root-equivalent container with the host filesystem available. On the OCI path the same window applies, mount validation is structural only (no source/destination allow-list), and the attacker-settable username field permits running as another tenant's uid.
Mitigation: Upgrade to 3.1.0, where the command file is validated before the ownership change and re-verified on open, and mount sources and destinations are constrained by configuration. The launcher must be rebuilt and reinstalled after upgrading. Users who cannot upgrade should disable Docker/OCI worker isolation or restrict submission on affected supervisors to trusted principals.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: SimpleACLAuthorizer evaluated the user-level command set by returning early when nimbus.users was empty, before nimbus.groups was considered. An operator who restricted access by group alone, leaving nimbus.users unset, therefore received no restriction at all: every authenticated principal was permitted every user-level operation, including submitTopology, beginFileUpload and getNimbusConf. docs/SECURITY.md presents nimbus.groups as a supported lock-down mechanism, so a deployment following the documentation could believe it was restricted while it was not. The failure was silent.
Mitigation: Upgrade to 3.1.0, where nimbus.groups is evaluated whether or not nimbus.users is set. Users who cannot upgrade should also populate nimbus.users, since a non-empty user list causes the group list to be evaluated on affected versions, and review Nimbus access logs. Note: after upgrading, a cluster configured with nimbus.groups alone becomes restrictive for the first time -- clients outside the configured groups (including NimbusClient, which calls getLeader on every connection) will begin to be refused.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: Nimbus validated topology.blobstore.map against the calling subject at submission time only. The rebalance operation accepts configuration overrides but never re-ran that validation, so a caller authorised to rebalance a topology could introduce a blobstore-map entry naming a blob whose ACL does not grant them access; supervisors localise whatever key the map names. The same advisory covers listBlobs, which performed no authorization check and returned every key in the blobstore to any caller able to reach the Nimbus Thrift port -- the key names that make the above practical. On its own the disclosure is metadata only.
Mitigation: Upgrade to 3.1.0, where rebalance overrides are validated exactly as submission-time configuration is, against the rebalancing caller, and listBlobs applies the configured authorization. Users who cannot upgrade should restrict rebalance rights to trusted principals, noting that membership of a topology's topology.users/topology.groups confers them.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: getNimbusConf returned the complete daemon configuration without redaction after only a user-level authorization check; where configured, that includes storm.zookeeper.auth.payload and the Thrift/Netty/ZooKeeper TLS keystore and truststore passwords. The UI endpoint /api/v1/cluster/configuration compounded this: it carried no @AuthNimbusOp annotation, the authorization filter treated a missing annotation as "no gate required", and it proxied the request under the UI daemon's own principal, so any user able to pass ui.filter received the full configuration.
Mitigation: Upgrade to 3.1.0, where credential-bearing values are masked before the configuration is served and every UI API endpoint must declare its authorization explicitly. Users who cannot upgrade should place the UI behind an authenticating reverse proxy restricting /api/v1/cluster/configuration, and rotate the ZooKeeper authentication payload and any TLS keystore/truststore passwords reachable through it.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: When ZooKeeper authentication is configured, Storm deliberately retains storm.zookeeper.topology.auth.payload in the topology configuration because workers need it. Nimbus then served that configuration verbatim to any caller holding read-only topology permissions, so a user whose only grant was the ability to view a topology received its ZooKeeper credential. That credential is not read-only: the cluster-state implementation uses write-capable ACLs for worker heartbeats, backpressure and error state, so a recipient can forge or remove that state for the topology. The same advisory covers the submission client, which logged the generated payload at INFO on every submission, and the SASL handlers, which logged it at DEBUG, so the credential also reached log aggregation and support bundles.
Mitigation: Upgrade to 3.1.0, where the payload is removed from the configuration served to read-only callers and is no longer written to logs. Users who cannot upgrade immediately should rotate storm.zookeeper.topology.auth.payload for existing topologies, review retained logs and support bundles for the value, and restrict read-only topology permissions to trusted principals.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: The worker's Netty message decoder is installed ahead of the SASL authentication handlers and acts on frames before authentication. It allocated buffers sized from a length field carried in the frame, so a single frame from an unauthenticated peer able to reach a worker slot port could drive a large allocation. storm.messaging.netty.authentication defaults to false and the decoder runs before the handler that enforces it in any case, so the attacker needs only TCP reachability to a worker port. The precise effect at the default 768 MB worker heap was not measured; the assigned severity reflects the conservative reading.
Mitigation: Upgrade to 3.1.0, where frames are decoded only after the handshake completes. Users who cannot upgrade should ensure worker slot ports are reachable only from within the cluster and enable storm.messaging.netty.authentication where the deployment permits.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: DefaultStateSerializer, used to persist and restore stateful bolt checkpoints, configured Kryo with class registration disabled and an instantiation strategy that constructs objects without invoking their constructors, then deserialized whatever bytes the state store returned. A party able to write to the topology's keyspace in the state store -- for example a co-tenant of a shared Redis instance, or anyone with network access to it -- could place a serialized object graph that executes code inside the worker JVM when state is restored. This requires the Redis-backed state provider, which is not the default. Write access to a state-store key is a data-plane privilege; code execution in the worker JVM is not, so the two are not equivalent.
Mitigation: Upgrade to 3.1.0, where the state serializer requires registered classes. Note this is a behaviour change for existing state: checkpoints written by an affected version may fail to restore and a migration step may be required. Users who cannot upgrade should restrict network and credential access to the state store to the cluster itself.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: The Logviewer offers logs.users and logs.groups to control who may read log content, but for daemon logs those settings were not applied: the access decision discarded the authorizer's answer whenever the "daemon log" flag was set, and the daemon log page and download endpoints reached the handler without consulting an authorizer at all. Any user able to pass the configured servlet filter could read nimbus.log, supervisor.log and other daemon logs on every reachable node, which contain other tenants' topology names, owners and configuration fragments. The same advisory covers /listLogs and /searchLogs, which accepted a user argument and never applied it, returning every tenant's log file names (metadata only). No configuration closed either behaviour.
Mitigation: Upgrade to 3.1.0, where the daemon log paths evaluate the same configured user/group lists the worker log paths already used, and the listing endpoints filter by the requesting user. Users who cannot upgrade should place the Logviewer behind a reverse proxy that restricts the daemon log endpoints.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: Three mechanisms allowed a web page on an unrelated origin to read responses served to an authenticated user. The Logviewer reflected the request's Origin back in Access-Control-Allow-Origin while also sending Access-Control-Allow-Credentials: true, removing the protection that makes the documented * posture safe. The shared CORS filter used by the UI, Logviewer and DRPC was configured with a response header name where an init parameter name was expected, so the container applied its own credential-allowing defaults. And the UI and Logviewer wrapped every GET response in a caller-supplied JSONP callback, which any origin's script element can load, bypassing the same-origin policy entirely, with no way to turn it off.
Mitigation: Upgrade to 3.1.0, where the Logviewer no longer reflects the request origin in a credentialed response, the CORS filter is configured explicitly, and JSONP wrapping is governed by ui.enable.jsonp (default false). Disabling JSONP is a behaviour change for tooling that passes a callback parameter. Users who cannot upgrade should front these endpoints with a reverse proxy that strips the CORS headers and rejects callback.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: The DRPC server kept a map from function name to request queue and created an entry the first time a function name was seen; no code path ever removed an entry. Function names come from the client and are not constrained to registered functions, so the number of retained entries is bounded only by the distinct names an attacker sends. drpc.authorizer is unset by default, so no credentials are required. The retained state is permanent rather than a transient load spike, so it accumulates until the DRPC server exhausts its heap.
Mitigation: Upgrade to 3.1.0, where a function's queue is removed once nothing is waiting in it. Users who cannot upgrade should configure drpc.authorizer so only trusted principals can reach the DRPC endpoints and ensure the DRPC ports are not reachable from untrusted networks.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: Nimbus instantiates the class named by a topology's topology.scheduler.strategy. The allowlist meant to constrain that choice, nimbus.scheduler.strategy.class.whitelist, was treated as "allow any class" when unset and had no entry in the shipped defaults.yaml, so unset was the shipped state. An authenticated submitter could cause Nimbus to instantiate any class on its classpath with a no-argument constructor; Storm's own artifacts contain classes with side effects (e.g. org.apache.storm.testing.InProcessZookeeper, org.apache.storm.LocalCluster), so repeated submissions consume threads, sockets and memory in the most privileged daemon.
Mitigation: Upgrade to 3.1.0, where an unset allowlist means the strategies shipped with Storm rather than any class. This is a behaviour change: a cluster running an out-of-tree strategy without an explicit allowlist will have those topologies refused until the class is added to nimbus.scheduler.strategy.class.whitelist. Users who cannot upgrade should set that key explicitly.
Credit: The ASF -- found using Claude agents to study the security of open-source projects, validated and reported by Apache Storm.
Versions Affected: 3.0.0.
Description: A submitted topology carries dependency_jars and dependency_artifacts lists of blobstore keys, which Nimbus did not validate on submission yet acted on in two places. During cleanup Nimbus deletes the keys named in those lists as the Nimbus subject, for which the blobstore short-circuits its ACL check, so a submitter who listed another topology's key (such as its -stormjar.jar) could cause that blob to be deleted. Separately, on acquiring leadership a Nimbus surrenders leadership if any listed dependency key is missing, so a single non-existent key on one active topology makes every Nimbus acquire, surrender and requeue leadership indefinitely, leaving the cluster without a leader.
Mitigation: Upgrade to 3.1.0, where a submission is refused unless every entry in both lists is a dependency blob key that exists in the blobstore. This validates new submissions only; an operator whose cluster is failing to retain a leader should inspect the Nimbus log for the missing dependency keys and remove or resubmit the topology naming them. Users who cannot upgrade should restrict topology submission to trusted principals.
Credit: This issue was discovered by rzo1 while investigating an unrelated blobstore defect.
Versions Affected: 3.0.0.
Description: getTopologyPageInfo merged the Nimbus daemon configuration with the topology's own configuration and returned the result without redaction in the topology_conf field of TopologyPageInfo; the UI copied it verbatim into the configuration field of GET /api/v1/topology/{id} and the corresponding metrics endpoint. Where configured, the merged map includes storm.zookeeper.auth.payload and the Thrift/Netty/ZooKeeper TLS keystore and truststore passwords. Because this is a topology read-only operation, under SimpleACLAuthorizer a principal in topology.readonly.users/topology.readonly.groups could read daemon credentials that the dedicated getNimbusConf API redacts and gates on nimbus.users.
Mitigation: Upgrade to 3.1.0, where credential-bearing values are masked before any configuration is served over the Nimbus API. Users who cannot upgrade should remove any principal not trusted with cluster credentials from topology.readonly.users, topology.readonly.groups, topology.users and topology.groups, and rotate the ZooKeeper authentication payload and any TLS keystore/truststore passwords reachable through the topology page.
Credit: Wanxin Yin (yaklang.io) reported this issue to the Apache Security Team.
We also thank n0mi1k for reporting the getTopologyHistory authorization gap addressed in this release by #9003. It is handled as a hardening improvement and is not covered by a separate advisory.