From 88b7e62cd46e2a23ad29a0c841efeaff03012c07 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 10:50:32 +0000 Subject: [PATCH] Fix path traversal in get_state_file Sanitized `session_id` to prevent path traversal vectors and arbitrary file writing. Expanded tests to cover adversarial payloads. --- .gitignore | 1 + hooks/security_guard.py | 3 ++- hooks/test_security_guard.py | 24 ++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 hooks/test_security_guard.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/hooks/security_guard.py b/hooks/security_guard.py index 97350b2..ccdb5cd 100644 --- a/hooks/security_guard.py +++ b/hooks/security_guard.py @@ -37,7 +37,8 @@ def debug_log(msg): def get_state_file(session_id): """Return path to session-specific state file for dedup.""" - return os.path.expanduser(f"~/.claude/.seal_guard_state_{session_id}.json") + safe_session_id = re.sub(r"[^a-zA-Z0-9_-]", "_", str(session_id)) + return os.path.expanduser(f"~/.claude/.seal_guard_state_{safe_session_id}.json") def load_shown(session_id): diff --git a/hooks/test_security_guard.py b/hooks/test_security_guard.py new file mode 100644 index 0000000..3a40c34 --- /dev/null +++ b/hooks/test_security_guard.py @@ -0,0 +1,24 @@ +import pytest +import os + +# Mocking os.path.expanduser to avoid touching real ~ +original_expanduser = os.path.expanduser + +def mock_expanduser(path): + return path.replace("~", "/tmp/mock_home") + +def test_get_state_file(monkeypatch): + monkeypatch.setattr(os.path, "expanduser", mock_expanduser) + + # We need to import get_state_file + import sys + sys.path.append(os.path.abspath(os.path.dirname(__file__))) + from security_guard import get_state_file + + path1 = get_state_file("normal_id_123") + assert "/tmp/mock_home/.claude/.seal_guard_state_normal_id_123.json" == path1 + + # With path traversal attack + path2 = get_state_file("../../../etc/passwd") + # After sanitization it should be: + assert "/tmp/mock_home/.claude/.seal_guard_state__________etc_passwd.json" == path2