Skip to content

Commit 434a744

Browse files
authored
fix!: HMAC-sign Dash store data and deprecate dash (#6867)
1 parent b348c09 commit 434a744

1 file changed

Lines changed: 33 additions & 5 deletions

File tree

holoviews/plotting/plotly/dash.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33

44
import base64
55
import copy
6+
import hashlib
7+
import hmac
8+
import os
69
import pickle
10+
import secrets
711
import uuid
812
from collections import namedtuple
913

@@ -29,6 +33,10 @@
2933
from holoviews.plotting.plotly.util import clean_internal_figure_properties
3034
from holoviews.plotting.util import initialize_dynamic
3135
from holoviews.streams import Derived, History
36+
from holoviews.util.warnings import deprecated
37+
38+
deprecated("1.24.0", "Dash support", repr_old=False)
39+
3240

3341
# Dash imports
3442
try:
@@ -50,6 +58,8 @@
5058
DashComponents = namedtuple("DashComponents", ["graphs", "kdims", "store", "resets", "children"])
5159
HoloViewsFunctionSpec = namedtuple("HoloViewsFunctionSpec", ["fn", "kdims", "streams"])
5260

61+
_STORE_SECRET: bytes = os.getenv("HOLOVIEWS_DASH_TOKEN", "").encode() or secrets.token_bytes(32)
62+
5363

5464
def get_layout_ranges(plot):
5565
layout_ranges = {}
@@ -272,9 +282,10 @@ def populate_stream_callback_graph(stream_callbacks, streams):
272282
def encode_store_data(store_data):
273283
"""Encode store_data dict into a JSON serializable dict
274284
275-
This is currently done by pickling store_data and converting to a base64 encoded
276-
string. If HoloViews supports JSON serialization in the future, this method could
277-
be updated to use this approach instead
285+
The dict is pickled, base64-encoded, and HMAC-signed with a secret
286+
(set via the ``HOLOVIEWS_DASH_TOKEN`` environment variable, or a random
287+
per-process value) so that decode_store_data can reject any payload that
288+
was not produced by a server process sharing the same secret.
278289
279290
Parameters
280291
----------
@@ -284,21 +295,38 @@ def encode_store_data(store_data):
284295
-------
285296
dict that can be JSON serialized
286297
"""
287-
return {"pickled": base64.b64encode(pickle.dumps(store_data)).decode("utf-8")}
298+
pickled = pickle.dumps(store_data)
299+
mac = hmac.new(_STORE_SECRET, pickled, hashlib.sha256).hexdigest()
300+
return {"pickled": base64.b64encode(pickled).decode("utf-8"), "mac": mac}
288301

289302

290303
def decode_store_data(store_data):
291304
"""Decode a dict that was encoded by the encode_store_data function.
292305
306+
The HMAC signature (using the secret from ``HOLOVIEWS_DASH_TOKEN`` or the
307+
per-process random value) is verified before unpickling to prevent
308+
deserialisation of attacker-controlled payloads.
309+
293310
Parameters
294311
----------
295312
store_data : dict that was encoded by encode_store_data
296313
297314
Returns
298315
-------
299316
decoded dict
317+
318+
Raises
319+
------
320+
ValueError
321+
If the payload signature is missing or does not match.
300322
"""
301-
return pickle.loads(base64.b64decode(store_data["pickled"]))
323+
pickled = base64.b64decode(store_data["pickled"])
324+
expected_mac = hmac.new(_STORE_SECRET, pickled, hashlib.sha256).hexdigest()
325+
if not hmac.compare_digest(store_data.get("mac", ""), expected_mac):
326+
raise ValueError(
327+
"Store data integrity check failed, the payload may have been tampered with."
328+
)
329+
return pickle.loads(pickled)
302330

303331

304332
def to_dash(

0 commit comments

Comments
 (0)