@@ -30,13 +30,23 @@ def __init__(self, env_path):
3030 """Create a store rooted at ``env_path`` (``None`` disables persistence)."""
3131 self .env_path = env_path
3232
33+ def _safe_eid (self , eid ):
34+ """Sanitise ``eid`` into the id used for on-disk filenames.
35+
36+ Strips surrounding whitespace and neutralises path separators (via
37+ ``escape_eid``) so a crafted id such as ``../evil`` cannot escape
38+ ``env_path``. Saves, loads, deletes and existence checks all funnel
39+ through this so they agree on the file a given ``eid`` maps to.
40+ """
41+ return escape_eid (eid .strip ())
42+
3343 def _primary_path (self , eid ):
3444 """Return the canonical ``<env_path>/<eid>.json`` path for ``eid``.
3545
3646 Returns ``None`` if the resolved path would escape ``env_path`` (guards
3747 against path traversal via a crafted env id).
3848 """
39- safe_eid = escape_eid ( eid . strip () )
49+ safe_eid = self . _safe_eid ( eid )
4050 base = os .path .abspath (self .env_path )
4151 path = os .path .abspath (os .path .join (base , "{0}.json" .format (safe_eid )))
4252 try :
@@ -47,7 +57,7 @@ def _primary_path(self, eid):
4757
4858 def _hash_path (self , eid ):
4959 """Return the ``hash_<sha256>.json`` fallback path for ``eid``."""
50- safe_eid = escape_eid ( eid . strip () )
60+ safe_eid = self . _safe_eid ( eid )
5161 hashed_id = hashlib .sha256 (safe_eid .encode ("utf-8" )).hexdigest ()
5262 return os .path .join (self .env_path , "hash_{0}.json" .format (hashed_id ))
5363
@@ -63,22 +73,29 @@ def _resolve_existing(self, eid):
6373
6474 def save_env (self , eid , env_data ):
6575 """Persist a single environment; return ``True`` if written, else ``False``."""
66- if self .env_path is None :
67- return False
68- serialize_env ({eid : env_data }, [eid ], env_path = self .env_path )
69- return True
76+ return bool (self .save_envs ({eid : env_data }, [eid ]))
7077
7178 def save_envs (self , state , eids ):
72- """Persist the named subset of ``state``; return the ids actually written."""
79+ """Persist the named subset of ``state``; return the ids actually written.
80+
81+ Each id is sanitised (see :meth:`_safe_eid`) before it becomes a
82+ filename, so a crafted id cannot write outside ``env_path``. The real
83+ (unsanitised) ids are returned, matching how callers refer to them.
84+ """
7385 if self .env_path is None :
7486 return []
75- return serialize_env (state , eids , env_path = self .env_path )
87+ written = []
88+ for eid in eids :
89+ if eid not in state :
90+ continue
91+ safe_eid = self ._safe_eid (eid )
92+ serialize_env ({safe_eid : state [eid ]}, [safe_eid ], env_path = self .env_path )
93+ written .append (eid )
94+ return written
7695
7796 def save_all (self , state ):
7897 """Persist every environment in ``state``; return the ids written."""
79- if self .env_path is None :
80- return []
81- return serialize_env (state , list (state .keys ()), env_path = self .env_path )
98+ return self .save_envs (state , list (state .keys ()))
8299
83100 def load_env (self , eid ):
84101 """Read one environment by ``eid``; return ``{}`` if it is absent."""
0 commit comments