Unauthenticated Serve replica gRPC requests allow arbitrary code execution
Every Python Serve replica exposed an unauthenticated pickle sink on an ephemeral node port
CVSS 3.1 base score
CriticalVector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Summary
Source
ASGIRequest.pickled_request_metadata
src/ray/protobuf/serve.proto:525
Sink
_wrap_grpc_call.wrapper()
python/ray/serve/_private/replica.py:248
A network caller could invoke an internal Serve replica gRPC method without the configured Ray token and place a Python pickle reduction in pickled_request_metadata, causing arbitrary code execution in the replica process before normal request dispatch.
Root cause. Replica.__init__ created ASGIService with raw grpc.aio.server() and _on_initialized bound it through add_insecure_port("[::]:0"). The shared token interceptor and TLS helper were skipped, while _wrap_grpc_call performed pickle.loads() as its first operation.
Remediation evidence. Ray opened exact PR #65189 after the disclosure and merged it on 5 August 2026. The patch moves both server and client sides to Ray's shared authenticated/TLS-aware gRPC helpers and adds token-mode coverage.
Severity
- Attack vectorAV
- Network
- Attack complexityAC
- Low
- Privileges requiredPR
- None
- User interactionUI
- None
- ScopeS
- Unchanged
- ConfidentialityC
- High
- IntegrityI
- High
- AvailabilityA
- High
Metric values as published in the disclosure vector. Meters show how far each value raises exposure.
Source-to-sink trace
- Source · attacker-controlledsrc/
ray/ protobuf/ serve.proto:525 ASGIRequest.pickled_request_metadata
- Step 01python/
ray/ serve/ _private/ replica.py:Replica.__init__() Each initialized Python Serve replica created a raw asynchronous gRPC server outside Ray's authentication factory.
python self._server = grpc.aio.server(options=grpc_options) - Step 02python/
ray/ serve/ _private/ replica.py:_on_initialized() The internal service was exposed on an all-interface ephemeral port without TLS or token interception.
python port = self._server.add_insecure_port("[::]:0") await self._server.start() - Step 03python/
ray/ serve/ _private/ replica.py:248 Any registered ASGI RPC reached an unconditional deserialization sink before admission control or user-code dispatch.
python request_metadata = pickle.loads(request.pickled_request_metadata) - Sinkpython/
ray/ serve/ _private/ replica.py:248 _wrap_grpc_call.wrapper()
Impact
Reported impact
The attacker obtains arbitrary Python/process execution in an existing Serve replica despite token authentication. That process authority can expose inherited secrets, files, cloud identity, application state, and network access, and can tamper with or terminate the service.
Attack surface
Ephemeral ray.serve.ASGIService gRPC ports reachable on Ray cluster or VPN node networks when a Python Serve replica is initialized.
Preconditions
The attacker needs TCP reachability to a Ray node and the replica's discoverable ephemeral port. The documented token mode may be enabled; no valid token, deployment handle, or victim interaction is required.
Attack path
- 1
Identify an initialized Python Serve replica's open ASGIService port.
- 2
Invoke a registered gRPC method without valid authorization metadata.
- 3
Place a pickle reduction in
ASGIRequest.pickled_request_metadata. - 4
The handler deserializes it immediately under the replica worker identity.
Proof of concept
- 01
Environment setup
Use an isolated Ray cluster on the vulnerable revision with
RAY_AUTH_MODE=tokenand deploy one harmless Python Serve replica. - 02
Target configuration
Allow the test client to reach node ephemeral ports, but do not provide it the Ray authentication token.
- 03
Exploit delivery
Discover the ASGIService port and send one protobuf request whose
pickled_request_metadatacontains a harmless proof reduction, such as writing a marker in the disposable container. - 04
Expected response
The marker appears in the replica before request metadata validation; missing or invalid authorization metadata does not block the RPC.
- 05
Outcome
PR #65189 attaches Ray's asynchronous authentication interceptor and uses the corresponding client/channel helper, so the same unauthenticated request is rejected before deserialization.
Remediation
Guidance
Upgrade to a Ray release containing PR #65189. Construct every internal gRPC server and client through the shared token- and TLS-aware helpers, and never deserialize Python pickle before authentication.
self._server = grpc.aio.server(options=options)self._server.add_insecure_port("[::]:0")self._server = create_grpc_server_with_interceptors( asynchronous=True, options=options)add_port_to_grpc_server(self._server, address)
Check the upstream record for the project's current remediation status.
Investigate the paths that matter in your codebase.
Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.
