4242MAX_LINES = 999999
4343LINE_NUMBER_WIDTH = len (str (MAX_LINES ))
4444
45+ # Owner read/write only. Avoids 0o666 which, in environments with a permissive
46+ # umask (e.g. Docker where umask is often 0o000), would make memory files
47+ # world-readable or even world-writable.
48+ _FILE_CREATE_MODE = 0o600
49+ # The default mkdir mode is 0o777, but we want to be more restrictive for memory
50+ # directories to avoid them being world-accessible in environments with permissive umasks
51+ # (eg Docker)
52+ _DIR_CREATE_MODE = 0o700
53+
4554
4655class BetaAbstractMemoryTool (BetaBuiltinFunctionTool ):
4756 """Abstract base class for memory tool implementations.
@@ -275,7 +284,7 @@ def _atomic_write_file(target_path: Path, content: str) -> None:
275284 data = content .encode ("utf-8" )
276285
277286 try :
278- fd = os .open (temp_path , os .O_CREAT | os .O_EXCL | os .O_WRONLY , 0o666 )
287+ fd = os .open (temp_path , os .O_CREAT | os .O_EXCL | os .O_WRONLY , _FILE_CREATE_MODE )
279288 try :
280289 offset = 0
281290 while offset < len (data ):
@@ -342,7 +351,7 @@ def __init__(self, base_path: str = "./memory"):
342351 super ().__init__ ()
343352 self .base_path = Path (base_path )
344353 self .memory_root = self .base_path / "memories"
345- self .memory_root .mkdir (parents = True , exist_ok = True )
354+ self .memory_root .mkdir (parents = True , exist_ok = True , mode = _DIR_CREATE_MODE )
346355
347356 def _validate_path (self , path : str ) -> Path :
348357 """Validate and resolve memory paths"""
@@ -434,10 +443,10 @@ def collect_items(dir_path: Path, relative_path: str, depth: int) -> None:
434443 def create (self , command : BetaMemoryTool20250818CreateCommand ) -> str :
435444 full_path = self ._validate_path (command .path )
436445
437- full_path .parent .mkdir (parents = True , exist_ok = True )
446+ full_path .parent .mkdir (parents = True , exist_ok = True , mode = _DIR_CREATE_MODE )
438447
439448 try :
440- fd = os .open (full_path , os .O_CREAT | os .O_EXCL | os .O_WRONLY , 0o666 )
449+ fd = os .open (full_path , os .O_CREAT | os .O_EXCL | os .O_WRONLY , _FILE_CREATE_MODE )
441450 try :
442451 os .write (fd , command .file_text .encode ("utf-8" ))
443452 os .fsync (fd )
@@ -549,7 +558,7 @@ def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str:
549558 if new_full_path .exists ():
550559 raise ToolError (f"The destination { command .new_path } already exists" )
551560
552- new_full_path .parent .mkdir (parents = True , exist_ok = True )
561+ new_full_path .parent .mkdir (parents = True , exist_ok = True , mode = _DIR_CREATE_MODE )
553562
554563 try :
555564 old_full_path .rename (new_full_path )
@@ -563,7 +572,7 @@ def clear_all_memory(self) -> str:
563572 """Override the base implementation to provide file system clearing."""
564573 if self .memory_root .exists ():
565574 shutil .rmtree (self .memory_root )
566- self .memory_root .mkdir (parents = True , exist_ok = True )
575+ self .memory_root .mkdir (parents = True , exist_ok = True , mode = _DIR_CREATE_MODE )
567576 return "All memory cleared"
568577
569578
@@ -576,7 +585,7 @@ async def _async_atomic_write_file(target_path: AsyncPath, content: str) -> None
576585 try :
577586
578587 def write_replace_and_sync () -> None :
579- fd = os .open (sync_temp_path , os .O_CREAT | os .O_EXCL | os .O_WRONLY , 0o666 )
588+ fd = os .open (sync_temp_path , os .O_CREAT | os .O_EXCL | os .O_WRONLY , _FILE_CREATE_MODE )
580589 try :
581590 offset = 0
582591 while offset < len (data ):
@@ -624,7 +633,7 @@ def __init__(self, base_path: str = "./memory"):
624633
625634 async def _ensure_memory_root (self ) -> None :
626635 """Ensure the memory root directory exists"""
627- await self .memory_root .mkdir (parents = True , exist_ok = True )
636+ await self .memory_root .mkdir (parents = True , exist_ok = True , mode = _DIR_CREATE_MODE )
628637
629638 async def _validate_path (self , path : str ) -> AsyncPath :
630639 """Validate and resolve memory paths"""
@@ -724,13 +733,13 @@ async def create(self, command: BetaMemoryTool20250818CreateCommand) -> str:
724733 await self ._ensure_memory_root ()
725734 full_path = await self ._validate_path (command .path )
726735
727- await full_path .parent .mkdir (parents = True , exist_ok = True )
736+ await full_path .parent .mkdir (parents = True , exist_ok = True , mode = _DIR_CREATE_MODE )
728737
729738 try :
730739 sync_full_path = Path (str (full_path ))
731740
732741 def create_exclusive () -> None :
733- fd = os .open (sync_full_path , os .O_CREAT | os .O_EXCL | os .O_WRONLY , 0o666 )
742+ fd = os .open (sync_full_path , os .O_CREAT | os .O_EXCL | os .O_WRONLY , _FILE_CREATE_MODE )
734743 try :
735744 os .write (fd , command .file_text .encode ("utf-8" ))
736745 os .fsync (fd )
@@ -848,7 +857,7 @@ async def rename(self, command: BetaMemoryTool20250818RenameCommand) -> str:
848857 if await new_full_path .exists ():
849858 raise ToolError (f"The destination { command .new_path } already exists" )
850859
851- await new_full_path .parent .mkdir (parents = True , exist_ok = True )
860+ await new_full_path .parent .mkdir (parents = True , exist_ok = True , mode = _DIR_CREATE_MODE )
852861
853862 try :
854863 await old_full_path .rename (new_full_path )
@@ -862,5 +871,5 @@ async def clear_all_memory(self) -> str:
862871 """Override the base implementation to provide file system clearing."""
863872 if await self .memory_root .exists ():
864873 await run_sync (shutil .rmtree , str (self .memory_root ))
865- await self .memory_root .mkdir (parents = True , exist_ok = True )
874+ await self .memory_root .mkdir (parents = True , exist_ok = True , mode = _DIR_CREATE_MODE )
866875 return "All memory cleared"
0 commit comments