From ce4bd5db919f249c84d01913bd3b16d8024fd63b Mon Sep 17 00:00:00 2001 From: badMade <106821302+badMade@users.noreply.github.com> Date: Sat, 11 Apr 2026 15:35:49 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20Fix=20path=20traversal=20in=20session=20stores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL 💡 Vulnerability: Python `session_store.py` and Rust `session_control.rs` use the `session_id` to read and write local files (`{session_id}.json`) without validation. A malicious input string containing `../../` allows an attacker to read or overwrite arbitrary `.json` files on the filesystem. 🎯 Impact: This path traversal vulnerability could lead to information disclosure or data destruction if a manipulated session reference is processed. 🔧 Fix: Added strict string validation checks (`validate_session_id`) inside the data access patterns in Python (`save_session`, `load_session`) and Rust (`create_handle`, `resolve_managed_path`) to reject inputs containing path separators (`/`, `\`) or traversal elements (`.`, `..`). ✅ Verification: Python and Rust tests continue to pass and specifically test scripts constructed for `../../../tmp/file` inputs are now safely rejected by both data planes. --- .jules/sentinel.md | 4 ++++ rust/crates/runtime/src/session_control.rs | 18 +++++++++++++----- src/session_store.py | 7 +++++++ 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 0000000000..515865e155 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 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. +**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. diff --git a/rust/crates/runtime/src/session_control.rs b/rust/crates/runtime/src/session_control.rs index 3b33f6a367..df6c6cc213 100644 --- a/rust/crates/runtime/src/session_control.rs +++ b/rust/crates/runtime/src/session_control.rs @@ -74,13 +74,20 @@ impl SessionStore { &self.workspace_root } - #[must_use] - pub fn create_handle(&self, session_id: &str) -> SessionHandle { + pub fn validate_session_id(session_id: &str) -> Result<(), SessionControlError> { + if session_id.contains('/') || session_id.contains('\\') || session_id.contains("..") || session_id == "." { + return Err(SessionControlError::Format(format!("Invalid session ID: {session_id}"))); + } + Ok(()) + } + + pub fn create_handle(&self, session_id: &str) -> Result { + Self::validate_session_id(session_id)?; let id = session_id.to_string(); let path = self .sessions_root .join(format!("{id}.{PRIMARY_SESSION_EXTENSION}")); - SessionHandle { id, path } + Ok(SessionHandle { id, path }) } pub fn resolve_reference(&self, reference: &str) -> Result { @@ -116,6 +123,7 @@ impl SessionStore { } pub fn resolve_managed_path(&self, session_id: &str) -> Result { + 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() { @@ -223,7 +231,7 @@ impl SessionStore { ) -> Result { let parent_session_id = session.session_id.clone(); let forked = session.fork(branch_name); - let handle = self.create_handle(&forked.session_id); + let handle = self.create_handle(&forked.session_id)?; let branch_name = forked .fork .as_ref() @@ -713,7 +721,7 @@ mod tests { session .push_user_text(text) .expect("session message should save"); - let handle = store.create_handle(&session.session_id); + let handle = store.create_handle(&session.session_id).expect("should create handle"); let session = session.with_persistence_path(handle.path.clone()); session .save_to_path(&handle.path) diff --git a/src/session_store.py b/src/session_store.py index 5f7502a56d..7132e5178e 100644 --- a/src/session_store.py +++ b/src/session_store.py @@ -16,7 +16,13 @@ class StoredSession: DEFAULT_SESSION_DIR = Path('.port_sessions') +def validate_session_id(session_id: str) -> None: + if "/" in session_id or "\\" in session_id or ".." in session_id or session_id in (".", ".."): + raise ValueError(f"Invalid session ID: {session_id}") + + def save_session(session: StoredSession, directory: Path | None = None) -> Path: + validate_session_id(session.session_id) target_dir = directory or DEFAULT_SESSION_DIR target_dir.mkdir(parents=True, exist_ok=True) path = target_dir / f'{session.session_id}.json' @@ -25,6 +31,7 @@ def save_session(session: StoredSession, directory: Path | None = None) -> Path: def load_session(session_id: str, directory: Path | None = None) -> StoredSession: + validate_session_id(session_id) target_dir = directory or DEFAULT_SESSION_DIR data = json.loads((target_dir / f'{session_id}.json').read_text()) return StoredSession( From ced170f8ca886ed006f6b0fd28cab979ca2094c5 Mon Sep 17 00:00:00 2001 From: badMade <106821302+badMade@users.noreply.github.com> Date: Sat, 11 Apr 2026 15:38:56 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20Fix=20path=20traversal=20in=20session=20stores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL 💡 Vulnerability: Python `session_store.py` and Rust `session_control.rs` use the `session_id` to read and write local files (`{session_id}.json`) without validation. A malicious input string containing `../../` allows an attacker to read or overwrite arbitrary `.json` files on the filesystem. 🎯 Impact: This path traversal vulnerability could lead to information disclosure or data destruction if a manipulated session reference is processed. 🔧 Fix: Added strict string validation checks (`validate_session_id`) inside the data access patterns in Python (`save_session`, `load_session`) and Rust (`create_handle`, `resolve_managed_path`) to reject inputs containing path separators (`/`, `\`) or traversal elements (`.`, `..`). ✅ Verification: Python and Rust tests continue to pass and specifically test scripts constructed for `../../../tmp/file` inputs are now safely rejected by both data planes. --- rust/crates/runtime/src/session_control.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rust/crates/runtime/src/session_control.rs b/rust/crates/runtime/src/session_control.rs index df6c6cc213..1771726449 100644 --- a/rust/crates/runtime/src/session_control.rs +++ b/rust/crates/runtime/src/session_control.rs @@ -75,8 +75,14 @@ impl SessionStore { } pub fn validate_session_id(session_id: &str) -> Result<(), SessionControlError> { - if session_id.contains('/') || session_id.contains('\\') || session_id.contains("..") || session_id == "." { - return Err(SessionControlError::Format(format!("Invalid session ID: {session_id}"))); + if session_id.contains('/') + || session_id.contains('\\') + || session_id.contains("..") + || session_id == "." + { + return Err(SessionControlError::Format(format!( + "Invalid session ID: {session_id}" + ))); } Ok(()) } @@ -721,7 +727,9 @@ mod tests { session .push_user_text(text) .expect("session message should save"); - let handle = store.create_handle(&session.session_id).expect("should create handle"); + let handle = store + .create_handle(&session.session_id) + .expect("should create handle"); let session = session.with_persistence_path(handle.path.clone()); session .save_to_path(&handle.path) From 39e2bb69ca5e7f7918b3ac3a1dd00c2eb7e514a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:44:01 +0000 Subject: [PATCH 3/5] chore: complete copilot review validation pass Agent-Logs-Url: https://github.com/badMade/claw-code/sessions/0444deeb-aa4f-4249-9b5b-b31621571087 Co-authored-by: badMade <106821302+badMade@users.noreply.github.com> --- .port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json | 9 +++++++++ .port_sessions/12df9fb8241443429885334481d2fcf3.json | 8 ++++++++ .port_sessions/ca9bd3a0962240338bce6e02a1933f52.json | 9 +++++++++ .port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json | 9 +++++++++ 4 files changed, 35 insertions(+) create mode 100644 .port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json create mode 100644 .port_sessions/12df9fb8241443429885334481d2fcf3.json create mode 100644 .port_sessions/ca9bd3a0962240338bce6e02a1933f52.json create mode 100644 .port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json diff --git a/.port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json b/.port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json new file mode 100644 index 0000000000..08abd6bfb1 --- /dev/null +++ b/.port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json @@ -0,0 +1,9 @@ +{ + "session_id": "0b6cc7246b184e3aa83d7def69b8ef4c", + "messages": [ + "review MCP tool", + "review MCP tool" + ], + "input_tokens": 6, + "output_tokens": 32 +} \ No newline at end of file diff --git a/.port_sessions/12df9fb8241443429885334481d2fcf3.json b/.port_sessions/12df9fb8241443429885334481d2fcf3.json new file mode 100644 index 0000000000..61a27fc01b --- /dev/null +++ b/.port_sessions/12df9fb8241443429885334481d2fcf3.json @@ -0,0 +1,8 @@ +{ + "session_id": "12df9fb8241443429885334481d2fcf3", + "messages": [ + "review MCP tool" + ], + "input_tokens": 3, + "output_tokens": 13 +} \ No newline at end of file diff --git a/.port_sessions/ca9bd3a0962240338bce6e02a1933f52.json b/.port_sessions/ca9bd3a0962240338bce6e02a1933f52.json new file mode 100644 index 0000000000..6c3f91d695 --- /dev/null +++ b/.port_sessions/ca9bd3a0962240338bce6e02a1933f52.json @@ -0,0 +1,9 @@ +{ + "session_id": "ca9bd3a0962240338bce6e02a1933f52", + "messages": [ + "review MCP tool", + "review MCP tool" + ], + "input_tokens": 6, + "output_tokens": 32 +} \ No newline at end of file diff --git a/.port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json b/.port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json new file mode 100644 index 0000000000..32ebcd8db5 --- /dev/null +++ b/.port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json @@ -0,0 +1,9 @@ +{ + "session_id": "dd34b9d9fe60452f841daaa8aa2ab061", + "messages": [ + "review MCP tool", + "review MCP tool" + ], + "input_tokens": 6, + "output_tokens": 32 +} \ No newline at end of file From c63af0755acffba702099b13c00b0b2a1db7f6bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:44:21 +0000 Subject: [PATCH 4/5] chore: remove accidentally committed local session artifacts Agent-Logs-Url: https://github.com/badMade/claw-code/sessions/0444deeb-aa4f-4249-9b5b-b31621571087 Co-authored-by: badMade <106821302+badMade@users.noreply.github.com> --- .port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json | 9 --------- .port_sessions/12df9fb8241443429885334481d2fcf3.json | 8 -------- .port_sessions/ca9bd3a0962240338bce6e02a1933f52.json | 9 --------- .port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json | 9 --------- 4 files changed, 35 deletions(-) delete mode 100644 .port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json delete mode 100644 .port_sessions/12df9fb8241443429885334481d2fcf3.json delete mode 100644 .port_sessions/ca9bd3a0962240338bce6e02a1933f52.json delete mode 100644 .port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json diff --git a/.port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json b/.port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json deleted file mode 100644 index 08abd6bfb1..0000000000 --- a/.port_sessions/0b6cc7246b184e3aa83d7def69b8ef4c.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "session_id": "0b6cc7246b184e3aa83d7def69b8ef4c", - "messages": [ - "review MCP tool", - "review MCP tool" - ], - "input_tokens": 6, - "output_tokens": 32 -} \ No newline at end of file diff --git a/.port_sessions/12df9fb8241443429885334481d2fcf3.json b/.port_sessions/12df9fb8241443429885334481d2fcf3.json deleted file mode 100644 index 61a27fc01b..0000000000 --- a/.port_sessions/12df9fb8241443429885334481d2fcf3.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "session_id": "12df9fb8241443429885334481d2fcf3", - "messages": [ - "review MCP tool" - ], - "input_tokens": 3, - "output_tokens": 13 -} \ No newline at end of file diff --git a/.port_sessions/ca9bd3a0962240338bce6e02a1933f52.json b/.port_sessions/ca9bd3a0962240338bce6e02a1933f52.json deleted file mode 100644 index 6c3f91d695..0000000000 --- a/.port_sessions/ca9bd3a0962240338bce6e02a1933f52.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "session_id": "ca9bd3a0962240338bce6e02a1933f52", - "messages": [ - "review MCP tool", - "review MCP tool" - ], - "input_tokens": 6, - "output_tokens": 32 -} \ No newline at end of file diff --git a/.port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json b/.port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json deleted file mode 100644 index 32ebcd8db5..0000000000 --- a/.port_sessions/dd34b9d9fe60452f841daaa8aa2ab061.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "session_id": "dd34b9d9fe60452f841daaa8aa2ab061", - "messages": [ - "review MCP tool", - "review MCP tool" - ], - "input_tokens": 6, - "output_tokens": 32 -} \ No newline at end of file From b59843aef476e8b039388302f312ecded192cde6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Apr 2026 08:29:16 +0000 Subject: [PATCH 5/5] chore: resolve PR merge conflicts with main Agent-Logs-Url: https://github.com/badMade/claw-code/sessions/4f913af6-1b3d-48c5-ac25-c30b4b994868 Co-authored-by: badMade <106821302+badMade@users.noreply.github.com> --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index b3e17c411e..a52be32a1a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,3 @@ target/ .DS_Store .vscode/ .idea/ -.port_sessions/