Security: pickle.loads() on remote HTTP response in RemoteAPIOrderBookDataSource
Summary
RemoteAPIOrderBookDataSource.get_tracking_pairs() fetches a binary blob from api.coinalpha.com and deserializes it with pickle.loads() directly, with no integrity check. Python's pickle protocol can instantiate arbitrary objects and execute code during deserialization, so a compromised server or DNS hijack is enough to achieve code execution — HTTPS and Basic Auth protect the transport, not the payload.
I sent a detailed report with reproduction steps and test artifacts to [email protected]. Happy to share the full writeup here or in a private channel.
Affected Code
File: hummingbot/core/data_type/remote_api_order_book_data_source.py#L64-L65
binary_data: bytes = await response.read()
order_book_tracker_data: Dict[str, Tuple[pd.DataFrame, pd.DataFrame]] = pickle.loads(binary_data)Impact
If api.coinalpha.com is ever compromised or the DNS resolution is hijacked, the attacker can return a crafted pickle payload that runs arbitrary code on every machine that calls get_tracking_pairs(). The official Docker image runs as root.
For a bot that holds live exchange API credentials, the threat model really does include server compromise — the credentials themselves are the prize.
Suggested Fix
The cleanest long-term fix is switching the endpoint to JSON. If the binary format needs to stay, a restricted unpickler that allowlists only the expected types stops arbitrary code execution:
import io
class SafeOrderBookUnpickler(pickle.Unpickler):
_ALLOWED = {
("pandas.core.frame", "DataFrame"),
("builtins", "dict"),
("builtins", "tuple"),
("builtins", "list"),
("builtins", "float"),
("builtins", "int"),
("builtins", "str"),
("numpy", "ndarray"),
("numpy.core.multiarray", "_reconstruct"),
("numpy", "dtype"),
}
def find_class(self, module, name):
if (module, name) not in self._ALLOWED:
raise pickle.UnpicklingError(f"Blocked: {module}.{name}")
return super().find_class(module, name)
order_book_tracker_data = SafeOrderBookUnpickler(io.BytesIO(binary_data)).load()I've submitted a pull request with this fix and tests.
Source: hummingbot/hummingbot