fix: check the remote deployment allow list before deserializing constructor args - #3493
Merged
pjfanning merged 1 commit intoAug 31, 2026
Merged
Conversation
…tructor args Motivation: DaemonMsgCreateSerializer.fromBinary loaded the peer-named actor class and then deserialized every constructor argument - with peer-chosen serializer ids and manifests - before RemoteSystemDaemon consulted the remote deployment allow list. For a class the allow list rejects, that deserialization is attack surface taken on for a deployment that is refused moments later, which weakens the guarantee the allow list is credited with. Modification: Extract RemoteDeploymentAllowList, holding the two existing config keys and the class-name comparison, and use it from both RemoteSystemDaemon and DaemonMsgCreateSerializer so the two cannot drift. The serializer now checks the allow list immediately after resolving the actor class and before deserializing any argument, logging at error with LogMarker.Security and the same exception RemoteSystemDaemon raises. Result: With the allow list enabled, a rejected class is refused before any peer-supplied argument is deserialized. No new configuration, and no change when the allow list is off, which is the default. Tests: - sbt "remote/testOnly org.apache.pekko.remote.serialization.DaemonMsgCreateSerializerAllowListSpec org.apache.pekko.remote.serialization.DaemonMsgCreateSerializerAllowListDisabledSpec" - new, incl. a counting serializer asserting args are not deserialized for a rejected class - sbt "remote/testOnly org.apache.pekko.remote.classic.RemoteDeploymentAllowListSpec org.apache.pekko.remote.serialization.DaemonMsgCreateSerializerAllowJavaSerializationSpec org.apache.pekko.remote.serialization.DaemonMsgCreateSerializerNoJavaSerializationSpec" - existing specs pass unchanged - sbt "remote/mimaReportBinaryIssues" - no issues References: Refs apache#3478
samueleresca
approved these changes
Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
DaemonMsgCreateSerializer.fromBinaryresolved the peer-named actor class and then deserialized every constructor argument — each with a peer-chosen serializer id and manifest — beforeRemoteSystemDaemonconsulted the remote deployment allow list (RemoteDaemon.scala).So for a class the allow list rejects, a peer still got arbitrary nested deserialization performed on its behalf, for a deployment refused moments later. That weakens the guarantee the allow list is credited with: the check exists to bound what a peer can cause this node to do, but the surface-bearing work happened before it.
(For the avoidance of doubt: class loading here is
Class.forName(fqcn, false, loader)—initialize = false— so static initializers do not run. The argument deserialization is the part worth moving.)Modification
RemoteDeploymentAllowList(INTERNAL API, inRemoteDaemon.scalabeside the related exception): it owns the two existing config keys —pekko.remote.deployment.enable-allow-listand…allowed-actor-classes— and thegetCanonicalNamecomparison.RemoteSystemDaemonandDaemonMsgCreateSerializerboth use it, so the two sites cannot drift on either the keys or the comparison.errorwithLogMarker.Securityand throwing the sameNotAllowedClassRemoteDeploymentAttemptExceptionthe daemon raises.RemoteSystemDaemonkeeps its check as defence in depth.No new configuration. With the allow list off — the default — behaviour is unchanged.
Result
With the allow list enabled, a rejected class is refused before any peer-supplied constructor argument is deserialized. The deployment is still refused, with the same exception and the same security-marked log signal, wherever it is caught.
Tests
sbt "remote/testOnly …DaemonMsgCreateSerializerAllowListSpec …DaemonMsgCreateSerializerAllowListDisabledSpec"— new specs, including one that registers a counting serializer for the constructor argument and asserts the count stays at zero for a rejected class (the ordering this change is about), plus a disabled-allow-list spec pinning the default behavioursbt "remote/testOnly org.apache.pekko.remote.classic.RemoteDeploymentAllowListSpec …AllowJavaSerializationSpec …NoJavaSerializationSpec"— existing specs pass unchanged, including the end-to-end allow-list rejectionsbt "remote/mimaReportBinaryIssues"— no issuesReferences
Refs #3478