Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ target/
.DS_Store
.vscode/
.idea/
.port_sessions/
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2025-04-11 - Path Traversal Vulnerability in Session IDs
**Vulnerability:** The Python `src/session_store.py` and Rust `rust/crates/runtime/src/session_control.rs` allow path traversal (e.g. `../` or `/`) via the `session_id` property when saving and loading sessions.
**Learning:** This exposes a critical vulnerability where an attacker controlling the `session_id` can write or read arbitrary `.json` / `.jsonl` files on the filesystem. The session identifiers must be strictly validated to exclude path separators ('/', '\') and special segments ('.', '..') to mitigate path traversal vulnerabilities.
Comment thread
badMade marked this conversation as resolved.
**Prevention:** Explicitly validate input IDs to ensure they only contain safe characters (e.g., alphanumeric, hyphens, underscores) or strictly reject path separators and directory traversal sequences.

## 2024-04-10 - Path Traversal in Session Storage
**Vulnerability:** Path traversal existed in both the Python (`src/session_store.py`) and Rust (`rust/crates/runtime/src/session_control.rs`) implementations because unsanitized `session_id` strings were used directly in file paths.
**Learning:** Both reference implementations lacked central validation logic for system identifiers derived from external/user input.
Expand Down
33 changes: 17 additions & 16 deletions rust/crates/runtime/src/session_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,17 @@ impl SessionStore {
&self.workspace_root
}

pub fn create_handle(&self, session_id: &str) -> Result<SessionHandle, SessionControlError> {
pub fn validate_session_id(session_id: &str) -> Result<(), SessionControlError> {
if !is_valid_session_id(session_id) {
return Err(SessionControlError::Format(format!(
"Invalid session ID: {session_id}"
)));
return Err(SessionControlError::Format(
"Invalid session ID".to_string(),
));
}
Ok(())
}
Comment thread
badMade marked this conversation as resolved.

pub fn create_handle(&self, session_id: &str) -> Result<SessionHandle, SessionControlError> {
Self::validate_session_id(session_id)?;
let id = session_id.to_string();
let path = self
.sessions_root
Expand Down Expand Up @@ -120,11 +125,7 @@ impl SessionStore {
}

pub fn resolve_managed_path(&self, session_id: &str) -> Result<PathBuf, SessionControlError> {
if !is_valid_session_id(session_id) {
return Err(SessionControlError::Format(format!(
"Invalid session ID: {session_id}"
)));
}
Self::validate_session_id(session_id)?;
for extension in [PRIMARY_SESSION_EXTENSION, LEGACY_SESSION_EXTENSION] {
let path = self.sessions_root.join(format!("{session_id}.{extension}"));
if path.exists() {
Expand Down Expand Up @@ -353,9 +354,9 @@ pub fn create_managed_session_handle_for(
session_id: &str,
) -> Result<SessionHandle, SessionControlError> {
if !is_valid_session_id(session_id) {
return Err(SessionControlError::Format(format!(
"Invalid session ID: {session_id}"
)));
return Err(SessionControlError::Format(
"Invalid session ID".to_string(),
));
}
let id = session_id.to_string();
let path =
Expand Down Expand Up @@ -420,9 +421,9 @@ pub fn resolve_managed_session_path_for(
session_id: &str,
) -> Result<PathBuf, SessionControlError> {
if !is_valid_session_id(session_id) {
return Err(SessionControlError::Format(format!(
"Invalid session ID: {session_id}"
)));
return Err(SessionControlError::Format(
"Invalid session ID".to_string(),
));
}
let directory = managed_sessions_dir_for(base_dir)?;
for extension in [PRIMARY_SESSION_EXTENSION, LEGACY_SESSION_EXTENSION] {
Expand Down Expand Up @@ -742,7 +743,7 @@ mod tests {
.expect("session message should save");
let handle = store
.create_handle(&session.session_id)
.expect("handle should create");
.expect("should create handle");
let session = session.with_persistence_path(handle.path.clone());
session
.save_to_path(&handle.path)
Expand Down
10 changes: 5 additions & 5 deletions src/session_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ class StoredSession:


def validate_session_id(session_id: str) -> None:
if not session_id:
raise ValueError("Invalid session ID: cannot be empty")
if "/" in session_id or "\\" in session_id:
raise ValueError(f"Invalid session ID: contains path separators ({session_id})")
raise ValueError("Invalid session ID: contains path separators")
if session_id in (".", ".."):
raise ValueError(f"Invalid session ID: cannot be '.' or '..' ({session_id})")
raise ValueError("Invalid session ID: cannot be '.' or '..'")
if ".." in session_id:
raise ValueError(
f"Invalid session ID: contains directory traversal ('..') ({session_id})"
)
raise ValueError("Invalid session ID: contains directory traversal ('..')")


def save_session(session: StoredSession, directory: Path | None = None) -> Path:
Expand Down