fix: broaden session ID validator to support new hermes-agent format (#212)
* fix: broaden session ID validator to support new hermes-agent format * test: add more path traversal evil IDs to session validator test Add null byte, backslash, forward slash, and dot-extension variants to the rejected session ID test to cover additional attack vectors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Nathan Esquenazi <nesquena@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,7 @@ class Session:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, sid):
|
def load(cls, sid):
|
||||||
# Validate session ID format to prevent path traversal
|
# Validate session ID format to prevent path traversal
|
||||||
if not sid or not all(c in '0123456789abcdef' for c in sid):
|
if not sid or not all(c in '0123456789abcdefghijklmnopqrstuvwxyz_' for c in sid):
|
||||||
return None
|
return None
|
||||||
p = SESSION_DIR / f'{sid}.json'
|
p = SESSION_DIR / f'{sid}.json'
|
||||||
if not p.exists():
|
if not p.exists():
|
||||||
|
|||||||
@@ -154,8 +154,15 @@ class TestSessionIDValidation:
|
|||||||
result = Session.load(valid_hex)
|
result = Session.load(valid_hex)
|
||||||
assert result is None # No file, but no error
|
assert result is None # No file, but no error
|
||||||
|
|
||||||
|
def test_new_format_session_id_passes_validation(self):
|
||||||
|
"""New hermes-agent session IDs (YYYYMMDD_HHMMSS_xxxxxx) must pass validation."""
|
||||||
|
from api.models import Session
|
||||||
|
# Should pass the validator (returns None only because the file doesn't exist)
|
||||||
|
result = Session.load("20260406_164014_74b2d1")
|
||||||
|
assert result is None # file doesn't exist, but validator passed
|
||||||
|
|
||||||
def test_non_hex_session_id_rejected(self):
|
def test_non_hex_session_id_rejected(self):
|
||||||
"""A session ID with non-hex chars must be rejected."""
|
"""A session ID with dangerous chars must be rejected."""
|
||||||
from api.models import Session
|
from api.models import Session
|
||||||
evil_ids = [
|
evil_ids = [
|
||||||
"../../../etc/passwd",
|
"../../../etc/passwd",
|
||||||
@@ -163,11 +170,15 @@ class TestSessionIDValidation:
|
|||||||
"session; rm -rf /",
|
"session; rm -rf /",
|
||||||
"hello world",
|
"hello world",
|
||||||
"ZZZZZZZZZZZZZZZZ",
|
"ZZZZZZZZZZZZZZZZ",
|
||||||
|
"session\x00evil",
|
||||||
|
"..\\..\\windows\\system32",
|
||||||
|
"session/../../etc/passwd",
|
||||||
|
"valid_looking.json",
|
||||||
]
|
]
|
||||||
for sid in evil_ids:
|
for sid in evil_ids:
|
||||||
result = Session.load(sid)
|
result = Session.load(sid)
|
||||||
assert result is None, \
|
assert result is None, \
|
||||||
f"Session.load should reject non-hex ID '{sid}', got {result}"
|
f"Session.load should reject dangerous ID '{sid}', got {result}"
|
||||||
|
|
||||||
def test_empty_session_id_rejected(self):
|
def test_empty_session_id_rejected(self):
|
||||||
"""An empty session ID must be rejected."""
|
"""An empty session ID must be rejected."""
|
||||||
|
|||||||
Reference in New Issue
Block a user