@@ -30,6 +30,25 @@ def __init__(self, root: str | Path) -> None:
3030 """
3131 self .root = Path (root )
3232
33+ def _resolve_in_root (self , path_like : str | Path , * , subject : str ) -> Path :
34+ """
35+ Resolve a path-like value and ensure it stays within the configured store root.
36+
37+ Relative values are interpreted relative to `self.root`; absolute values are used as-is.
38+
39+ :param path_like: Relative or absolute path-like value to resolve.
40+ :param subject: Human-readable label used in the error message.
41+ :returns: The resolved absolute path within the store root.
42+ :raises ValueError: If the resolved path escapes the store root.
43+ """
44+ root = self .root .resolve ()
45+ path = Path (path_like )
46+ candidate = path if path .is_absolute () else root / path
47+ resolved = candidate .resolve ()
48+ if not resolved .is_relative_to (root ):
49+ raise ValueError (f"{ subject } '{ path_like } ' resolves outside the store root '{ root } '." )
50+ return resolved
51+
3352 def write (self , * , key : str , content : str ) -> str :
3453 """
3554 Write `content` to `<root>/<key>`, creating parent directories, and return the file path.
@@ -42,10 +61,7 @@ def write(self, *, key: str, content: str) -> str:
4261 :returns: The absolute path the content was written to, as a string, for use with `read`.
4362 :raises ValueError: If `key` resolves to a location outside the store root.
4463 """
45- root = self .root .resolve ()
46- path = (root / key ).resolve ()
47- if not path .is_relative_to (root ):
48- raise ValueError (f"Result key '{ key } ' resolves outside the store root '{ root } '." )
64+ path = self ._resolve_in_root (key , subject = "Result key" )
4965 path .parent .mkdir (parents = True , exist_ok = True )
5066 path .write_text (content , encoding = "utf-8" )
5167 return str (path )
@@ -54,10 +70,14 @@ def read(self, reference: str) -> str:
5470 """
5571 Read back the content previously written to `reference`.
5672
57- :param reference: A path returned by `write`.
73+ The resolved reference must stay within the store root: callers must treat it as an opaque
74+ store-scoped reference, not as an arbitrary filesystem path.
75+
76+ :param reference: A store reference returned by `write`.
5877 :returns: The stored content.
78+ :raises ValueError: If `reference` resolves to a location outside the store root.
5979 """
60- return Path (reference ).read_text (encoding = "utf-8" )
80+ return self . _resolve_in_root (reference , subject = "Result reference" ).read_text (encoding = "utf-8" )
6181
6282 def to_dict (self ) -> dict [str , Any ]:
6383 """
0 commit comments