Skip to content

Commit 8c63d75

Browse files
authored
session manager - prevent file path injection (#680)
1 parent 606f657 commit 8c63d75

File tree

9 files changed

+452
-300
lines changed

9 files changed

+452
-300
lines changed

src/strands/_identifier.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Strands identifier utilities."""
2+
3+
import enum
4+
import os
5+
6+
7+
class Identifier(enum.Enum):
8+
"""Strands identifier types."""
9+
10+
AGENT = "agent"
11+
SESSION = "session"
12+
13+
14+
def validate(id_: str, type_: Identifier) -> str:
15+
"""Validate strands id.
16+
17+
Args:
18+
id_: Id to validate.
19+
type_: Type of the identifier (e.g., session id, agent id, etc.)
20+
21+
Returns:
22+
Validated id.
23+
24+
Raises:
25+
ValueError: If id contains path separators.
26+
"""
27+
if os.path.basename(id_) != id_:
28+
raise ValueError(f"{type_.value}_id={id_} | id cannot contain path separators")
29+
30+
return id_

src/strands/agent/agent.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from opentelemetry import trace as trace_api
2020
from pydantic import BaseModel
2121

22+
from .. import _identifier
2223
from ..event_loop.event_loop import event_loop_cycle, run_tool
2324
from ..handlers.callback_handler import PrintingCallbackHandler, null_callback_handler
2425
from ..hooks import (
@@ -249,12 +250,15 @@ def __init__(
249250
Defaults to None.
250251
session_manager: Manager for handling agent sessions including conversation history and state.
251252
If provided, enables session-based persistence and state management.
253+
254+
Raises:
255+
ValueError: If agent id contains path separators.
252256
"""
253257
self.model = BedrockModel() if not model else BedrockModel(model_id=model) if isinstance(model, str) else model
254258
self.messages = messages if messages is not None else []
255259

256260
self.system_prompt = system_prompt
257-
self.agent_id = agent_id or _DEFAULT_AGENT_ID
261+
self.agent_id = _identifier.validate(agent_id or _DEFAULT_AGENT_ID, _identifier.Identifier.AGENT)
258262
self.name = name or _DEFAULT_AGENT_NAME
259263
self.description = description
260264

src/strands/session/file_session_manager.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import tempfile
88
from typing import Any, Optional, cast
99

10+
from .. import _identifier
1011
from ..types.exceptions import SessionException
1112
from ..types.session import Session, SessionAgent, SessionMessage
1213
from .repository_session_manager import RepositorySessionManager
@@ -40,8 +41,9 @@ def __init__(self, session_id: str, storage_dir: Optional[str] = None, **kwargs:
4041
"""Initialize FileSession with filesystem storage.
4142
4243
Args:
43-
session_id: ID for the session
44-
storage_dir: Directory for local filesystem storage (defaults to temp dir)
44+
session_id: ID for the session.
45+
ID is not allowed to contain path separators (e.g., a/b).
46+
storage_dir: Directory for local filesystem storage (defaults to temp dir).
4547
**kwargs: Additional keyword arguments for future extensibility.
4648
"""
4749
self.storage_dir = storage_dir or os.path.join(tempfile.gettempdir(), "strands/sessions")
@@ -50,12 +52,29 @@ def __init__(self, session_id: str, storage_dir: Optional[str] = None, **kwargs:
5052
super().__init__(session_id=session_id, session_repository=self)
5153

5254
def _get_session_path(self, session_id: str) -> str:
53-
"""Get session directory path."""
55+
"""Get session directory path.
56+
57+
Args:
58+
session_id: ID for the session.
59+
60+
Raises:
61+
ValueError: If session id contains a path separator.
62+
"""
63+
session_id = _identifier.validate(session_id, _identifier.Identifier.SESSION)
5464
return os.path.join(self.storage_dir, f"{SESSION_PREFIX}{session_id}")
5565

5666
def _get_agent_path(self, session_id: str, agent_id: str) -> str:
57-
"""Get agent directory path."""
67+
"""Get agent directory path.
68+
69+
Args:
70+
session_id: ID for the session.
71+
agent_id: ID for the agent.
72+
73+
Raises:
74+
ValueError: If session id or agent id contains a path separator.
75+
"""
5876
session_path = self._get_session_path(session_id)
77+
agent_id = _identifier.validate(agent_id, _identifier.Identifier.AGENT)
5978
return os.path.join(session_path, "agents", f"{AGENT_PREFIX}{agent_id}")
6079

6180
def _get_message_path(self, session_id: str, agent_id: str, message_id: int) -> str:

src/strands/session/s3_session_manager.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from botocore.config import Config as BotocoreConfig
99
from botocore.exceptions import ClientError
1010

11+
from .. import _identifier
1112
from ..types.exceptions import SessionException
1213
from ..types.session import Session, SessionAgent, SessionMessage
1314
from .repository_session_manager import RepositorySessionManager
@@ -51,6 +52,7 @@ def __init__(
5152
5253
Args:
5354
session_id: ID for the session
55+
ID is not allowed to contain path separators (e.g., a/b).
5456
bucket: S3 bucket name (required)
5557
prefix: S3 key prefix for storage organization
5658
boto_session: Optional boto3 session
@@ -79,12 +81,29 @@ def __init__(
7981
super().__init__(session_id=session_id, session_repository=self)
8082

8183
def _get_session_path(self, session_id: str) -> str:
82-
"""Get session S3 prefix."""
84+
"""Get session S3 prefix.
85+
86+
Args:
87+
session_id: ID for the session.
88+
89+
Raises:
90+
ValueError: If session id contains a path separator.
91+
"""
92+
session_id = _identifier.validate(session_id, _identifier.Identifier.SESSION)
8393
return f"{self.prefix}/{SESSION_PREFIX}{session_id}/"
8494

8595
def _get_agent_path(self, session_id: str, agent_id: str) -> str:
86-
"""Get agent S3 prefix."""
96+
"""Get agent S3 prefix.
97+
98+
Args:
99+
session_id: ID for the session.
100+
agent_id: ID for the agent.
101+
102+
Raises:
103+
ValueError: If session id or agent id contains a path separator.
104+
"""
87105
session_path = self._get_session_path(session_id)
106+
agent_id = _identifier.validate(agent_id, _identifier.Identifier.AGENT)
88107
return f"{session_path}agents/{AGENT_PREFIX}{agent_id}/"
89108

90109
def _get_message_path(self, session_id: str, agent_id: str, message_id: int) -> str:

tests/strands/agent/test_agent.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,18 @@ def test_agent__init__deeply_nested_tools(tool_decorated, tool_module, tool_impo
250250
assert tru_tool_names == exp_tool_names
251251

252252

253+
@pytest.mark.parametrize(
254+
"agent_id",
255+
[
256+
"a/../b",
257+
"a/b",
258+
],
259+
)
260+
def test_agent__init__invalid_id(agent_id):
261+
with pytest.raises(ValueError, match=f"agent_id={agent_id} | id cannot contain path separators"):
262+
Agent(agent_id=agent_id)
263+
264+
253265
def test_agent__call__(
254266
mock_model,
255267
system_prompt,

0 commit comments

Comments
 (0)