Skip to content

Commit 47ba5b8

Browse files
authored
fix(memory): use restrictive file mode for memory files
* fix(memory): use restrictive file mode for memory files Extract _FILE_CREATE_MODE constant set to 0o600 (owner read/write only), replacing the previous 0o666. In environments with a permissive umask (e.g. Docker where umask is often 0o000), 0o666 would make memory files world-readable or even world-writable. * fix(client): update mkdir calls to use 0o700
1 parent 4fd9446 commit 47ba5b8

File tree

1 file changed

+21
-12
lines changed

1 file changed

+21
-12
lines changed

src/anthropic/lib/tools/_beta_builtin_memory_tool.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@
4242
MAX_LINES = 999999
4343
LINE_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

4655
class 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

Comments
 (0)